new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Better Auth · all subjects

oauth & social sign-on

58 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

account_already_linked_to_different_user debug via database

To debug the account_already_linked_to_different_user error locally, inspect the account database table, which should contain rows keyed by providerId (e.g., 'google') and accountId (e.g., OIDC sub), pointing to a userId. Identify which user currently owns the provider link and decide whether to unlink, merge, or keep as-is. Verify your app is connected to the expected database and environment (dev/staging/prod) to avoid confusion due to shared credentials or misconfigured environment variables.

account_already_linked_to_different_user is a security safeguard

The account_already_linked_to_different_user error is a security safeguard that prevents an OAuth identity that already belongs to one user from being attached to another user without explicit action. If a legitimate merge is intended, perform a controlled merge or unlink-then-link flow rather than bypassing the check.

account_already_linked_to_different_user common causes

Common causes of the account_already_linked_to_different_user error include: previously signing in or signing up using the provider on a different user in the same project; having two local users created via email/password or magic link with the provider linked to one and attempting to link the same provider to the other; test/preview environments sharing the same OAuth provider configuration and database with the provider account already linked to a different user record; data migration or manual database edits leaving a stale link pointing to the wrong user; relying on email matching to decide linking when the actual unique key is the provider account identifier (providerId + accountId), and that mapping exists for another user.

account_already_linked_to_different_user error overview

The account_already_linked_to_different_user error occurs during the OAuth flow when attempting to link an OAuth provider account to the currently authenticated user, but that exact provider account is already linked to another user in the project. To prevent account takeover, Better Auth blocks the link and throws this error. This situation is only possible through the OAuth flow (e.g., Google, GitHub, etc.) and is not triggered by email/password flows on their own.

account_already_linked_to_different_user prevention patterns

Safer patterns to prevent the account_already_linked_to_different_user error include: avoiding automatically linking a provider to whichever user is currently signed in unless you explicitly confirm ownership with the user; if providing a 'Connect account' UI, clearly communicate which user will receive the link and what to do if the provider is already linked elsewhere; considering disabling linking for providers you only want to use for sign-in, to avoid accidental cross-linking.

account_already_linked_to_different_user provider considerations

When dealing with the account_already_linked_to_different_user error, ensure you request stable user identifiers from the provider (e.g., OIDC openid scope) so accountId remains consistent across sessions. If you changed provider projects/tenants, identifiers may differ; confirm you are linking the correct provider credentials for the environment.

account_already_linked_to_different_user resolution steps

To resolve the account_already_linked_to_different_user error, log in as the user who already has the provider linked and unlink the provider from that account, then link it to the intended account. Alternatively, if both accounts belong to the same person and you want a single user, merge the accounts by choosing a primary user, moving sessions and linked accounts from the secondary user to the primary, then deactivating or deleting the secondary.

unable_to_get_user_info error overview

The unable_to_get_user_info error occurs only on the /api/auth/callback endpoint during an OAuth flow. After exchanging the authorization code for tokens, Better Auth fetches the user's profile from the provider. If the provider response is incorrect, empty, or missing required fields like id or email when needed, no usable user info can be derived and the request is rejected.

unable_to_get_user_info common causes

Common causes of the unable_to_get_user_info error include: missing or insufficient scopes so the provider does not return profile data; the provider returning an error or empty profile object for the user info request; token exchange succeeding but the user info request failing due to network error, 401/403, or invalid token; provider configuration or environment mismatch causing unexpected or minimal claims; or temporary provider outage or rate limiting.

unable_to_get_user_info resolution - request the right data

To resolve unable_to_get_user_info errors related to data, start the OAuth flow using Better Auth methods so the correct scopes and parameters are used. Ensure your provider app is configured to return basic profile details needed by your app.

unable_to_get_user_info resolution - verify configuration

To resolve unable_to_get_user_info errors related to configuration, confirm the client credentials and callback URL match the environment you are testing (dev/staging/prod). If the provider supports different response modes or endpoints, ensure they align with the integration you use.

Apple provider configuration code example

```ts import { betterAuth } from "better-auth" import { importPKCS8, SignJWT } from "jose"; async function generateAppleClientSecret(clientId, teamId, keyId, privateKey) { const key = await importPKCS8(privateKey, "ES256"); const now = Math.floor(Date.now() / 1000); return new SignJWT({}) .setProtectedHeader({ alg: "ES256", kid: keyId }) .setIssuer(teamId) .setSubject(clientId) .setAudience("https://appleid.apple.com") .setIssuedAt(now) .setExpirationTime(now + 180 * 24 * 60 * 60) .sign(key); } export const auth = betterAuth({ socialProviders: { apple: async () => ({ clientId: process.env.APPLE_CLIENT_ID as string, clientSecret: await generateAppleClientSecret( process.env.APPLE_CLIENT_ID!, process.env.APPLE_TEAM_ID!, process.env.APPLE_KEY_ID!, process.env.APPLE_PRIVATE_KEY!, ), appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER as string, }), }, trustedOrigins: ["https://appleid.apple.com"], }) ``` This example shows how to configure Apple sign-in with dynamic JWT generation for the client secret.

