Add Google sign-in to VitNode - create an OAuth client, set the exact redirect URI and register the Google SSO adapter in your API config.
Google is the provider most people want first. The whole job is: create an OAuth
client in the Google Auth Platform, give it the redirect URI VitNode already
decided on, and pass the two credentials to GoogleSSOApiPlugin.
Quick start
If you already have a client ID and secret, this is the entire integration.
GOOGLE_CLIENT_ID=1234567890-abc.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxximport { GoogleSSOApiPlugin } from '@vitnode/core/api/adapters/sso/google'
import { buildApiConfig } from '@vitnode/core/vitnode.config'
export const vitNodeApiConfig = buildApiConfig({
plugins: [],
authorization: {
ssoAdapters: [
GoogleSSOApiPlugin({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
},
})Restart the API and a Google button appears on /login. The rest of this
page is how to get those two values.
Set up the OAuth client
Sign in to Google Cloud
Go to the Google Cloud Console and sign in with the account that should own the OAuth client. It does not have to be the account you sign in to your own site with.
Pick or create a project
An OAuth client belongs to a project, so select one from the project picker in the top bar - or create one if this is a fresh account. Name it after your site; the name is internal and never shown to visitors.
Open the Google Auth Platform
Type Google Auth Platform into the console's search bar and open the first
result. This is the section that used to be called "OAuth consent screen", and
it is where both the consent details and the clients live.
Fill in the consent details
Google will not issue a client until it knows what to show on the consent screen. Provide the App name - this is the name visitors read in "Sign in to continue to ..." - and a Support email.
Then, under Audience, choose External so anyone with a Google account can sign in. Internal restricts sign-in to a single Google Workspace organisation, which is almost never what a public site wants.
Create the client and set the redirect URI
Go to Clients in the left navigation and press Create client.
- Application type: Web application.
- Name: anything - this one is internal too.
- Under Authorized redirect URIs, add the URI for every origin you run.
VitNode builds this URI itself, from NEXT_PUBLIC_WEB_URL, so it has to match
character for character - no trailing slash, no locale prefix.
| Environment | Authorized redirect URI |
|---|---|
Development (NEXT_PUBLIC_WEB_URL unset) | http://localhost:3000/login/sso/google |
| Production | https://your-domain.com/login/sso/google |
Add both. Google allows several redirect URIs per client, and one client for dev and prod is fine while you are getting started.
redirect_uri_mismatch
This is the error you will get if the URI differs by so much as a trailing slash. It comes from Google, not from VitNode, and it is always this field.
Copy the client ID and secret
Open the client you just created from the Clients list. The Client ID and
Client secret are on the right-hand side of the detail panel. Copy both -
Google will let you see the secret again, but you may as well paste it straight
into .env.
Set the environment variables
These names are not magic - they are whatever you read in your config on the
next step. What matters is that they are server-side variables, so no
NEXT_PUBLIC_ prefix: that prefix is what marks a value for the browser bundle,
and a client secret in the browser bundle is a client secret you have published.
GOOGLE_CLIENT_ID=1234567890-abc.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxRegister the adapter
Add GoogleSSOApiPlugin to authorization.ssoAdapters in your API config.
Note that it is a plain factory function, not a class - there is no new.
import { GoogleSSOApiPlugin } from '@vitnode/core/api/adapters/sso/google'
import { buildApiConfig } from '@vitnode/core/vitnode.config'
export const vitNodeApiConfig = buildApiConfig({
plugins: [],
authorization: {
ssoAdapters: [
GoogleSSOApiPlugin({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
},
})The redirect URI is computed when this file is evaluated, so restart the API rather than relying on a hot reload:
bun devpnpm devnpm run devVerify it works
Ask the API what it thinks it supports:
curl http://localhost:3000/api/@vitnode/core/middleware
# {"isEmail":false,"sso":[{"id":"google","name":"Google"}]}Then open /login and press Google. You should be sent to Google's consent
screen, bounced back to /login/sso/google?code=...&state=..., and land on the
front page signed in. Your account now has a row in core_users_sso with
providerId google.
Publish the app
While the client is unpublished, Google restricts sign-in to the test users you listed and shows an "unverified app" warning. Once the round trip works, go to Google Auth Platform → Audience and press Publish app.
What the adapter asks Google for
Useful when you are debugging a consent screen that shows more or fewer permissions than you expected.
| Setting | Value |
|---|---|
| Authorize URL | https://accounts.google.com/o/oauth2/auth |
| Token URL | https://oauth2.googleapis.com/token |
| Profile URL | https://www.googleapis.com/oauth2/v1/userinfo |
| Scopes | openid profile email |
| Provider id | google |
| Fields read | id, email, name, verified_email |
The account name VitNode creates comes from Google's name field, stripped of
characters that cannot appear in a profile URL.
Gotchas
Unverified Google emails are rejected
The adapter reads verified_email and throws a 400 when it is false, so a
Google account with an unconfirmed address cannot sign in at all. The visitor
sees the generic error screen rather than an explanation, which is the one
place this is worth knowing about in advance.
A missing key fails on click, not on boot
A GoogleSSOApiPlugin whose clientId is undefined registers happily - the
button renders and the provider is listed - and throws Missing Google client ID the moment somebody presses it. If the button is there but does nothing
except raise an error toast, check the environment before you check the
console.
An email that already has a password account is a dead end
If someone registered with the same address using the password form, the
callback answers 409 and offers them the login page instead. There is no
link-an-existing-account screen yet - see how the callback handles
accounts.