Skip to content

Commit

Permalink
Feature/nonce check type (#4100)
Browse files Browse the repository at this point in the history
* feat: add nonce check type

* Update types import for nonce-handler.ts

* Update packages/next-auth/src/core/lib/oauth/callback.ts

Co-authored-by: Thang Vu <thvu@hey.com>

* Add further info to debug msg as per PR suggestion

* Cast OauthChecks as OpenIDCallbackChecks

* Update order of imports as per PR suggestion

Co-authored-by: Hamid Adelyar <hamid.adelyar@bjss.com>
Co-authored-by: hamidbjss <98807568+hamidbjss@users.noreply.github.com>
Co-authored-by: Thang Vu <thvu@hey.com>
  • Loading branch information
4 people authored Aug 16, 2022
1 parent 32f4d50 commit d349ae2
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 3 deletions.
9 changes: 9 additions & 0 deletions docs/docs/configuration/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,15 @@ cookies: {
secure: useSecureCookies,
},
},
nonce: {
name: `${cookiePrefix}next-auth.nonce`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
},
}
```

Expand Down
9 changes: 9 additions & 0 deletions packages/next-auth/src/core/lib/cookie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ export function defaultCookies(useSecureCookies: boolean): CookiesOptions {
secure: useSecureCookies,
},
},
nonce: {
name: `${cookiePrefix}next-auth.nonce`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions packages/next-auth/src/core/lib/oauth/authorization-url.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { openidClient } from "./client"
import { oAuth1Client } from "./client-legacy"
import { createState } from "./state-handler"
import { createNonce } from "./nonce-handler"
import { createPKCE } from "./pkce-handler"

import type { AuthorizationParameters } from "openid-client"
Expand Down Expand Up @@ -62,6 +63,12 @@ export default async function getAuthorizationUrl({
cookies.push(state.cookie)
}

const nonce = await createNonce(options)
if (nonce) {
authorizationParams.nonce = nonce.value
cookies.push(nonce.cookie)
}

const pkce = await createPKCE(options)
if (pkce) {
authorizationParams.code_challenge = pkce.code_challenge
Expand Down
11 changes: 9 additions & 2 deletions packages/next-auth/src/core/lib/oauth/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { openidClient } from "./client"
import { oAuth1Client } from "./client-legacy"
import { useState } from "./state-handler"
import { usePKCECodeVerifier } from "./pkce-handler"
import { useNonce } from "./nonce-handler"
import { OAuthCallbackError } from "../../errors"

import type { CallbackParamsType } from "openid-client"
import type { CallbackParamsType, OpenIDCallbackChecks } from "openid-client"
import type { Account, LoggerInstance, Profile } from "../../.."
import type { OAuthChecks, OAuthConfig } from "../../../providers"
import type { InternalOptions } from "../../types"
Expand Down Expand Up @@ -33,6 +34,7 @@ export default async function oAuthCallback(params: {
logger.debug("OAUTH_CALLBACK_HANDLER_ERROR", { body })
throw error
}


if (provider.version?.startsWith("1.")) {
try {
Expand Down Expand Up @@ -73,12 +75,17 @@ export default async function oAuthCallback(params: {
const resCookies: Cookie[] = []

const state = await useState(cookies?.[options.cookies.state.name], options)

if (state) {
checks.state = state.value
resCookies.push(state.cookie)
}

const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options)
if (nonce && provider.idToken) {
(checks as OpenIDCallbackChecks).nonce = nonce.value
resCookies.push(nonce.cookie)
}

const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name]
const pkce = await usePKCECodeVerifier(codeVerifier, options)
if (pkce) {
Expand Down
75 changes: 75 additions & 0 deletions packages/next-auth/src/core/lib/oauth/nonce-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as jwt from "../../../jwt"
import { generators } from "openid-client"
import type { InternalOptions } from "../../types"
import type { Cookie } from "../cookie"

const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds

/**
* Returns nonce if the provider supports it
* and saves it in a cookie */
export async function createNonce(options: InternalOptions<"oauth">): Promise<
| undefined
| {
value: string
cookie: Cookie
}
> {
const { cookies, logger, provider } = options
if (!provider.checks?.includes("nonce")) {
// Provider does not support nonce, return nothing.
return
}

const nonce = generators.nonce()

const expires = new Date()
expires.setTime(expires.getTime() + NONCE_MAX_AGE * 1000)

// Encrypt nonce and save it to an encrypted cookie
const encryptedNonce = await jwt.encode({
...options.jwt,
maxAge: NONCE_MAX_AGE,
token: { nonce },
})

logger.debug("CREATE_ENCRYPTED_NONCE", {
nonce,
maxAge: NONCE_MAX_AGE,
})

return {
cookie: {
name: cookies.nonce.name,
value: encryptedNonce,
options: { ...cookies.nonce.options, expires },
},
value: nonce,
}
}

/**
* Returns nonce from if the provider supports nonce,
* and clears the container cookie afterwards.
*/
export async function useNonce(
nonce: string | undefined,
options: InternalOptions<"oauth">
): Promise<{ value: string; cookie: Cookie } | undefined> {
const { cookies, provider } = options

if (!provider?.checks?.includes("nonce") || !nonce) {
return
}

const value = (await jwt.decode({...options.jwt, token: nonce })) as any

return {
value: value?.nonce ?? undefined,
cookie: {
name: cookies.nonce.name,
value: "",
options: { ...cookies.nonce.options, maxAge: 0 },
},
}
}
1 change: 1 addition & 0 deletions packages/next-auth/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ export interface CookiesOptions {
csrfToken: CookieOption
pkceCodeVerifier: CookieOption
state: CookieOption
nonce: CookieOption
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/next-auth/src/providers/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type Client = InstanceType<Issuer["Client"]>

export type { OAuthProviderType } from "./oauth-types"

type ChecksType = "pkce" | "state" | "none"
type ChecksType = "pkce" | "state" | "none" | "nonce"

export type OAuthChecks = OpenIDCallbackChecks | OAuthCallbackChecks

Expand Down

1 comment on commit d349ae2

@vercel
Copy link

@vercel vercel bot commented on d349ae2 Aug 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.