Apple appBundleIdentifier configuration for native iOS

On native iOS, the app uses the app ID (bundle ID) as client ID, not the service ID. If using the service ID as clientId in signIn.social with idToken, it throws an error: 'JWTClaimValidationFailed: unexpected "aud" claim value'. You must provide the appBundleIdentifier when signing in with Apple using the ID Token on native platforms.

Apple multiple audiences configuration

For multiple static audiences (e.g., a Service ID plus a native bundle ID), use clientId: string[] or audience: string[] instead of setting only appBundleIdentifier. When the audience must depend on the request (e.g., reading an x-platform header), use a custom verifyIdToken callback, which receives the request context as its third argument.

Apple sign-in usage with signIn.social

To sign in with Apple, use the signIn.social function provided by the auth client. Call authClient.signIn.social({ provider: "apple" }). The provider should be set to "apple".

Apple sign-in client code example

```ts import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "apple" }) } ``` This example shows basic Apple sign-in using the auth client.

Apple sign-in with ID Token

To sign in with Apple using the ID Token, call authClient.signIn.social with the provider set to "apple" and an idToken object containing: token (Apple ID Token), nonce (optional), and accessToken (optional). If ID token is provided, no redirection will happen and the user will be signed in directly.

Apple ID Token sign-in client example

```ts await authClient.signIn.social({ provider: "apple", idToken: { token: // Apple ID Token, nonce: // Nonce (optional), accessToken: // Access Token (optional) } }) ``` This example shows signing in with Apple using an ID Token from the client-side.

Apple sign-in credentials required

To use Apple sign-in, you need a client ID, Team ID, Key ID, and private key. These are obtained from the Apple Developer Portal at https://developer.apple.com/account/resources/authkeys/list. You must have an active Apple Developer account to access the developer portal.

Apple App ID setup for Sign In with Apple

In the Apple Developer Portal, navigate to Certificates, Identifiers & Profiles. Go to the Identifiers tab, click the + icon, select App IDs, then click Continue. Select App as the type. Enter a description (app name to display to users), set a Bundle ID in reverse domain format (e.g., com.yourcompany.yourapp), and optionally add a suffix like .ai. Scroll down to Capabilities and select the checkbox for Sign In with Apple. Click Continue, then Register.

Apple Service ID setup for OAuth

In the Identifiers tab of Apple Developer Portal, click the + icon, select Service IDs, then Continue. Enter a description. Set a unique identifier in reverse domain format, distinct from your App ID (e.g., com.yourcompany.yourapp.si where .si indicates service identifier). This Service ID becomes your clientId. Click Continue, then Register.

Apple Service ID configuration for Sign In

Find the Service ID in the Identifiers list and click on it. Check the Sign In with Apple capability and click Configure. Select the App ID you created earlier as the Primary App ID. Under Domains and Subdomains, list all root domains you will use for Sign In with Apple (e.g., example.com, anotherdomain.com). Under Return URLs, enter the callback URL in the format https://yourdomain.com/api/auth/callback/apple. Add all necessary return URLs. Click Next, then Done, then Continue, then Save.

Apple Client Secret Key creation

Go to the Keys tab in Apple Developer Portal. Click the + icon to create a new key. Enter a name for the key (e.g., 'Sign In with Apple Key'). Scroll down and select the checkbox for Sign In with Apple. Click Configure next to Sign In with Apple and select the Primary App ID you created. Click Save, then Continue, then Register. Immediately download the .p8 key file (only available for download once). Note the Key ID from the Keys page and your Team ID from your Apple Developer Account settings.

Apple JWT clientSecret requirements and expiration

Apple requires a JSON Web Token (JWT) as the clientSecret instead of a shared secret string. The JWT must be cryptographically signed with your private key and cannot expire more than 15,777,000 seconds (six months) in the future. You must regenerate the client secret before it expires to maintain uninterrupted authentication.

Why Better Auth's oAuthProxy is necessary with Apple sign-in

