Detailed setup instructions for each supported OAuth provider.
Important distinction:
-
Built-in providers - Provider templates included in the OAuth plugin code (GitHub, Google, Azure, Auth0, Okta)
- Zero runtime overhead - code presence ≠ execution
- Not active until you configure them
- No security risk from unused providers
-
Active providers - Providers you explicitly configure with credentials
- Only configured providers are instantiated and available for authentication
- Each requires
clientId,clientSecret, and OAuth URLs - Only these providers accept login requests
Example: The OAuth plugin includes Okta code, but Okta authentication is not available unless you configure an Okta provider with credentials. Built-in providers are templates, not active endpoints.
Every provider setup below has you register a callback URL with the provider. That is only half of it — you must also tell the plugin to send that URL, via the plugin-level redirectUri option:
'@harperfast/oauth':
redirectUri: ${OAUTH_REDIRECT_URI} # e.g. https://yourdomain.com/oauth/callback
providers:
# ...- Set it once, at the plugin level (a sibling of
providers, not inside a provider). The plugin appends the provider name per request:https://yourdomain.com/oauth/callback→https://yourdomain.com/oauth/github/callback,.../oauth/google/callback, and so on. That's why the value you configure has no provider name in it but the URL you register with the provider does. - If you omit it, it defaults to
http://localhost:9926/oauth/callback. On a deployed app that default is a trap: the provider accepts the request and redirects your users tolocalhost— their own machine — so login never completes and no configuration error is raised anywhere. Set it explicitly on every deployed app. - After deploying, verify what the plugin actually sends.
See Understanding Redirects for how this differs from postLoginRedirect.
- Go to GitHub Settings > Developer settings > OAuth Apps
- Click "New OAuth App"
- Fill in the application details:
- Application name: Your app name
- Homepage URL:
https://yourdomain.com - Authorization callback URL:
https://yourdomain.com/oauth/github/callback
- Click "Register application"
- Copy the Client ID and generate a Client Secret
'@harperfast/oauth':
redirectUri: ${OAUTH_REDIRECT_URI} # required on any deployed app — see Callback URLs above
providers:
github:
clientId: ${OAUTH_GITHUB_CLIENT_ID}
clientSecret: ${OAUTH_GITHUB_CLIENT_SECRET}
scope: 'user:email' # Optional, default: 'user:email'export OAUTH_GITHUB_CLIENT_ID="your_client_id"
export OAUTH_GITHUB_CLIENT_SECRET="your_client_secret"
export OAUTH_REDIRECT_URI="https://yourdomain.com/oauth/callback"user- Access user profile datauser:email- Access user email addresses (default)read:user- Read-only access to user profile
GitHub OAuth Scopes Documentation
- Go to Google Cloud Console
- Create a new project or select an existing one
- Go to APIs & Services > Credentials
- Click "Create Credentials" > "OAuth 2.0 Client ID"
- Configure OAuth consent screen if prompted
- Select "Web application" as application type
- Add authorized redirect URI:
https://yourdomain.com/oauth/google/callback - Copy the Client ID and Client Secret
'@harperfast/oauth':
redirectUri: ${OAUTH_REDIRECT_URI} # required on any deployed app — see Callback URLs above
providers:
google:
clientId: ${OAUTH_GOOGLE_CLIENT_ID}
clientSecret: ${OAUTH_GOOGLE_CLIENT_SECRET}
scope: 'openid profile email' # Optional, this is the defaultexport OAUTH_GOOGLE_CLIENT_ID="your_client_id"
export OAUTH_GOOGLE_CLIENT_SECRET="your_client_secret"
export OAUTH_REDIRECT_URI="https://yourdomain.com/oauth/callback"openid- OpenID Connect (required)profile- Access basic profile informationemail- Access email address
Google OAuth Scopes Documentation
- Go to Azure Portal
- Navigate to Azure Active Directory > App registrations
- Click "New registration"
- Fill in:
- Name: Your application name
- Supported account types: Choose appropriate option
- Redirect URI: Web -
https://yourdomain.com/oauth/azure/callback
- Click "Register"
- Copy the Application (client) ID and Directory (tenant) ID
- Go to Certificates & secrets > New client secret
- Copy the Client Secret value
'@harperfast/oauth':
redirectUri: ${OAUTH_REDIRECT_URI} # required on any deployed app — see Callback URLs above
providers:
azure:
clientId: ${OAUTH_AZURE_CLIENT_ID}
clientSecret: ${OAUTH_AZURE_CLIENT_SECRET}
tenantId: ${OAUTH_AZURE_TENANT_ID}
scope: 'openid profile email' # Optional, this is the defaultexport OAUTH_AZURE_CLIENT_ID="your_client_id"
export OAUTH_AZURE_CLIENT_SECRET="your_client_secret"
export OAUTH_AZURE_TENANT_ID="your_tenant_id"
export OAUTH_REDIRECT_URI="https://yourdomain.com/oauth/callback"openid- OpenID Connect (required)profile- Access profile informationemail- Access email addressUser.Read- Read user profile
- Go to Auth0 Dashboard
- Navigate to Applications > Applications
- Click "Create Application"
- Choose "Regular Web Application"
- Go to Settings tab
- Copy Domain, Client ID, and Client Secret
- Add to Allowed Callback URLs:
https://yourdomain.com/oauth/auth0/callback - Add to Allowed Logout URLs:
https://yourdomain.com(optional) - Save changes
'@harperfast/oauth':
redirectUri: ${OAUTH_REDIRECT_URI} # required on any deployed app — see Callback URLs above
providers:
auth0:
domain: ${OAUTH_AUTH0_DOMAIN}
clientId: ${OAUTH_AUTH0_CLIENT_ID}
clientSecret: ${OAUTH_AUTH0_CLIENT_SECRET}
scope: 'openid profile email' # Optional, this is the defaultexport OAUTH_AUTH0_DOMAIN="yourapp.auth0.com"
export OAUTH_AUTH0_CLIENT_ID="your_client_id"
export OAUTH_AUTH0_CLIENT_SECRET="your_client_secret"
export OAUTH_REDIRECT_URI="https://yourdomain.com/oauth/callback"openid- OpenID Connect (required)profile- Access profile informationemail- Access email address
- Go to Okta Developer Console
- Navigate to Applications > Applications
- Click "Create App Integration"
- Choose "OIDC - OpenID Connect"
- Select "Web Application"
- Fill in:
- App integration name: Your application name
- Sign-in redirect URIs:
https://yourdomain.com/oauth/okta/callback - Sign-out redirect URIs:
https://yourdomain.com(optional)
- Click "Save"
- Copy the Client ID and Client Secret
- Note your Okta domain (e.g.,
dev-12345.okta.com)
'@harperfast/oauth':
redirectUri: ${OAUTH_REDIRECT_URI} # required on any deployed app — see Callback URLs above
providers:
okta:
domain: ${OAUTH_OKTA_DOMAIN}
clientId: ${OAUTH_OKTA_CLIENT_ID}
clientSecret: ${OAUTH_OKTA_CLIENT_SECRET}
scope: 'openid profile email groups' # Optional, this is the defaultexport OAUTH_OKTA_DOMAIN="dev-12345.okta.com"
export OAUTH_OKTA_CLIENT_ID="your_client_id"
export OAUTH_OKTA_CLIENT_SECRET="your_client_secret"
export OAUTH_REDIRECT_URI="https://yourdomain.com/oauth/callback"openid- OpenID Connect (required)profile- Access profile informationemail- Access email addressgroups- Access user's group memberships (for role mapping)
Okta supports mapping user groups to roles. The plugin will use the first group as the user's role:
'@harperfast/oauth':
providers:
okta:
domain: ${OAUTH_OKTA_DOMAIN}
clientId: ${OAUTH_OKTA_CLIENT_ID}
clientSecret: ${OAUTH_OKTA_CLIENT_SECRET}
scope: 'openid profile email groups'
# First group will be used as role, falls back to defaultRole
defaultRole: 'user'To include groups in the ID token:
- In Okta Admin Console, go to Security > API > Authorization Servers
- Select your authorization server (or "default")
- Go to Claims tab
- Add a claim with:
- Name:
groups - Include in token type: ID Token, Always
- Value type: Groups
- Filter: Matches regex
.*(or filter to specific groups)
- Name:
For other OpenID Connect compatible providers:
'@harperfast/oauth':
redirectUri: ${OAUTH_REDIRECT_URI} # required on any deployed app — see Callback URLs above
providers:
custom:
clientId: ${OAUTH_CUSTOM_CLIENT_ID}
clientSecret: ${OAUTH_CUSTOM_CLIENT_SECRET}
authorizationUrl: ${OAUTH_CUSTOM_AUTHORIZATION_URL}
tokenUrl: ${OAUTH_CUSTOM_TOKEN_URL}
userInfoUrl: ${OAUTH_CUSTOM_USERINFO_URL}
jwksUri: ${OAUTH_CUSTOM_JWKS_URL} # note: jwksUri, not jwksUrl
scope: 'openid profile email'export OAUTH_REDIRECT_URI="https://yourdomain.com/oauth/callback"
export OAUTH_CUSTOM_CLIENT_ID="your_client_id"
export OAUTH_CUSTOM_CLIENT_SECRET="your_client_secret"
export OAUTH_CUSTOM_AUTHORIZATION_URL="https://provider.com/oauth/authorize"
export OAUTH_CUSTOM_TOKEN_URL="https://provider.com/oauth/token"
export OAUTH_CUSTOM_USERINFO_URL="https://provider.com/oauth/userinfo"
export OAUTH_CUSTOM_JWKS_URL="https://provider.com/.well-known/jwks.json"- Start your Harper application
- Navigate to
http://localhost:9926/oauth/{provider}/login - Complete the OAuth flow
- Check your session for OAuth data
The login route is a 302, so you can inspect exactly what the plugin sends the provider without completing a login. Do this after every deployment:
curl -sS -D - -o /dev/null https://yourdomain.com/oauth/github/login | grep -i '^location'In the returned Location header, confirm:
redirect_uriis your public origin —https%3A%2F%2Fyourdomain.com%2Foauth%2Fgithub%2Fcallback, notlocalhostclient_idis a real credential — not%24%7BOAUTH_..._CLIENT_ID%7D, which is a URL-encoded${OAUTH_..._CLIENT_ID}and means the environment variable never reached the running app
Both failures happen before the provider is involved, so neither shows up in your provider's logs.
Symptom: login appears to work — the provider's consent screen shows, you approve — and then the browser lands on http://localhost:9926/oauth/{provider}/callback and stalls or loops. No error appears in the app log.
Cause: redirectUri isn't set, so the plugin fell back to its http://localhost:9926/oauth/callback default and sent that to the provider. It reaches the consent screen (rather than failing with redirect_uri_mismatch) whenever localhost is also registered with the provider — a very common leftover from local development.
Solution: set the plugin-level redirectUri to your public origin, as described in Callback URLs. Registering the URL with your provider does not affect what the plugin sends.
Symptom: the provider errors immediately with invalid_client / "OAuth client was not found", and the authorization URL contains client_id=%24%7BOAUTH_GITHUB_CLIENT_ID%7D.
Cause: ${VAR} placeholders in config.yaml are substituted from the environment of the running app. When a variable is undefined, the placeholder is passed through as a literal string rather than raising a configuration error — so the plugin starts up looking healthy and ships ${OAUTH_GITHUB_CLIENT_ID} to the provider verbatim.
Solution: make the variable available to the deployed app, not just your shell — for Harper Fabric, see managing runtime environment variables. Then re-check with the curl above.
Error: redirect_uri_mismatch or similar
Solution: the URL the plugin sends and the URL registered with the provider must match exactly. Check both sides:
- Plugin:
redirectUriis set to your public origin plus/oauth/callback(the plugin appends the provider name itself) - Provider: the registered callback is the provider-specific form:
https://yourdomain.com/oauth/{provider}/callback
Watch for a trailing slash, http vs https, and www. differences — providers compare byte-for-byte.
Error: invalid_client or unauthorized_client
Solution:
- Verify client ID and secret are correct
- Check environment variables are set in the deployed app's environment, and that they expanded (see above)
- Ensure client secret hasn't expired
Error: User email not available
Solution:
- Verify email scope is requested
- Check provider consent screen configuration
- Ensure user has granted email permission