mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-25 10:13:54 -05:00
docs: imprvoe account linking docs
This commit is contained in:
@@ -122,28 +122,37 @@ The account table stores the authentication data of the user. The account table
|
||||
|
||||
### Account linking
|
||||
|
||||
Account linking allows users to link multiple authentication methods to the same account. This is useful when users want to sign in using different methods.
|
||||
Account linking allows users to connect multiple authentication methods to a single account. Better Auth supports linking social sign-on or OAuth providers to an existing account if the provider returns a verified email. If the email isn't verified, the account can't be linked.
|
||||
|
||||
By default Better Auth allows account linking for all providers. You can disable account linking by setting `accountLinking.enabled` to `false`.
|
||||
```ts
|
||||
const auth = new BetterAuth({
|
||||
account: {
|
||||
accountLinking: {
|
||||
accountLinking: {
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can also specify, the list of providers that should be trusted. When a user signs in using a trusted provider, if the provider returns a verfied email, the user is automatically signed in.
|
||||
You can also specify a list of trusted providers. If a user signs in using a trusted provider, the account is linked, even if the email verification status is not provided.
|
||||
|
||||
If account linking is disabled, accounts cannot be linked, regardless of the provider or email verification.
|
||||
|
||||
```ts
|
||||
const auth = new BetterAuth({
|
||||
account: {
|
||||
accountLinking: {
|
||||
accountLinking: {
|
||||
enabled: true,
|
||||
trustedProviders: ["email-password", "google"],
|
||||
}
|
||||
trustedProviders: ["google", "github"],
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To link a credential-based account (email and password), users can either go through the "forgot password" flow or use the `setPassword` function if they are already logged in.
|
||||
|
||||
```ts
|
||||
await authClient.user.setPassword({
|
||||
password,
|
||||
});
|
||||
```
|
||||
@@ -1,4 +1,3 @@
|
||||
import { APIError } from "better-call";
|
||||
import { z } from "zod";
|
||||
import { userSchema } from "../../db/schema";
|
||||
import { generateId } from "../../utils/id";
|
||||
@@ -115,7 +114,15 @@ export const callbackOAuth = createAuthEndpoint(
|
||||
`${c.context.baseURL}/error?error=please_restart_the_process`,
|
||||
);
|
||||
}
|
||||
//find user in db
|
||||
|
||||
function redirectOnError(error: string) {
|
||||
throw c.redirect(
|
||||
`${
|
||||
currentURL || callbackURL || `${c.context.baseURL}/error`
|
||||
}?error=${error}`,
|
||||
);
|
||||
}
|
||||
|
||||
const dbUser = await c.context.internalAdapter
|
||||
.findUserByEmail(user.email, {
|
||||
includeAccounts: true,
|
||||
@@ -141,17 +148,11 @@ export const callbackOAuth = createAuthEndpoint(
|
||||
const isTrustedProvider = trustedProviders?.includes(
|
||||
provider.id as "apple",
|
||||
);
|
||||
if (!isTrustedProvider) {
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(currentURL || callbackURL);
|
||||
url.searchParams.set("error", "account_not_linked");
|
||||
} catch (e) {
|
||||
throw c.redirect(
|
||||
`${c.context.baseURL}/error?error=account_not_linked`,
|
||||
);
|
||||
}
|
||||
throw c.redirect(url.toString());
|
||||
if (
|
||||
(!isTrustedProvider && !user.emailVerified) ||
|
||||
!c.context.options.account?.accountLinking?.enabled
|
||||
) {
|
||||
redirectOnError("account_not_linked");
|
||||
}
|
||||
try {
|
||||
await c.context.internalAdapter.linkAccount({
|
||||
@@ -162,10 +163,8 @@ export const callbackOAuth = createAuthEndpoint(
|
||||
...getAccountTokens(tokens),
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw c.redirect(
|
||||
`${c.context.baseURL}/error?error=failed_linking_account`,
|
||||
);
|
||||
logger.error("Unable to link account", e);
|
||||
redirectOnError("unable_to_link_account");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -199,40 +198,23 @@ export const callbackOAuth = createAuthEndpoint(
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
const url = new URL(currentURL || callbackURL);
|
||||
url.searchParams.set("error", "unable_to_create_user");
|
||||
throw c.redirect(url.toString());
|
||||
logger.error("Unable to create user", e);
|
||||
redirectOnError("unable_to_create_user");
|
||||
}
|
||||
}
|
||||
//this should never happen
|
||||
if (!userId && !id)
|
||||
throw new APIError("INTERNAL_SERVER_ERROR", {
|
||||
message: "Unable to create user",
|
||||
});
|
||||
//create session
|
||||
try {
|
||||
const session = await c.context.internalAdapter.createSession(
|
||||
userId || id,
|
||||
c.request,
|
||||
);
|
||||
if (!session) {
|
||||
const url = new URL(currentURL || callbackURL);
|
||||
url.searchParams.set("error", "unable_to_create_session");
|
||||
throw c.redirect(url.toString());
|
||||
}
|
||||
try {
|
||||
await setSessionCookie(c, session.id);
|
||||
} catch (e) {
|
||||
c.context.logger.error("Unable to set session cookie", e);
|
||||
const url = new URL(currentURL || callbackURL);
|
||||
url.searchParams.set("error", "unable_to_create_session");
|
||||
throw c.redirect(url.toString());
|
||||
}
|
||||
} catch {
|
||||
const url = new URL(currentURL || callbackURL || "");
|
||||
url.searchParams.set("error", "unable_to_create_session");
|
||||
throw c.redirect(url.toString());
|
||||
|
||||
if (!userId && !id) {
|
||||
redirectOnError("unable_to_create_user");
|
||||
}
|
||||
|
||||
const session = await c.context.internalAdapter.createSession(
|
||||
userId || id,
|
||||
c.request,
|
||||
);
|
||||
if (!session) {
|
||||
redirectOnError("unable_to_create_session");
|
||||
}
|
||||
await setSessionCookie(c, session.id);
|
||||
throw c.redirect(callbackURL);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getTestInstance } from "../../test-utils/test-instance";
|
||||
import { createAuthClient } from "../../client";
|
||||
import { parseSetCookieHeader } from "../../cookies";
|
||||
|
||||
describe("updateUser", async () => {
|
||||
let emailVerificationToken: string;
|
||||
|
||||
Reference in New Issue
Block a user