@molecule/api-oauth-google
Provider bond · auth · API (Node) · v1.2.0 · Apache-2.0
Google OAuth provider for molecule.dev.
npm install @molecule/api-oauth-googlenpm · Source on GitHub · Implements @molecule/api-oauth
How it works
@molecule/api-oauth-google is a provider bond on the API (Node) side: it implements the auth core interface (@molecule/api-oauth) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
// npm install @molecule/api-oauth-google --workspace=api
// Env: OAUTH_GOOGLE_CLIENT_ID, OAUTH_GOOGLE_CLIENT_SECRET (server-only).
import { bond } from '@molecule/api-bond'
import { getAuthorizeUrl, serverName, verify } from '@molecule/api-oauth-google'
// api/src/bonds/oauth-google.ts, called from setupBonds() — the `oauth`
// category is NAMED: providers coexist, and the bond name IS the
// serverName ('google').
bond('oauth', serverName, { serverName, verify, getAuthorizeUrl })
// Redirect: Google matches redirect URIs exactly — register the app origin
// + each OAuth-starting page path (e.g. {origin} and {origin}/login) under
// "Authorized redirect URIs"; the login button just links to
// GET /users/oauth/google.Works with: @molecule/api-bond, @molecule/api-http, @molecule/api-oauth, @molecule/api-secrets
Secrets: OAUTH_GOOGLE_CLIENT_ID, OAUTH_GOOGLE_CLIENT_SECRET
Live demos
Running apps built with Molecule that install this package — open the app or study the template.
Reference
Google OAuth provider for molecule.dev.
Setup
-
Log into Google (or sign up).
-
Open the Google API Console and create a new project if you do not already have one.
If you're releasing your app on Google Play, you can use the "Google Play Console Developer" project automatically created by Google Play.
-
Configure your project's OAuth consent screen if you have not already.
3.1. Open the OAuth consent screen page for your project.
3.2. For most apps, choose the "External" option for "User Type".
3.3. Fill out your app information. Note that if you include a logo, you may have to go through a verification process with Google which can take 4 to 6 weeks.
3.4. At a minimum, add the
/auth/userinfo.emailand/auth/userinfo.profilescopes.3.5. Add at least one test user. You can enter your current Google login's email address.
-
Open your project's Credentials page.
-
Google may have already automatically created an "Web client" for you, found under the "OAuth 2.0 Client IDs" section. If not, you will need to create an OAuth client.
5a. If you already have an OAuth client you would like to use, view it by clicking the name. Your client credentials should be visible on the right.
-
Copy the client ID and set it to your API's
OAUTH_GOOGLE_CLIENT_IDenvironment variable. -
Copy the client secret and set it to your API's
OAUTH_GOOGLE_CLIENT_SECRETenvironment variable.
5b. To create a new OAuth client, click "Create credentials" at the top and choose "OAuth client ID".
-
Choose "Web application" for the application type.
-
Enter the name of your Google OAuth client.
-
For development, add
http://localhost:3000to the "Authorized JavaScript origins", and add BOTHhttp://localhost:3000andhttp://localhost:3000/login(plus any other page your app starts OAuth from) to the "Authorized redirect URIs". Google matches redirect URIs exactly, and the API sendsredirect_uri = APP_ORIGIN + the initiating page's path— a login started from/loginredirects back tohttp://localhost:3000/login. -
For production, do the same with your app's origin (this should match your API's
APP_ORIGINenvironment variable): the origin as a JavaScript origin, and the origin + each OAuth-initiating page path as redirect URIs. -
Click "Create".
-
Copy the client ID and set it to your API's
OAUTH_GOOGLE_CLIENT_IDenvironment variable. -
Copy the client secret and set it to your API's
OAUTH_GOOGLE_CLIENT_SECRETenvironment variable.
-
-
Restart your API and/or rebuild your app so that they have the environment variables.
Your users should now be able to log in via Google!
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
// npm install @molecule/api-oauth-google --workspace=api
// Env: OAUTH_GOOGLE_CLIENT_ID, OAUTH_GOOGLE_CLIENT_SECRET (server-only).
import { bond } from '@molecule/api-bond'
import { getAuthorizeUrl, serverName, verify } from '@molecule/api-oauth-google'
// api/src/bonds/oauth-google.ts, called from setupBonds() — the `oauth`
// category is NAMED: providers coexist, and the bond name IS the
// serverName ('google').
bond('oauth', serverName, { serverName, verify, getAuthorizeUrl })
// Redirect: Google matches redirect URIs exactly — register the app origin
// + each OAuth-starting page path (e.g. {origin} and {origin}/login) under
// "Authorized redirect URIs"; the login button just links to
// GET /users/oauth/google.
Type
provider
Installation
npm install @molecule/api-oauth-google @molecule/api-bond @molecule/api-http @molecule/api-oauth @molecule/api-secrets
API
Interfaces
OAuthUserProps
The properties returned when verifying an OAuth code.
interface OAuthUserProps {
/**
* An alphanumeric username derived from the OAuth provider.
*
* Format: `{provider_username}@{provider_name}`
*/
username: string
/**
* The user's display name from the OAuth provider.
*/
name?: string
/**
* The user's short biography / description from the OAuth provider
* (e.g. GitHub's `bio`, GitLab's `bio`, X's `description`). Omitted when
* the provider exposes no such field.
*/
bio?: string
/**
* URL of the user's profile image from the OAuth provider (e.g. Google's
* `picture`, GitHub/GitLab's `avatar_url`, X's `profile_image_url`).
* Omitted when the provider exposes none (Apple never does; Microsoft
* Graph only serves photos as a binary endpoint behind an extra scope).
*/
avatar?: string
/**
* The user's email address from the OAuth provider.
*/
email?: string
/**
* Whether the OAuth provider has affirmatively verified that the user
* controls this `email` mailbox.
*
* `true` MUST mean the provider proved mailbox ownership (e.g. Google's
* `email_verified`, Apple's `email_verified` ID-token claim). When the
* provider exposes no trustworthy verification signal in the profile data
* the verifier fetched, this MUST be `false`/`undefined` (never optimistically
* `true`) — consumers treat only an explicit `true` as verified.
*
* Consumers (e.g. the user resource's `logInOAuth` handler) use this to
* decide whether a provider-supplied email may be trusted over an existing,
* unverified local account — preventing an unverified squatter from blocking
* the verified mailbox owner.
*/
emailVerified?: boolean
/**
* The OAuth server identifier (e.g., 'github', 'google', 'twitter').
*/
oauthServer: string
/**
* Unique identifier for the user from the OAuth provider.
*/
oauthId: string
/**
* Raw user data from the OAuth provider.
*/
oauthData: Record<string, unknown>
}
OAuthAuthorizeUrlParams
Parameters for building a provider authorization (initiation) URL — the URL the user's browser is redirected to so the provider can authenticate them and send back an authorization code.
interface OAuthAuthorizeUrlParams {
/**
* Absolute URI the provider should redirect the user back to after
* authorization (the app origin, optionally with a path). When omitted,
* the builder leaves `redirect_uri` off the URL so the provider falls
* back to its registered callback URL.
*/
redirectUri?: string
/**
* The CSRF `state` parameter bound to the initiating session (stored in
* an httpOnly cookie by the initiation endpoint and validated by the
* login handler on callback).
*/
state: string
/**
* PKCE code challenge derived (S256) from the per-session code verifier.
* Omit only for providers that do not support PKCE.
*/
codeChallenge?: string
/**
* PKCE challenge method. Always prefer `'S256'`; `'plain'` exists only
* for providers that cannot hash.
*/
codeChallengeMethod?: 'S256' | 'plain'
}
Types
OAuthVerifier
Exchanges an OAuth authorization code for user profile information.
Implementations call the provider's token and user-info endpoints,
then return normalized OAuthUserProps for account creation or login.
Returning null means the provider AFFIRMATIVELY rejected the code
(e.g. GitHub's bad_verification_code, an expired/forged code) — the
consumer (logInOAuth) surfaces that as a clean 403 "verification
failed". A thrown error means an infrastructure failure (network,
provider outage) and surfaces as a 500. Implementations MUST NOT throw
for a rejected code — that would misreport a client mistake (or an
attack) as a server fault.
type OAuthVerifier = (
code: string,
codeVerifier?: string,
redirectUri?: string,
) => Promise<OAuthUserProps | null>
code— The authorization code received from the OAuth redirect.codeVerifier— The PKCE code verifier, if the flow uses proof-key exchange.redirectUri— The redirect URI that was used in the authorization request.
OAuthAuthorizeUrlBuilder
Builds the provider's authorization URL for OAuth initiation
(GET /users/oauth/:provider → 302 to this URL). Implementations embed
their own client id, scopes, and authorize endpoint so no consumer ever
hardcodes provider knowledge.
type OAuthAuthorizeUrlBuilder = (params: OAuthAuthorizeUrlParams) => string | null
params— State, PKCE challenge, and optional redirect URI.
Returns: The absolute authorization URL, or null when the provider is not configured (e.g. its client id env var is unset).
Functions
getAuthorizeUrl(params)
Builds the Google authorization URL for OAuth initiation
(GET /users/oauth/:provider 302s the browser here). Embeds this bond's
client id and OpenID scopes (openid email profile — exactly what
verify's OpenID userinfo call reads: sub, email, email_verified,
and profile fields) plus the caller's CSRF state and PKCE S256
challenge, so no consumer hardcodes Google knowledge. The authorize
endpoint defaults to Google's v2 endpoint but can be overridden via
OAUTH_GOOGLE_AUTHORIZE_URL for proxy deployments or E2E mock OAuth
servers (consistent with OAUTH_GOOGLE_TOKEN_URL / OAUTH_GOOGLE_USER_URL).
Deliberately does NOT request access_type=offline: verify uses the
access token exactly once (a single userinfo fetch) and never stores or
uses a refresh token, so requesting standing offline access would ask
users to consent to access this bond never exercises. Do not "helpfully"
add it back unless the bond actually starts persisting and refreshing
tokens.
function getAuthorizeUrl({
redirectUri,
state,
codeChallenge,
codeChallengeMethod,
}: OAuthAuthorizeUrlParams): string | null
params— State, PKCE challenge, and optional redirect URI.
Returns: The Google authorize URL, or null when OAUTH_GOOGLE_CLIENT_ID is unset.
verify(code, codeVerifier, redirectUri)
Verifies a Google OAuth code and responds with OAuth-related user props.
Endpoints can be overridden via OAUTH_GOOGLE_TOKEN_URL and
OAUTH_GOOGLE_USER_URL for testing (E2E mocks) or proxy deployments.
function verify(
code: string,
codeVerifier?: string,
redirectUri?: string,
): Promise<{
username: string
name: string | undefined
avatar: string | undefined
email: string | undefined
emailVerified: boolean
oauthServer: 'google'
oauthId: string
oauthData: Record<string, unknown>
} | null>
code— The authorization code from the OAuth callback.codeVerifier— The PKCE code verifier (if PKCE was used).redirectUri— The redirect URI used in the authorization request.
Returns: An OAuthUserInfo with the user's Google email/sub, and OAuth ID — or null when Google affirmatively rejected the code (HTTP 400 invalid_grant: bad/expired/already-redeemed code or PKCE verifier mismatch), which the consumer surfaces as a 403 rather than a 500.
Constants
oauthGoogleSecretDefinitions
Secret definitions required by the Google OAuth bond.
const oauthGoogleSecretDefinitions: SecretDefinition[]
serverName
The OAuth server identifier for Google.
const serverName: 'google'
Core Interface
Implements @molecule/api-oauth interface.
Bond Wiring
Setup function to register this provider with the bond system:
import { bond } from '@molecule/api-bond'
import { serverName, verify, getAuthorizeUrl } from '@molecule/api-oauth-google'
export function setupOauthGoogle(): void {
bond('oauth', serverName, { serverName, verify, getAuthorizeUrl })
}
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-http^1.0.1@molecule/api-oauth^1.0.1@molecule/api-secrets^1.0.1
Environment Variables
OAUTH_GOOGLE_CLIENT_ID(required) — Google OAuth client ID- Setup: Google Cloud Console → APIs & Services → Credentials → Create OAuth 2.0 Client ID (Web application); add your app origin as an authorized JavaScript origin, and the app origin plus each page path that starts OAuth (e.g. {appUrl}/login) as authorized redirect URIs.
- Get it here: https://console.cloud.google.com/apis/credentials
- Example:
1234567890-abc.apps.googleusercontent.com
OAUTH_GOOGLE_CLIENT_SECRET(required) — Google OAuth client secret- Setup: Shown when creating the OAuth 2.0 Client ID in Google Cloud Console.
- Get it here: https://console.cloud.google.com/apis/credentials
- Example:
GOCSPX-...
Runtime Dependencies
@molecule/api-bond@molecule/api-http@molecule/api-oauth@molecule/api-secrets
The token exchange (verify's call to Google's token endpoint) is
application/x-www-form-urlencoded, per RFC 6749 §4.1.3 and Google's own
docs — matching every other molecule.dev OAuth bond (gitlab, twitter,
github, apple, microsoft).
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Clicking the app's "Sign in with {provider}" button (Google, GitHub, …) redirects to the provider's authorize URL carrying the correct client_id, the app's requested scopes, AND the app's registered redirect_uri — inspect the actual outbound URL (the 302 Location, or the address the popup/tab navigates to) and confirm each value; a missing or wrong one is the bug.
- The callback route exchanges the returned code SERVER-SIDE for a token, fetches the profile, and creates-or-links the app user + establishes a session — after the round-trip the app shows that user logged in. CAVEAT: the provider's own consent screen runs on ITS domain and CANNOT be driven in the sandbox, so verify the two boundaries you DO own — the authorize URL going out (above) and the callback coming back — not the provider's page. Complete the round-trip with a test/stub provider bond if one is wired; otherwise assert the callback handler's own behavior (state check → code exchange → user create-or-link → session). Never mock the flow or edit production code to bypass the provider.
- A returning OAuth user logs into the SAME account — sign in twice and confirm one user row linked by provider id (oauthServer + oauthId), not a fresh duplicate created each time.
- SECURITY — the
stateparameter is generated on initiation and verified on callback (CSRF protection): a mismatched or absentstateis rejected (403); theredirect_uriis validated against an allowlist so an attacker cannot redirect the code elsewhere; and the client secret + tokens stay server-side — grep the browser bundle and network tab to confirm the secret never reaches the client (only the authorize URL and returned code cross the boundary).