Skip to content
Merged
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
18 changes: 17 additions & 1 deletion packages/core/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function AuthInternal<
case "providers":
return (await routes.providers(options.providers)) as any
case "session": {
const session = await routes.session(sessionStore, options)
const session = await routes.session({ sessionStore, options })
if (session.cookies) cookies.push(...session.cookies)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return { ...session, cookies } as any
Expand Down Expand Up @@ -177,6 +177,22 @@ export async function AuthInternal<
return { ...callback, cookies }
}
break
case "session": {
if (options.csrfTokenVerified) {
const session = await routes.session({
options,
sessionStore,
newSession: request.body?.data,
isUpdate: true,
})
if (session.cookies) cookies.push(...session.cookies)
return { ...session, cookies } as any
}

// If CSRF token is invalid, return a 400 status code
// we should not redirect to a page as this is an API route
return { status: 400, cookies }
}
default:
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/lib/routes/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export async function callback(params: {
account,
profile: OAuthProfile,
isNewUser,
trigger: isNewUser ? "signUp" : "signIn",
})

// Clear cookies if token is null
Expand Down Expand Up @@ -244,6 +245,7 @@ export async function callback(params: {
user: loggedInUser,
account,
isNewUser,
trigger: isNewUser ? "signUp" : "signIn",
})

// Clear cookies if token is null
Expand Down Expand Up @@ -340,6 +342,7 @@ export async function callback(params: {
// @ts-expect-error
account,
isNewUser: false,
trigger: "signIn",
})

// Clear cookies if token is null
Expand Down
29 changes: 19 additions & 10 deletions packages/core/src/lib/routes/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import type { InternalOptions, ResponseInternal, Session } from "../../types.js"
import type { SessionStore } from "../cookie.js"

