mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
2 Commits
next-auth@
...
next-auth@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50b117dfbb | ||
|
|
e6590ffc20 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "next-auth",
|
||||
"version": "4.20.0",
|
||||
"version": "4.20.1",
|
||||
"description": "Authentication for Next.js",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { openidClient } from "./client"
|
||||
import { oAuth1Client, oAuth1TokenStore } from "./client-legacy"
|
||||
import { createState } from "./state-handler"
|
||||
import { createNonce } from "./nonce-handler"
|
||||
import { createPKCE } from "./pkce-handler"
|
||||
import * as checks from "./checks"
|
||||
|
||||
import type { AuthorizationParameters } from "openid-client"
|
||||
import type { InternalOptions } from "../../types"
|
||||
@@ -54,24 +52,9 @@ export default async function getAuthorizationUrl({
|
||||
const authorizationParams: AuthorizationParameters = params
|
||||
const cookies: Cookie[] = []
|
||||
|
||||
const state = await createState(options)
|
||||
if (state) {
|
||||
authorizationParams.state = state.value
|
||||
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
|
||||
authorizationParams.code_challenge_method = pkce.code_challenge_method
|
||||
cookies.push(pkce.cookie)
|
||||
}
|
||||
await checks.state.create(options, cookies, authorizationParams)
|
||||
await checks.pkce.create(options, cookies, authorizationParams)
|
||||
await checks.nonce.create(options, cookies, authorizationParams)
|
||||
|
||||
const url = client.authorizationUrl(authorizationParams)
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { TokenSet } from "openid-client"
|
||||
import { openidClient } from "./client"
|
||||
import { oAuth1Client, oAuth1TokenStore } from "./client-legacy"
|
||||
import { useState } from "./state-handler"
|
||||
import { usePKCECodeVerifier } from "./pkce-handler"
|
||||
import { useNonce } from "./nonce-handler"
|
||||
import * as _checks from "./checks"
|
||||
import { OAuthCallbackError } from "../../errors"
|
||||
|
||||
import type { CallbackParamsType, OpenIDCallbackChecks } from "openid-client"
|
||||
import type { CallbackParamsType } from "openid-client"
|
||||
import type { LoggerInstance, Profile } from "../../.."
|
||||
import type { OAuthChecks, OAuthConfig } from "../../../providers"
|
||||
import type { InternalOptions } from "../../types"
|
||||
@@ -73,24 +71,9 @@ export default async function oAuthCallback(params: {
|
||||
const checks: OAuthChecks = {}
|
||||
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) {
|
||||
checks.code_verifier = pkce.codeVerifier
|
||||
resCookies.push(pkce.cookie)
|
||||
}
|
||||
await _checks.state.use(cookies, resCookies, options, checks)
|
||||
await _checks.pkce.use(cookies, resCookies, options, checks)
|
||||
await _checks.nonce.use(cookies, resCookies, options, checks)
|
||||
|
||||
const params: CallbackParamsType = {
|
||||
...client.callbackParams({
|
||||
|
||||
181
packages/next-auth/src/core/lib/oauth/checks.ts
Normal file
181
packages/next-auth/src/core/lib/oauth/checks.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import {
|
||||
AuthorizationParameters,
|
||||
generators,
|
||||
OpenIDCallbackChecks,
|
||||
} from "openid-client"
|
||||
import * as jwt from "../../../jwt"
|
||||
|
||||
import type { RequestInternal } from "../.."
|
||||
import type { CookiesOptions, InternalOptions } from "../../types"
|
||||
import type { Cookie } from "../cookie"
|
||||
import { OAuthChecks } from "src/providers"
|
||||
|
||||
/** Returns a signed cookie. */
|
||||
export async function signCookie(
|
||||
type: keyof CookiesOptions,
|
||||
value: string,
|
||||
maxAge: number,
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<Cookie> {
|
||||
const { cookies, logger } = options
|
||||
|
||||
logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge })
|
||||
|
||||
const expires = new Date()
|
||||
expires.setTime(expires.getTime() + maxAge * 1000)
|
||||
return {
|
||||
name: cookies[type].name,
|
||||
value: await jwt.encode({ ...options.jwt, maxAge, token: { value } }),
|
||||
options: { ...cookies[type].options, expires },
|
||||
}
|
||||
}
|
||||
|
||||
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
export const PKCE_CODE_CHALLENGE_METHOD = "S256"
|
||||
export const pkce = {
|
||||
async create(
|
||||
options: InternalOptions<"oauth">,
|
||||
cookies: Cookie[],
|
||||
resParams: AuthorizationParameters
|
||||
) {
|
||||
if (!options.provider?.checks?.includes("pkce")) return
|
||||
const code_verifier = generators.codeVerifier()
|
||||
const value = generators.codeChallenge(code_verifier)
|
||||
resParams.code_challenge = value
|
||||
resParams.code_challenge_method = PKCE_CODE_CHALLENGE_METHOD
|
||||
|
||||
const maxAge =
|
||||
options.cookies.pkceCodeVerifier.options.maxAge ?? PKCE_MAX_AGE
|
||||
|
||||
cookies.push(
|
||||
await signCookie("pkceCodeVerifier", code_verifier, maxAge, options)
|
||||
)
|
||||
},
|
||||
/**
|
||||
* Returns code_verifier if the provider is configured to use PKCE,
|
||||
* and clears the container cookie afterwards.
|
||||
* An error is thrown if the code_verifier is missing or invalid.
|
||||
* @see https://www.rfc-editor.org/rfc/rfc7636
|
||||
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#pkce
|
||||
*/
|
||||
async use(
|
||||
cookies: RequestInternal["cookies"],
|
||||
resCookies: Cookie[],
|
||||
options: InternalOptions<"oauth">,
|
||||
checks: OAuthChecks
|
||||
): Promise<string | undefined> {
|
||||
if (!options.provider?.checks?.includes("pkce")) return
|
||||
|
||||
const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name]
|
||||
|
||||
if (!codeVerifier)
|
||||
throw new TypeError("PKCE code_verifier cookie was missing.")
|
||||
|
||||
const value = (await jwt.decode({
|
||||
...options.jwt,
|
||||
token: codeVerifier,
|
||||
})) as any
|
||||
|
||||
if (!value?.value)
|
||||
throw new TypeError("PKCE code_verifier value could not be parsed.")
|
||||
|
||||
resCookies.push({
|
||||
name: options.cookies.pkceCodeVerifier.name,
|
||||
value: "",
|
||||
options: { ...options.cookies.pkceCodeVerifier.options, maxAge: 0 },
|
||||
})
|
||||
|
||||
checks.code_verifier = value.value
|
||||
},
|
||||
}
|
||||
|
||||
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
export const state = {
|
||||
async create(
|
||||
options: InternalOptions<"oauth">,
|
||||
cookies: Cookie[],
|
||||
resParams: AuthorizationParameters
|
||||
) {
|
||||
if (!options.provider.checks?.includes("state")) return
|
||||
const value = generators.state()
|
||||
resParams.state = value
|
||||
const maxAge = options.cookies.state.options.maxAge ?? STATE_MAX_AGE
|
||||
cookies.push(await signCookie("state", value, maxAge, options))
|
||||
},
|
||||
/**
|
||||
* Returns state if the provider is configured to use state,
|
||||
* and clears the container cookie afterwards.
|
||||
* An error is thrown if the state is missing or invalid.
|
||||
* @see https://www.rfc-editor.org/rfc/rfc6749#section-10.12
|
||||
* @see https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
|
||||
*/
|
||||
async use(
|
||||
cookies: RequestInternal["cookies"],
|
||||
resCookies: Cookie[],
|
||||
options: InternalOptions<"oauth">,
|
||||
checks: OAuthChecks
|
||||
) {
|
||||
if (!options.provider.checks?.includes("state")) return
|
||||
|
||||
const state = cookies?.[options.cookies.state.name]
|
||||
|
||||
if (!state) throw new TypeError("State cookie was missing.")
|
||||
|
||||
const value = (await jwt.decode({ ...options.jwt, token: state })) as any
|
||||
|
||||
if (!value?.value) throw new TypeError("State value could not be parsed.")
|
||||
|
||||
resCookies.push({
|
||||
name: options.cookies.state.name,
|
||||
value: "",
|
||||
options: { ...options.cookies.state.options, maxAge: 0 },
|
||||
})
|
||||
|
||||
checks.state = value.value
|
||||
},
|
||||
}
|
||||
|
||||
const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
export const nonce = {
|
||||
async create(
|
||||
options: InternalOptions<"oauth">,
|
||||
cookies: Cookie[],
|
||||
resParams: AuthorizationParameters
|
||||
) {
|
||||
if (!options.provider.checks?.includes("nonce")) return
|
||||
const value = generators.nonce()
|
||||
resParams.nonce = value
|
||||
const maxAge = options.cookies.nonce.options.maxAge ?? NONCE_MAX_AGE
|
||||
cookies.push(await signCookie("nonce", value, maxAge, options))
|
||||
},
|
||||
/**
|
||||
* Returns nonce if the provider is configured to use nonce,
|
||||
* and clears the container cookie afterwards.
|
||||
* An error is thrown if the nonce is missing or invalid.
|
||||
* @see https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes
|
||||
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#nonce
|
||||
*/
|
||||
async use(
|
||||
cookies: RequestInternal["cookies"],
|
||||
resCookies: Cookie[],
|
||||
options: InternalOptions<"oauth">,
|
||||
checks: OpenIDCallbackChecks
|
||||
): Promise<string | undefined> {
|
||||
if (!options.provider?.checks?.includes("nonce")) return
|
||||
|
||||
const nonce = cookies?.[options.cookies.nonce.name]
|
||||
if (!nonce) throw new TypeError("Nonce cookie was missing.")
|
||||
|
||||
const value = (await jwt.decode({ ...options.jwt, token: nonce })) as any
|
||||
|
||||
if (!value?.value) throw new TypeError("Nonce value could not be parsed.")
|
||||
|
||||
resCookies.push({
|
||||
name: options.cookies.nonce.name,
|
||||
value: "",
|
||||
options: { ...options.cookies.nonce.options, maxAge: 0 },
|
||||
})
|
||||
|
||||
checks.nonce = value.value
|
||||
},
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
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,86 +0,0 @@
|
||||
import * as jwt from "../../../jwt"
|
||||
import { generators } from "openid-client"
|
||||
import type { InternalOptions } from "../../types"
|
||||
import type { Cookie } from "../cookie"
|
||||
|
||||
const PKCE_CODE_CHALLENGE_METHOD = "S256"
|
||||
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
|
||||
/**
|
||||
* Returns `code_challenge` and `code_challenge_method`
|
||||
* and saves them in a cookie.
|
||||
*/
|
||||
export async function createPKCE(options: InternalOptions<"oauth">): Promise<
|
||||
| undefined
|
||||
| {
|
||||
code_challenge: string
|
||||
code_challenge_method: "S256"
|
||||
cookie: Cookie
|
||||
}
|
||||
> {
|
||||
const { cookies, logger, provider } = options
|
||||
if (!provider.checks?.includes("pkce")) {
|
||||
// Provider does not support PKCE, return nothing.
|
||||
return
|
||||
}
|
||||
const code_verifier = generators.codeVerifier()
|
||||
const code_challenge = generators.codeChallenge(code_verifier)
|
||||
|
||||
const maxAge = cookies.pkceCodeVerifier.options.maxAge ?? PKCE_MAX_AGE
|
||||
|
||||
const expires = new Date()
|
||||
expires.setTime(expires.getTime() + maxAge * 1000)
|
||||
|
||||
// Encrypt code_verifier and save it to an encrypted cookie
|
||||
const encryptedCodeVerifier = await jwt.encode({
|
||||
...options.jwt,
|
||||
maxAge,
|
||||
token: { code_verifier },
|
||||
})
|
||||
|
||||
logger.debug("CREATE_PKCE_CHALLENGE_VERIFIER", {
|
||||
code_challenge,
|
||||
code_challenge_method: PKCE_CODE_CHALLENGE_METHOD,
|
||||
code_verifier,
|
||||
maxAge,
|
||||
})
|
||||
|
||||
return {
|
||||
code_challenge,
|
||||
code_challenge_method: PKCE_CODE_CHALLENGE_METHOD,
|
||||
cookie: {
|
||||
name: cookies.pkceCodeVerifier.name,
|
||||
value: encryptedCodeVerifier,
|
||||
options: { ...cookies.pkceCodeVerifier.options, expires },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns code_verifier if provider uses PKCE,
|
||||
* and clears the container cookie afterwards.
|
||||
*/
|
||||
export async function usePKCECodeVerifier(
|
||||
codeVerifier: string | undefined,
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<{ codeVerifier: string; cookie: Cookie } | undefined> {
|
||||
const { cookies, provider } = options
|
||||
|
||||
if (!provider?.checks?.includes("pkce") || !codeVerifier) {
|
||||
return
|
||||
}
|
||||
|
||||
const pkce = (await jwt.decode({
|
||||
...options.jwt,
|
||||
token: codeVerifier,
|
||||
})) as any
|
||||
|
||||
return {
|
||||
codeVerifier: pkce?.code_verifier ?? undefined,
|
||||
cookie: {
|
||||
name: cookies.pkceCodeVerifier.name,
|
||||
value: "",
|
||||
options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 },
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { generators } from "openid-client"
|
||||
|
||||
import type { InternalOptions } from "../../types"
|
||||
import type { Cookie } from "../cookie"
|
||||
|
||||
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
|
||||
/** Returns state if the provider supports it */
|
||||
export async function createState(
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<{ cookie: Cookie; value: string } | undefined> {
|
||||
const { logger, provider, jwt, cookies } = options
|
||||
|
||||
if (!provider.checks?.includes("state")) {
|
||||
// Provider does not support state, return nothing
|
||||
return
|
||||
}
|
||||
|
||||
const state = generators.state()
|
||||
const maxAge = cookies.state.options.maxAge ?? STATE_MAX_AGE
|
||||
|
||||
const encodedState = await jwt.encode({
|
||||
...jwt,
|
||||
maxAge,
|
||||
token: { state },
|
||||
})
|
||||
|
||||
logger.debug("CREATE_STATE", { state, maxAge })
|
||||
|
||||
const expires = new Date()
|
||||
expires.setTime(expires.getTime() + maxAge * 1000)
|
||||
return {
|
||||
value: state,
|
||||
cookie: {
|
||||
name: cookies.state.name,
|
||||
value: encodedState,
|
||||
options: { ...cookies.state.options, expires },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns state from if the provider supports states,
|
||||
* and clears the container cookie afterwards.
|
||||
*/
|
||||
export async function useState(
|
||||
state: string | undefined,
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<{ value: string; cookie: Cookie } | undefined> {
|
||||
const { cookies, provider, jwt } = options
|
||||
|
||||
if (!provider.checks?.includes("state")) return
|
||||
|
||||
if (!state) throw new Error("No state provided")
|
||||
|
||||
const value = (await jwt.decode({ ...options.jwt, token: state })) as any
|
||||
|
||||
return {
|
||||
value: value?.state ?? undefined,
|
||||
cookie: {
|
||||
name: cookies.state.name,
|
||||
value: "",
|
||||
options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 },
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user