Apple emits the email claim only on the first authorization; every subsequent sign-in omits it, and Apple provides no user-info endpoint to fetch it later. For handling this limitation, see documentation on Handling Providers Without Email, using mapProfileToUser fallback for email persistence.

Apple sign-in localhost and HTTPS restrictions

Apple Sign In does not support localhost or non-HTTPS URLs. During development, you cannot use http://localhost as a return URL and must use a domain with a valid HTTPS/TLS certificate. This limitation is enforced by Apple's security requirements and cannot be bypassed.

Apple provider configuration with jose

Install the jose package with 'npm install jose'. Generate the client secret JWT dynamically in auth configuration using importPKCS8 and SignJWT from jose. The JWT should use ES256 algorithm, set the issuer to Team ID, subject to client ID, audience to https://appleid.apple.com, and set expiration to 180 days (which is below Apple's six-month limit). Add https://appleid.apple.com to the trustedOrigins array in auth instance configuration.

Atlassian OAuth redirect URI format

The redirect URI for Atlassian OAuth should be configured as https://yourdomain.com/api/auth/callback/atlassian. If you change the base path of the auth routes, you should update the redirect URI accordingly.

Atlassian provider configuration in Better Auth

To configure the Atlassian provider, import it and pass it to the socialProviders option of the auth instance with clientId and clientSecret from your Atlassian app credentials.

Atlassian social sign-in example

To sign in with Atlassian using the client, call authClient.signIn.social({ provider: "atlassian" }). This function returns a promise with the authentication data.

Atlassian default OAuth scopes

The default scopes for Atlassian OAuth are read:jira-user and offline_access. Additional scopes can be configured by referring to the Atlassian OAuth documentation.

Create Atlassian OAuth app

To set up Atlassian OAuth, sign in to your Atlassian account, navigate to the Atlassian Developer Console at https://developer.atlassian.com/console/myapps/, click Create new app, fill out the app details, configure the redirect URI, and note the Client ID and Client Secret.

socialProviders option configuration

The socialProviders option configures social login providers with properties: clientId (OAuth client ID from provider), clientSecret (OAuth client secret), clientKey (client key used by some providers like TikTok instead of clientId; optional), redirectURI (custom redirect URI; optional), scope (additional OAuth scopes; optional), mapProfileToUser (custom function to map provider profile to user; optional), disableSignUp (disable sign up for new users; optional), disableImplicitSignUp (disable implicit sign up; optional), overrideUserInfoOnSignIn (override user info with provider data on sign in; optional), prompt (authorization prompt: select_account, consent, login, none, select_account consent; optional), responseMode (query or form_post; optional), getUserInfo (custom function to get user info; optional), refreshAccessToken (custom function to refresh token; optional), verifyIdToken (custom function to verify ID token receiving (token, nonce?, ctx?); optional), disableIdTokenSignIn (disable sign in with ID token from client; optional), disableDefaultScope (disable provider default scopes; optional), and authorizationEndpoint (custom authorization endpoint URL; optional).

Cognito provider configuration options

The cognito provider in socialProviders accepts: clientId (string, required), clientSecret (string, required), domain (string, required, e.g., your-app.auth.us-east-1.amazoncognito.com), region (string, required, e.g., us-east-1), userPoolId (string, required).

Cognito signIn.social additional options

The signIn.social function for Cognito accepts: scope (string, additional OAuth2 scopes to request combined with default permissions; default is "openid" "profile" "email"), getUserInfo (custom function to retrieve user information from the Cognito UserInfo endpoint), refreshAccessToken (custom function to refresh tokens receiving the stored refresh token).

Cognito available OAuth scopes

Common Cognito OAuth scopes are: openid (required for OpenID Connect authentication), profile (access to basic profile info), email (access to user's email), phone (access to user's phone number), aws.cognito.signin.user.admin (grants access to Cognito-specific APIs). Scopes must be configured in your Cognito App Client settings.

Cognito refresh token behavior

Cognito returns a refresh token after a successful authorization code grant. Later refresh-token grants return new access and ID tokens. Cognito only returns a new refresh token when refresh token rotation is enabled in the app client; otherwise, the original refresh token remains valid and Better Auth keeps using it.

Better Auth access token refresh for Cognito

auth.api.getAccessToken refreshes an expired access token automatically when the provider account has a refresh token and a known accessTokenExpiresAt. It returns the valid access token and ID token. If you need the refresh token in the response, use the /refresh-token endpoint instead.

Discord OAuth credentials required

To use Discord sign in, you need a client ID and client secret obtained from the Discord Developer Portal at https://discord.com/developers/applications.

Discord redirect URL configuration

