Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/docs/configuration/providers/oauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ interface OAuthConfig {
region?: string
issuer?: string
client?: Partial<ClientMetadata>
allowDangerousEmailAccountLinking?: boolean
}
```

Expand Down Expand Up @@ -278,6 +279,10 @@ If your Provider is OpenID Connect (OIDC) compliant, we recommend using the `wel

An advanced option, hopefully you won't need it in most cases. `next-auth` uses `openid-client` under the hood, see the docs on this option [here](https://github.com/panva/node-openid-client/blob/main/docs/README.md#new-clientmetadata-jwks-options).

### `allowDangerousEmailAccountLinking` option

Normally, when you sign in with an OAuth provider and another account with the same email address already exists, the accounts are not linked automatically. Automatic account linking on sign in is not secure between arbitrary providers and is disabled by default (see our [Security FAQ](https://next-auth.js.org/faq#security)). However, it may be desirable to allow automatic account linking if you trust that the provider involved has securely verified the email address associated with the account. Just set `allowDangerousEmailAccountLinking: true` in your provider configuration to enable automatic account linking.

## Using a custom provider

You can use an OAuth provider that isn't built-in by using a custom object.
Expand Down Expand Up @@ -404,6 +409,18 @@ GoogleProvider({
})
```

An example of how to enable automatic account linking:

```js title=/api/auth/[...nextauth].js
import GoogleProvider from "next-auth/providers/google"

GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
allowDangerousEmailAccountLinking: true,
})
```

### Adding a new built-in provider

If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list so others can discover it much more easily!
Expand Down
43 changes: 26 additions & 17 deletions packages/next-auth/src/core/lib/callback-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { AdapterSession, AdapterUser } from "../../adapters"
import type { JWT } from "../../jwt"
import type { Account, User } from "../.."
import type { SessionToken } from "./cookie"
import { OAuthConfig } from "src/providers"

/**
* This function handles the complex flow of signing users in, and either creating,
Expand Down Expand Up @@ -180,25 +181,33 @@ export default async function callbackHandler(params: {
? await getUserByEmail(profile.email)
: null
if (userByEmail) {
// We end up here when we don't have an account with the same [provider].id *BUT*
// we do already have an account with the same email address as the one in the
// OAuth profile the user has just tried to sign in with.
const provider = options.provider as OAuthConfig<any>;
if (provider?.allowDangerousEmailAccountLinking) {
// If you trust the oauth provider to correctly verify email addresses, you can opt-in to
// account linking even when the user is not signed-in.
user = userByEmail;
} else {
// We end up here when we don't have an account with the same [provider].id *BUT*
// we do already have an account with the same email address as the one in the
// OAuth profile the user has just tried to sign in with.
//
// We don't want to have two accounts with the same email address, and we don't
// want to link them in case it's not safe to do so, so instead we prompt the user
// to sign in via email to verify their identity and then link the accounts.
throw new AccountNotLinkedError(
"Another account already exists with the same e-mail address"
)
}
} else {
// If the current user is not logged in and the profile isn't linked to any user
// accounts (by email or provider account id)...
//
// We don't want to have two accounts with the same email address, and we don't
// want to link them in case it's not safe to do so, so instead we prompt the user
// to sign in via email to verify their identity and then link the accounts.
throw new AccountNotLinkedError(
"Another account already exists with the same e-mail address"
)
// If no account matching the same [provider].id or .email exists, we can
// create a new account for the user, link it to the OAuth acccount and
// create a new session for them so they are signed in with it.
const { id: _, ...newUser } = { ...profile, emailVerified: null }
user = await createUser(newUser)
}
// If the current user is not logged in and the profile isn't linked to any user
// accounts (by email or provider account id)...
//
// If no account matching the same [provider].id or .email exists, we can
// create a new account for the user, link it to the OAuth acccount and
// create a new session for them so they are signed in with it.
const { id: _, ...newUser } = { ...profile, emailVerified: null }
user = await createUser(newUser)
await events.createUser?.({ user })

await linkAccount({ ...account, userId: user.id })
Expand Down
1 change: 1 addition & 0 deletions packages/next-auth/src/providers/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
requestTokenUrl?: string
profileUrl?: string
encoding?: string
allowDangerousEmailAccountLinking?: boolean;
}

/** @internal */
Expand Down
2 changes: 1 addition & 1 deletion packages/next-auth/src/utils/detect-host.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** Extract the host from the environment */
export function detectHost(forwardedHost: any) {
// If we detect a Vercel environment, we can trust the host
if (process.env.VERCEL || process.env.AUTH_TRUST_HOST)
if (process.env.VERCEL ?? process.env.AUTH_TRUST_HOST)
return forwardedHost
// If `NEXTAUTH_URL` is `undefined` we fall back to "http://localhost:3000"
return process.env.NEXTAUTH_URL
Expand Down