/** Return a session object filtered via `callbacks.session` */
export async function session(
sessionStore: SessionStore,
export async function session(params: {
options: InternalOptions
): Promise<ResponseInternal<Session | null>> {
sessionStore: SessionStore
isUpdate?: boolean
newSession?: any
}): Promise<ResponseInternal<Session | null>> {
const { options, sessionStore, newSession, isUpdate } = params
const {
adapter,
jwt,
Expand All @@ -33,22 +36,26 @@ export async function session(
try {
const decodedToken = await jwt.decode({ ...jwt, token: sessionToken })

const token = await callbacks.jwt({
// @ts-expect-error
token: decodedToken,
...(isUpdate && { trigger: "update" }),
session: newSession,
})

const newExpires = fromDate(sessionMaxAge)

// By default, only exposes a limited subset of information to the client
// as needed for presentation purposes (e.g. "you are logged in as...").
const session = {
user: {
name: decodedToken?.name,
email: decodedToken?.email,
image: decodedToken?.picture,
name: token?.name,
email: token?.email,
image: token?.picture,
},
expires: newExpires.toISOString(),
}

// @ts-expect-error
const token = await callbacks.jwt({ token: decodedToken })

if (token !== null) {
// @ts-expect-error
const newSession = await callbacks.session({ session, token })
Expand Down Expand Up @@ -128,11 +135,13 @@ export async function session(
user: {
name: user.name,
email: user.email,
image: user.image,
picture: user.image,
},
expires: session.expires.toISOString(),
},
user,
newSession,
...(isUpdate ? { trigger: "update" } : {}),
})

// Return session payload as response
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/providers/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ export interface CredentialsConfig<
* @example
* ```ts
* //...
* async authorize(, request) {
* async authorize(credentials, request) {
* if(!isValidCredentials(credentials)) return null
* const response = await fetch(request)
* if(!response.ok) return null
* return await response.json() ?? null
* }
* //...
* ```
*/
authorize: (
/**
Expand Down
92 changes: 70 additions & 22 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,40 +238,86 @@ export interface CallbacksOptions<P = Profile, A = Account> {
* If you want to make something available you added to the token through the `jwt` callback,
* you have to explicitly forward it here to make it available to the client.
*
* [Documentation](https://authjs.dev/guides/basics/callbacks#session-callback) |
* [`jwt` callback](https://authjs.dev/guides/basics/callbacks#jwt-callback) |
* [`useSession`](https://authjs.dev/reference/react/#usesession) |
* [`getSession`](https://authjs.dev/reference/utilities/#getsession) |
*
* @see [`jwt` callback](https://authjs.dev/reference/core/types#jwt)
*/
session: (params: {
session: Session
user: User | AdapterUser
token: JWT
}) => Awaitable<Session>
session: (
params:
| {
session: Session
/** Available when {@link AuthConfig.session} is set to `strategy: "jwt"` */
token: JWT
/** Available when {@link AuthConfig.session} is set to `strategy: "database"`. */
user: AdapterUser
} & {
/**
* Available when using {@link AuthConfig.session} `strategy: "database"` and an update is triggered for the session.
*
* :::note
* You should validate this data before using it.
* :::
*/
newSession: any
trigger: "update"
}
) => Awaitable<Session | DefaultSession>
/**
* This callback is called whenever a JSON Web Token is created (i.e. at sign in)
* or updated (i.e whenever a session is accessed in the client).
* Its content is forwarded to the `session` callback,
* where you can control what should be returned to the client.
* Anything else will be kept inaccessible from the client.
* Anything else will be kept from your front-end.
*
* Returning `null` will invalidate the JWT session by clearing
* the user's cookies. You'll still have to monitor and invalidate
* unexpired tokens from future requests yourself to prevent
* unauthorized access.
* The JWT is encrypted by default.
*
* By default the JWT is encrypted.
*
* [Documentation](https://authjs.dev/guides/basics/callbacks#jwt-callback) |
* [`session` callback](https://authjs.dev/guides/basics/callbacks#session-callback)
* [Documentation](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
* [`session` callback](https://next-auth.js.org/configuration/callbacks#session-callback)
*/
jwt: (params: {
/**
* When `trigger` is `"signIn"` or `"signUp"`, it will be a subset of {@link JWT},
* `name`, `email` and `image` will be included.
*
* Otherwise, it will be the full {@link JWT} for subsequent calls.
*/
token: JWT
user?: User | AdapterUser
account?: A | null
/**
* Either the result of the {@link OAuthConfig.profile} or the {@link CredentialsConfig.authorize} callback.
* @note available when `trigger` is `"signIn"` or `"signUp"`.
*
* Resources:
* - [Credentials Provider](https://authjs.dev/reference/core/providers_credentials)
* - [User database model](https://authjs.dev/reference/adapters#user)
*/
user: User | AdapterUser
/**
* Contains information about the provider that was used to sign in.
* Also includes {@link TokenSet}
* @note available when `trigger` is `"signIn"` or `"signUp"`
*/
account: A | null
/**
* The OAuth profile returned from your provider.
* (In case of OIDC it will be the decoded ID Token or /userinfo response)
* @note available when `trigger` is `"signIn"`.
*/
profile?: P
/**
* Check why was the jwt callback invoked. Possible reasons are:
* - user sign-in: First time the callback is invoked, `user`, `profile` and `account` will be present.
* - user sign-up: a user is created for the first time in the database (when {@link AuthConfig.session}.strategy is set to `"database"`)
* - update event: Triggered by the [`useSession().update`](https://next-auth.js.org/getting-started/client#update-session) method.
* In case of the latter, `trigger` will be `undefined`.
*/
trigger?: "signIn" | "signUp" | "update"
/** @deprecated use `trigger === "signUp"` instead */
isNewUser?: boolean
/**
* When using {@link AuthConfig.session} `strategy: "jwt"`, this is the data
* sent from the client via the [`useSession().update`](https://next-auth.js.org/getting-started/client#update-session) method.
*
* ⚠ Note, you should validate this data before using it.
*/
session?: any
}) => Awaitable<JWT | null>
}

Expand Down Expand Up @@ -451,7 +497,9 @@ export type InternalProvider<T = ProviderType> = (T extends "oauth"
* :::
* - **`"error"`**: Renders the built-in error page.
* - **`"providers"`**: Returns a client-safe list of all configured providers.
* - **`"session"`**: Returns the user's session if it exists, otherwise `null`.
* - **`"session"`**:
* - **`GET**`: Returns the user's session if it exists, otherwise `null`.
* - **`POST**`: Updates the user's session and returns the updated session.
* - **`"signin"`**:
* - **`GET`**: Renders the built-in sign-in page.
* - **`POST`**: Initiates the sign-in flow.
Expand Down