Skip to content
Open
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
4 changes: 2 additions & 2 deletions docs/pages/getting-started/authentication/webauthn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ Support for more frameworks and adapters are coming soon.
### Install peer dependencies

```bash npm2yarn
npm install @simplewebauthn/server@9.0.3 @simplewebauthn/browser@9.0.1
npm install @simplewebauthn/server@^13.2.2 @simplewebauthn/browser@^13.2.2
```

The `@simplewebauthn/browser` peer dependency **is only required for custom signin pages**. If you're using the Auth.js default pages, you can skip installing that peer dependency.
Auth.js uses [SimpleWebAuthn v13](https://simplewebauthn.dev/docs/packages/server). The `@simplewebauthn/browser` peer dependency **is only required for custom sign-in pages**. If you're using the Auth.js default pages, you can skip installing the browser package. As of v13, types come from the browser and server packages (no separate `@simplewebauthn/types`).

### Apply the required schema Migrations

Expand Down
6 changes: 3 additions & 3 deletions docs/pages/getting-started/providers/passkey.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ Passkeys are currently supported in the following adapters / framework packages.
### Install peer dependencies

```bash npm2yarn
npm install @simplewebauthn/browser@9.0.1 @simplewebauthn/server@9.0.3
npm install @simplewebauthn/browser@^13.2.2 @simplewebauthn/server@^13.2.2
```

The `@simplewebauthn/browser` peer dependency is only required for custom signin pages. If you're using the Auth.js default pages, you can skip installing that peer dependency.
Auth.js uses [SimpleWebAuthn v13](https://simplewebauthn.dev/docs/packages/server). Both packages are optional peer dependencies: the server package is used for generating and verifying options; the browser package is only required for custom sign-in pages (you can skip it if using the default Auth.js pages). As of v13, types are exported from the browser and server packages (no separate `@simplewebauthn/types`).

### Database Setup

Expand Down Expand Up @@ -184,7 +184,7 @@ If you're using the built-in Auth.js pages, then you are good to go now! Navigat

### Custom Pages

If you're building a custom signin page, you can leverage the `next-auth/webauthn` `signIn` function to initiate both WebAuthn registration and authentication. Remember, when using the WebAuthn `signIn` function, you'll also need the `@simplewebauth/browser` peer dependency installed.
If you're building a custom sign-in page, you can leverage the `next-auth/webauthn` `signIn` function to initiate both WebAuthn registration and authentication. Remember, when using the WebAuthn `signIn` function, you'll also need the `@simplewebauthn/browser` peer dependency installed.

```ts filename="app/login/page.tsx" {4} /webauthn/
"use client"
Expand Down
9 changes: 4 additions & 5 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@
"preact-render-to-string": "6.5.11"
},
"peerDependencies": {
"@simplewebauthn/browser": "^9.0.1",
"@simplewebauthn/server": "^9.0.2",
"@simplewebauthn/browser": "^13.2.2",
"@simplewebauthn/server": "^13.2.2",
"nodemailer": "^7.0.7"
},
"peerDependenciesMeta": {
Expand All @@ -99,9 +99,8 @@
"providers": "node scripts/generate-providers"
},
"devDependencies": {
"@simplewebauthn/browser": "9.0.1",
"@simplewebauthn/server": "9.0.3",
"@simplewebauthn/types": "^9.0.1",
"@simplewebauthn/browser": "^13.2.2",
"@simplewebauthn/server": "^13.2.2",
"@types/node": "18.11.10",
"@types/nodemailer": "6.4.6",
"@types/react": "18.0.37",
Expand Down
16 changes: 9 additions & 7 deletions packages/core/src/lib/utils/webauthn-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
/**
* @template {WebAuthnOptionsAction} T
* @typedef {T extends WebAuthnAuthenticate ?
* { options: import("@simplewebauthn/types").PublicKeyCredentialRequestOptionsJSON; action: "authenticate" } :
* { options: import("@simplewebauthn/server").PublicKeyCredentialRequestOptionsJSON; action: "authenticate" } :
* T extends WebAuthnRegister ?
* { options: import("@simplewebauthn/types").PublicKeyCredentialCreationOptionsJSON; action: "register" } :
* { options: import("@simplewebauthn/server").PublicKeyCredentialCreationOptionsJSON; action: "register" } :
* never
* } WebAuthnOptionsReturn
*/
Expand Down Expand Up @@ -125,10 +125,10 @@ export async function webauthnScript(authURL, providerID) {
*/
async function authenticationFlow(options, autofill) {
// Start authentication
const authResp = await WebAuthnBrowser.startAuthentication(
options,
autofill
)
const authResp = await WebAuthnBrowser.startAuthentication({
optionsJSON: options,
useBrowserAutofill: autofill ?? false,
})

// Submit authentication response to server
return await submitForm("authenticate", authResp)
Expand All @@ -147,7 +147,9 @@ export async function webauthnScript(authURL, providerID) {
})

// Start registration
const regResp = await WebAuthnBrowser.startRegistration(options)
const regResp = await WebAuthnBrowser.startRegistration({
optionsJSON: options,
})

// Submit registration response to server
return await submitForm("register", regResp)
Expand Down
51 changes: 26 additions & 25 deletions packages/core/src/lib/utils/webauthn-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import {
WebAuthnVerificationError,
} from "../../errors.js"
import { webauthnChallenge } from "../actions/callback/oauth/checks.js"
import {
type AuthenticationResponseJSON,
type PublicKeyCredentialCreationOptionsJSON,
type PublicKeyCredentialRequestOptionsJSON,
type RegistrationResponseJSON,
} from "@simplewebauthn/types"
import type {
AuthenticationResponseJSON,
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON,
RegistrationResponseJSON,
} from "@simplewebauthn/server"
import type {
Adapter,
AdapterAccount,
Expand All @@ -33,6 +33,7 @@ import { randomString } from "./web.js"
import type {
VerifiedAuthenticationResponse,
VerifiedRegistrationResponse,
WebAuthnCredential,
} from "@simplewebauthn/server"

export type WebAuthnRegister = "register"
Expand Down Expand Up @@ -249,7 +250,7 @@ export async function verifyAuthenticate(
...provider.verifyAuthenticationOptions,
expectedChallenge,
response: data as AuthenticationResponseJSON,
authenticator: fromAdapterAuthenticator(authenticator),
credential: adapterAuthenticatorToWebAuthnCredential(authenticator),
expectedOrigin: relayingParty.origin,
expectedRPID: relayingParty.id,
})
Expand Down Expand Up @@ -369,23 +370,24 @@ export async function verifyRegister(
)
}

const { credential, credentialDeviceType, credentialBackedUp } =
verification.registrationInfo

// Build a new account
const account = {
providerAccountId: toBase64(verification.registrationInfo.credentialID),
providerAccountId: credential.id,
provider: options.provider.id,
type: provider.type,
}

// Build a new authenticator
const authenticator = {
providerAccountId: account.providerAccountId,
counter: verification.registrationInfo.counter,
credentialID: toBase64(verification.registrationInfo.credentialID),
credentialPublicKey: toBase64(
verification.registrationInfo.credentialPublicKey
),
credentialBackedUp: verification.registrationInfo.credentialBackedUp,
credentialDeviceType: verification.registrationInfo.credentialDeviceType,
counter: credential.counter,
credentialID: credential.id,
credentialPublicKey: toBase64(credential.publicKey),
credentialBackedUp,
credentialDeviceType,
transports: transportsToString(
(data as RegistrationResponseJSON).response
.transports as AuthenticatorTransport[]
Expand Down Expand Up @@ -428,7 +430,7 @@ async function getAuthenticationOptions(
...provider.authenticationOptions,
rpID: relayingParty.id,
allowCredentials: authenticators?.map((a) => ({
id: fromBase64(a.credentialID),
id: a.credentialID,
type: "public-key",
transports: stringToTransports(a.transports),
})),
Expand Down Expand Up @@ -459,7 +461,7 @@ async function getRegistrationOptions(
// We can do this because we don't use this user ID to link the
// credential to the user. Instead, we store actual userID in the
// Authenticator object and fetch it via it's credential ID.
const userID = randomString(32)
const userID = new TextEncoder().encode(randomString(32))

const relayingParty = provider.getRelayingParty(options, request)

Expand All @@ -472,7 +474,7 @@ async function getRegistrationOptions(
rpID: relayingParty.id,
rpName: relayingParty.name,
excludeCredentials: authenticators?.map((a) => ({
id: fromBase64(a.credentialID),
id: a.credentialID,
type: "public-key",
transports: stringToTransports(a.transports),
})),
Expand All @@ -495,16 +497,15 @@ export function assertInternalOptionsWebAuthn(
return { ...options, provider, adapter }
}

function fromAdapterAuthenticator(
function adapterAuthenticatorToWebAuthnCredential(
authenticator: AdapterAuthenticator
): InternalAuthenticator {
): WebAuthnCredential {
const publicKeyBytes = fromBase64(authenticator.credentialPublicKey)
return {
...authenticator,
credentialDeviceType:
authenticator.credentialDeviceType as InternalAuthenticator["credentialDeviceType"],
id: authenticator.credentialID,
publicKey: new Uint8Array(publicKeyBytes),
counter: authenticator.counter,
transports: stringToTransports(authenticator.transports),
credentialID: fromBase64(authenticator.credentialID),
credentialPublicKey: fromBase64(authenticator.credentialPublicKey),
}
}

Expand Down
19 changes: 15 additions & 4 deletions packages/core/src/providers/passkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ import WebAuthn, {
*
* ### Setup
*
* Install the required peer dependency.
* Install the required peer dependencies (SimpleWebAuthn v13).
*
* ```bash npm2yarn
* npm install @simplewebauthn/browser@9.0.1
* npm install @simplewebauthn/browser@^13.2.2 @simplewebauthn/server@^13.2.2
* ```
*
* Both packages are optional peer dependencies; the server package is used for
* generating and verifying options, the browser package for the sign-in page script.
* As of v13, types are exported from the browser and server packages (no separate
* `@simplewebauthn/types` package).
*
* #### Configuration
* ```ts
* import { Auth } from "@auth/core"
Expand All @@ -46,8 +51,14 @@ import WebAuthn, {
* ### Notes
*
* This provider is an extension of the WebAuthn provider that defines some default values
* associated with Passkey support. You may override these, but be aware that authenticators
* may not recognize your credentials as Passkey credentials if you do.
* associated with Passkey support (e.g. resident key, user verification). You may override
* these, but be aware that authenticators may not recognize your credentials as Passkey
* credentials if you do.
*
* **SimpleWebAuthn v13:** Auth.js uses the v13 API: credential IDs in options are base64
* strings; `verifyAuthenticationResponse` expects a `credential` (id, publicKey, counter);
* `verifyRegistrationResponse` returns `registrationInfo.credential` with the same shape;
* and the browser helpers use a single options object (`optionsJSON`, `useBrowserAutofill`).
*
* :::tip
*
Expand Down
22 changes: 19 additions & 3 deletions packages/core/src/providers/webauthn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type {
export type WebAuthnProviderType = "webauthn"

export const DEFAULT_WEBAUTHN_TIMEOUT = 5 * 60 * 1000 // 5 minutes
export const DEFAULT_SIMPLEWEBAUTHN_BROWSER_VERSION: SemverString = "v9.0.1"
export const DEFAULT_SIMPLEWEBAUTHN_BROWSER_VERSION: SemverString = "v13.2.2"

export type RelayingParty = {
/** Relaying Party ID. Use the website's domain name. */
Expand Down Expand Up @@ -72,7 +72,7 @@ type ConfigurableVerifyAuthenticationOptions = Omit<
| "expectedChallenge"
| "expectedOrigin"
| "expectedRPID"
| "authenticator"
| "credential"
| "response"
>
type ConfigurableVerifyRegistrationOptions = Omit<
Expand Down Expand Up @@ -105,7 +105,7 @@ export interface WebAuthnConfig extends CommonProviderOptions {
* Version of SimpleWebAuthn browser script to load in the sign in page.
*
* This is only loaded if the provider has conditional UI enabled. If set to false, it won't load any script.
* Defaults to `v9.0.0`.
* Defaults to `v13.2.2`.
*/
simpleWebAuthnBrowserVersion: SemverString | false
/** Form fields displayed in the default Passkey sign in/up form.
Expand Down Expand Up @@ -162,6 +162,16 @@ export interface WebAuthnConfig extends CommonProviderOptions {
*
* ### Setup
*
* Install the optional peer dependencies (SimpleWebAuthn v13):
*
* ```bash npm2yarn
* npm install @simplewebauthn/browser@^13.2.2 @simplewebauthn/server@^13.2.2
* ```
*
* The browser package is used for the sign-in page script; the server package for
* generating and verifying options. As of v13, types come from those packages
* (no separate `@simplewebauthn/types`).
*
* #### Configuration
* ```ts
* import { Auth } from "@auth/core"
Expand All @@ -178,6 +188,12 @@ export interface WebAuthnConfig extends CommonProviderOptions {
* - [SimpleWebAuthn - Client side](https://simplewebauthn.dev/docs/packages/client)
* - [Source code](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/webauthn.ts)
*
* **SimpleWebAuthn v13:** This provider targets the v13 API: credential IDs in
* `allowCredentials`/`excludeCredentials` are base64 strings; authentication
* verification uses a `credential` object (id, publicKey, counter); registration
* verification returns `registrationInfo.credential`; and the default browser
* script uses the v13 options shape (`optionsJSON`, `useBrowserAutofill`).
*
* :::tip
*
* The WebAuthn provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/webauthn.ts).
Expand Down
Loading