The redirect URL must be set to http://localhost:3000/api/auth/callback/discord for local development. For production, set it to your application's URL. If you change the base path of the auth routes, you must update the redirect URL accordingly.

Discord provider configuration in Better Auth

Import betterAuth and pass Discord configuration to the socialProviders option with clientId and clientSecret properties. The provider key must be 'discord'. Both clientId and clientSecret are required string values.

Discord email scope behavior

Discord returns email: null for phone-only accounts, even when the email scope is granted. Use mapProfileToUser fallback to handle this case, as documented in the Handling Providers Without Email section.

Discord bot permissions option

The permissions option for Discord OAuth allows specifying bot permissions as either a bitwise value (e.g., 2048 | 16384 for Send Messages and Embed Links) or a specific permission value (e.g., 16384 for Embed Links). This parameter only works when the bot scope is included in your OAuth2 scopes. Read Discord bot permissions documentation at https://discord.com/developers/docs/topics/permissions.

Discord OAuth configuration example with bot permissions

import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { discord: { clientId: process.env.DISCORD_CLIENT_ID as string, clientSecret: process.env.DISCORD_CLIENT_SECRET as string, permissions: 2048 | 16384, }, }, })

Dropbox credentials setup

To use Dropbox sign in, obtain a client ID and client secret from the Dropbox Developer Portal at https://www.dropbox.com/developers. You can allow "Implicit Grant & PKCE" for the application in the App Console.

Dropbox redirect URL for local development

For local development, set the Dropbox redirect URL to http://localhost:3000/api/auth/callback/dropbox. For production, set it to the URL of your application. If you change the base path of the auth routes, update the redirect URL accordingly.

Dropbox provider configuration in Better Auth

To configure Dropbox, import betterAuth and pass a socialProviders object with dropbox key containing clientId and clientSecret properties. Example: socialProviders: { dropbox: { clientId: process.env.DROPBOX_CLIENT_ID, clientSecret: process.env.DROPBOX_CLIENT_SECRET } }

Facebook sign in with ID Token code example

```ts const data = await authClient.signIn.social({ provider: "facebook", idToken: { ...(platform === 'ios' ? { token: idToken } : { token: accessToken, accessToken: accessToken }), }, }) ``` This example shows how to sign in with Facebook using an ID Token on iOS or with both token and accessToken on other platforms.

Facebook OAuth credentials location

Facebook App ID and App Secret are obtained from the Facebook Developer Portal. Navigate to the app, then App Settings > Basic to locate the App ID (which is the clientId) and App Secret (which is the clientSecret).

Facebook redirect URL for local development

For local development, set the Facebook redirect URL to http://localhost:3000/api/auth/callback/facebook. For production, set it to the URL of your application. If you change the base path of the auth routes, update the redirect URL accordingly.

Facebook clientSecret security warning

Avoid exposing the clientSecret in client-side code (e.g., frontend apps) because it is sensitive information.

Facebook provider configuration in Better Auth

Configure the Facebook provider by importing betterAuth and passing it to the socialProviders option with clientId and clientSecret from environment variables.

Facebook Login for Business with configId

Better Auth supports Facebook Login for Business. You need to provide the configId (from Facebook Login For Business > Configurations) alongside clientId and clientSecret. The app must be a Business app, and the configuration must be of the 'User access token' type. 'System-user access token' is not supported.

Facebook may omit email field

Facebook may omit the email field even when the permission is granted, such as for phone-only accounts, revoked consent, or addresses Meta has marked invalid. Use mapProfileToUser as a fallback for handling providers without email.

Facebook scopes configuration

The scopes option specifies access permissions and overwrites the default permissions. Default scopes are 'email' and 'public_profile'. Scopes can be customized in the auth configuration.

Facebook fields configuration

The fields option extends the list of fields to retrieve from the Facebook user profile. Default fields are 'id', 'name', 'email', and 'picture'. Fields can be extended in the auth configuration.

Facebook sign in with ID Token or access token

You can sign in with Facebook using an ID Token by passing it to authClient.signIn.social() with the idToken property. For limited login, pass idToken.token. For only accessToken, pass both idToken.token and idToken.accessToken together. When an ID token is provided, no redirection happens and the user is signed in directly.

Facebook provider configuration example with scopes and fields

```ts export const auth = betterAuth({ socialProviders: { facebook: { clientId: process.env.FACEBOOK_CLIENT_ID as string, clientSecret: process.env.FACEBOOK_CLIENT_SECRET as string, scopes: ["email", "public_profile", "user_friends"], fields: ["user_friends"], }, }, }) ``` This example shows how to configure Facebook provider with custom scopes and extended fields.

Give your agent this brain