Skip to content

Commit 79ad615

Browse files
committed
feat: add update session to core (#7505)
* feat: add update session to core Integrates #7056 into `@auth/core` * resolve default user after jwt callback
1 parent 28f287d commit 79ad615

File tree

5 files changed

+112
-34
lines changed

5 files changed

+112
-34
lines changed

packages/core/src/lib/index.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export async function AuthInternal<
4747
case "providers":
4848
return (await routes.providers(options.providers)) as any
4949
case "session": {
50-
const session = await routes.session(sessionStore, options)
50+
const session = await routes.session({ sessionStore, options })
5151
if (session.cookies) cookies.push(...session.cookies)
5252
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
5353
return { ...session, cookies } as any
@@ -177,6 +177,22 @@ export async function AuthInternal<
177177
return { ...callback, cookies }
178178
}
179179
break
180+
case "session": {
181+
if (options.csrfTokenVerified) {
182+
const session = await routes.session({
183+
options,
184+
sessionStore,
185+
newSession: request.body?.data,
186+
isUpdate: true,
187+
})
188+
if (session.cookies) cookies.push(...session.cookies)
189+
return { ...session, cookies } as any
190+
}
191+
192+
// If CSRF token is invalid, return a 400 status code
193+
// we should not redirect to a page as this is an API route
194+
return { status: 400, cookies }
195+
}
180196
default:
181197
}
182198
}

packages/core/src/lib/routes/callback.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ export async function callback(params: {
134134
account,
135135
profile: OAuthProfile,
136136
isNewUser,
137+
trigger: isNewUser ? "signUp" : "signIn",
137138
})
138139

139140
// Clear cookies if token is null
@@ -244,6 +245,7 @@ export async function callback(params: {
244245
user: loggedInUser,
245246
account,
246247
isNewUser,
248+
trigger: isNewUser ? "signUp" : "signIn",
247249
})
248250

249251
// Clear cookies if token is null
@@ -340,6 +342,7 @@ export async function callback(params: {
340342
// @ts-expect-error
341343
account,
342344
isNewUser: false,
345+
trigger: "signIn",
343346
})
344347

345348
// Clear cookies if token is null

packages/core/src/lib/routes/session.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import type { InternalOptions, ResponseInternal, Session } from "../../types.js"
66
import type { SessionStore } from "../cookie.js"
77

