Skip to content

Commit 370208c

Browse files
authored
feat: Add support for Auth.js passkey provider (#45)
This pull request introduces support for passkey (WebAuthn) authentication in the Auth.js integration for Payload CMS. The main changes include updating the user schema to store authenticators and extending the UI with buttons for passkey registration and sign-in.
1 parent 1a5d9f9 commit 370208c

26 files changed

Lines changed: 651 additions & 62 deletions

packages/dev/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
"@payloadcms/next": "3.54.0",
1919
"@payloadcms/translations": "3.54.0",
2020
"@payloadcms/ui": "3.54.0",
21+
"@simplewebauthn/browser": "^9.0.1",
22+
"@simplewebauthn/server": "^9.0.3",
2123
"@tailwindcss/postcss": "^4.1.12",
2224
"clsx": "^2.1.1",
2325
"graphql": "^16.11.0",

packages/dev/src/app/(app)/_components/auth/SignInOrOutButtons.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { getPayloadSession } from "payload-authjs";
2+
import { AuthjsRegisterPasskeyButtonClient } from "./authjs/AuthjsRegisterPasskeyButtonClient";
23
import { AuthjsSignInButtonClient } from "./authjs/AuthjsSignInButtonClient";
34
import { AuthjsSignInButtonServerAction } from "./authjs/AuthjsSignInButtonServerAction";
5+
import { AuthjsSignInPasskeyButtonClient } from "./authjs/AuthjsSignInPasskeyButtonClient";
46
import { AuthjsSignOutButtonClient } from "./authjs/AuthjsSignOutButtonClient";
57
import { AuthjsSignOutButtonServerAction } from "./authjs/AuthjsSignOutButtonServerAction";
68
import { PayloadSignInButtonClient } from "./payload/PayloadSignInButtonClient";
@@ -18,11 +20,13 @@ export const SignInOrOutButtons = async () => {
1820
<PayloadSignOutButtonClient />
1921
<AuthjsSignOutButtonServerAction />
2022
<AuthjsSignOutButtonClient />
23+
<AuthjsRegisterPasskeyButtonClient />
2124
</>
2225
) : (
2326
<>
2427
<AuthjsSignInButtonServerAction />
2528
<AuthjsSignInButtonClient />
29+
<AuthjsSignInPasskeyButtonClient />
2630
<PayloadSignInButtonClient />
2731
</>
2832
)}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use client";
2+
3+
import { Button } from "@/components/general/Button";
4+
import { AuthjsLogo } from "@/components/img/AuthjsLogo";
5+
import { signIn } from "next-auth/webauthn";
6+
7+
/**
8+
* Register a new passkey using Auth.js on the client
9+
*
10+
* @see https://authjs.dev/getting-started/authentication/webauthn
11+
*/
12+
export function AuthjsRegisterPasskeyButtonClient() {
13+
return (
14+
<Button onClick={() => signIn("passkey", { action: "register" })}>
15+
<AuthjsLogo />
16+
Register a Passkey (client)
17+
</Button>
18+
);
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use client";
2+
3+
import { Button } from "@/components/general/Button";
4+
import { AuthjsLogo } from "@/components/img/AuthjsLogo";
5+
import { signIn } from "next-auth/webauthn";
6+
7+
/**
8+
* Sign in with a passkey using Auth.js on the client
9+
*
10+
* @see https://authjs.dev/getting-started/authentication/webauthn
11+
*/
12+
export function AuthjsSignInPasskeyButtonClient() {
13+
return (
14+
<Button onClick={() => signIn("passkey")}>
15+
<AuthjsLogo />
16+
Sign In with Passkey (client)
17+
</Button>
18+
);
19+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { AccountRowLabel as AccountRowLabel_06d0cb594d8f6ba2ac35015f930c882e } from 'payload-authjs/components'
2+
import { AddAuthenticatorButton as AddAuthenticatorButton_06d0cb594d8f6ba2ac35015f930c882e } from 'payload-authjs/components'
23
import { SignInButton as SignInButton_06d0cb594d8f6ba2ac35015f930c882e } from 'payload-authjs/components'
34
import { default as default_2c1e1c35da30a80d88551f9fcc61be66 } from '../../../payload/components/Greeting'
45

56
export const importMap = {
67
"payload-authjs/components#AccountRowLabel": AccountRowLabel_06d0cb594d8f6ba2ac35015f930c882e,
8+
"payload-authjs/components#AddAuthenticatorButton": AddAuthenticatorButton_06d0cb594d8f6ba2ac35015f930c882e,
79
"payload-authjs/components#SignInButton": SignInButton_06d0cb594d8f6ba2ac35015f930c882e,
810
"/Greeting#default": default_2c1e1c35da30a80d88551f9fcc61be66
911
}

packages/dev/src/auth/base.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,7 @@ export const authConfig: NextAuthConfig = {
189189
return true;
190190
},
191191
},
192+
experimental: {
193+
enableWebAuthn: true,
194+
},
192195
};

packages/dev/src/auth/node.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@ import { discordProvider } from "./providers/discord";
44
import { githubProvider } from "./providers/github";
55
import { keycloakProvider } from "./providers/keycloak";
66
import { nodemailerProvider } from "./providers/nodemailer";
7+
import { passkeysProvider } from "./providers/passkeys";
78

89
export const nodeAuthConfig: NextAuthConfig = {
910
...authConfig,
10-
providers: [githubProvider, keycloakProvider, discordProvider, nodemailerProvider],
11+
providers: [
12+
githubProvider,
13+
keycloakProvider,
14+
discordProvider,
15+
nodemailerProvider,
16+
passkeysProvider,
17+
],
1118
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import passkey from "next-auth/providers/passkey";
2+
3+
export const passkeysProvider = passkey({});

packages/dev/src/payload-types.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,17 @@ export interface User {
164164
id?: string | null;
165165
}[]
166166
| null;
167+
authenticators?:
168+
| {
169+
credentialID: string;
170+
credentialPublicKey: string;
171+
credentialBackedUp: boolean;
172+
counter: number;
173+
transports?: string | null;
174+
credentialDeviceType: string;
175+
id?: string | null;
176+
}[]
177+
| null;
167178
updatedAt: string;
168179
createdAt: string;
169180
enableAPIKey?: boolean | null;
@@ -288,6 +299,17 @@ export interface UsersSelect<T extends boolean = true> {
288299
createdAt?: T;
289300
id?: T;
290301
};
302+
authenticators?:
303+
| T
304+
| {
305+
credentialID?: T;
306+
credentialPublicKey?: T;
307+
credentialBackedUp?: T;
308+
counter?: T;
309+
transports?: T;
310+
credentialDeviceType?: T;
311+
id?: T;
312+
};
291313
updatedAt?: T;
292314
createdAt?: T;
293315
enableAPIKey?: T;

packages/dev/src/payload/collections/users.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ const Users: CollectionConfig<"users"> = {
116116
name: "additionalAccountDatabaseField",
117117
type: "text",
118118
required: true,
119+
defaultValue: () => `Default value at ${new Date().toISOString()}`,
119120
},
120121
createdAtField,
121122
],

0 commit comments

Comments
 (0)