88
/** Return a session object filtered via `callbacks.session` */
9-
export async function session(
10-
sessionStore: SessionStore,
9+
export async function session(params: {
1110
options: InternalOptions
12-
): Promise<ResponseInternal<Session | null>> {
11+
sessionStore: SessionStore
12+
isUpdate?: boolean
13+
newSession?: any
14+
}): Promise<ResponseInternal<Session | null>> {
15+
const { options, sessionStore, newSession, isUpdate } = params
1316
const {
1417
adapter,
1518
jwt,
@@ -33,22 +36,26 @@ export async function session(
3336
try {
3437
const decodedToken = await jwt.decode({ ...jwt, token: sessionToken })
3538

39+
const token = await callbacks.jwt({
40+
// @ts-expect-error
41+
token: decodedToken,
42+
...(isUpdate && { trigger: "update" }),
43+
session: newSession,
44+
})
45+
3646
const newExpires = fromDate(sessionMaxAge)
3747

3848
// By default, only exposes a limited subset of information to the client
3949
// as needed for presentation purposes (e.g. "you are logged in as...").
4050
const session = {
4151
user: {
42-
name: decodedToken?.name,
43-
email: decodedToken?.email,
44-
image: decodedToken?.picture,
52+
name: token?.name,
53+
email: token?.email,
54+
image: token?.picture,
4555
},
4656
expires: newExpires.toISOString(),
4757
}
4858

49-
// @ts-expect-error
50-
const token = await callbacks.jwt({ token: decodedToken })
51-
5259
if (token !== null) {
5360
// @ts-expect-error
5461
const newSession = await callbacks.session({ session, token })
@@ -128,11 +135,13 @@ export async function session(
128135
user: {
129136
name: user.name,
130137
email: user.email,
131-
image: user.image,
138+
picture: user.image,
132139
},
133140
expires: session.expires.toISOString(),
134141
},
135142
user,
143+
newSession,
144+
...(isUpdate ? { trigger: "update" } : {}),
136145
})
137146

138147
// Return session payload as response

packages/core/src/providers/credentials.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ export interface CredentialsConfig<
3232
* @example
3333
* ```ts
3434
* //...
35-
* async authorize(, request) {
35+
* async authorize(credentials, request) {
36+
* if(!isValidCredentials(credentials)) return null
3637
* const response = await fetch(request)
3738
* if(!response.ok) return null
3839
* return await response.json() ?? null
3940
* }
4041
* //...
42+
* ```
4143
*/
4244
authorize: (
4345
/**

packages/core/src/types.ts

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -238,40 +238,86 @@ export interface CallbacksOptions<P = Profile, A = Account> {
238238
* If you want to make something available you added to the token through the `jwt` callback,
239239
* you have to explicitly forward it here to make it available to the client.
240240
*
241-
* [Documentation](https://authjs.dev/guides/basics/callbacks#session-callback) |
242-
* [`jwt` callback](https://authjs.dev/guides/basics/callbacks#jwt-callback) |
243-
* [`useSession`](https://authjs.dev/reference/react/#usesession) |
244-
* [`getSession`](https://authjs.dev/reference/utilities/#getsession) |
245-
*
241+
* @see [`jwt` callback](https://authjs.dev/reference/core/types#jwt)
246242
*/
247-
session: (params: {
248-
session: Session
249-
user: User | AdapterUser
250-
token: JWT
251-
}) => Awaitable<Session>
243+
session: (
244+
params:
245+
| {
246+
session: Session
247+
/** Available when {@link AuthConfig.session} is set to `strategy: "jwt"` */
248+
token: JWT
249+
/** Available when {@link AuthConfig.session} is set to `strategy: "database"`. */
250+
user: AdapterUser
251+
} & {
252+
/**
253+
* Available when using {@link AuthConfig.session} `strategy: "database"` and an update is triggered for the session.
254+
*
255+
* :::note
256+
* You should validate this data before using it.
257+
* :::
258+
*/
259+
newSession: any
260+
trigger: "update"
261+
}
262+
) => Awaitable<Session | DefaultSession>
252263
/**
253264
* This callback is called whenever a JSON Web Token is created (i.e. at sign in)
254265
* or updated (i.e whenever a session is accessed in the client).
255266
* Its content is forwarded to the `session` callback,
256267
* where you can control what should be returned to the client.
257-
* Anything else will be kept inaccessible from the client.
268+
* Anything else will be kept from your front-end.
258269
*
259-
* Returning `null` will invalidate the JWT session by clearing
260-
* the user's cookies. You'll still have to monitor and invalidate
261-
* unexpired tokens from future requests yourself to prevent
262-
* unauthorized access.
270+
* The JWT is encrypted by default.
263271
*
264-
* By default the JWT is encrypted.
265-
*
266-
* [Documentation](https://authjs.dev/guides/basics/callbacks#jwt-callback) |
267-
* [`session` callback](https://authjs.dev/guides/basics/callbacks#session-callback)
272+
* [Documentation](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
273+
* [`session` callback](https://next-auth.js.org/configuration/callbacks#session-callback)
268274
*/
269275
jwt: (params: {
276+
/**
277+
* When `trigger` is `"signIn"` or `"signUp"`, it will be a subset of {@link JWT},
278+
* `name`, `email` and `image` will be included.
279+
*
280+
* Otherwise, it will be the full {@link JWT} for subsequent calls.
281+
*/
270282
token: JWT
271-
user?: User | AdapterUser
272-
account?: A | null
283+
/**
284+
* Either the result of the {@link OAuthConfig.profile} or the {@link CredentialsConfig.authorize} callback.
285+
* @note available when `trigger` is `"signIn"` or `"signUp"`.
286+
*
287+
* Resources:
288+
* - [Credentials Provider](https://authjs.dev/reference/core/providers_credentials)
289+
* - [User database model](https://authjs.dev/reference/adapters#user)
290+
*/
291+
user: User | AdapterUser
292+
/**
293+
* Contains information about the provider that was used to sign in.
294+
* Also includes {@link TokenSet}
295+
* @note available when `trigger` is `"signIn"` or `"signUp"`
296+
*/
297+
account: A | null
298+
/**
299+
* The OAuth profile returned from your provider.
300+
* (In case of OIDC it will be the decoded ID Token or /userinfo response)
301+
* @note available when `trigger` is `"signIn"`.
302+
*/
273303
profile?: P
304+
/**
305+
* Check why was the jwt callback invoked. Possible reasons are:
306+
* - user sign-in: First time the callback is invoked, `user`, `profile` and `account` will be present.
307+
* - user sign-up: a user is created for the first time in the database (when {@link AuthConfig.session}.strategy is set to `"database"`)
308+
* - update event: Triggered by the [`useSession().update`](https://next-auth.js.org/getting-started/client#update-session) method.
309+
* In case of the latter, `trigger` will be `undefined`.
310+
*/
311+
trigger?: "signIn" | "signUp" | "update"
312+
/** @deprecated use `trigger === "signUp"` instead */
274313
isNewUser?: boolean
314+
/**
315+
* When using {@link AuthConfig.session} `strategy: "jwt"`, this is the data
316+
* sent from the client via the [`useSession().update`](https://next-auth.js.org/getting-started/client#update-session) method.
317+
*
318+
* ⚠ Note, you should validate this data before using it.
319+
*/
320+
session?: any
275321
}) => Awaitable<JWT | null>
276322
}
277323

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

0 commit comments

Comments
 (0)