Skip to content

Commit 92df43b

Browse files
authored
4.0.0-beta.13 (#1850)
1 parent 5bd9a1e commit 92df43b

File tree

5 files changed

+173
-122
lines changed

5 files changed

+173
-122
lines changed

README.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
### 1. Install the SDK
66

77
```shell
8-
npm i @auth0/nextjs-auth0@4.0.0-beta.12
8+
npm i @auth0/nextjs-auth0@beta
99
```
1010

1111
### 2. Add the environment variables
@@ -115,8 +115,8 @@ You can customize the client by using the options below:
115115
| clientId | `string` | The Auth0 client ID. If it's not specified, it will be loaded from the `AUTH0_CLIENT_ID` environment variable. |
116116
| clientSecret | `string` | The Auth0 client secret. If it's not specified, it will be loaded from the `AUTH0_CLIENT_SECRET` environment variable. |
117117
| authorizationParameters | `AuthorizationParameters` | The authorization parameters to pass to the `/authorize` endpoint. See [Passing authorization parameters](#passing-authorization-parameters) for more details. |
118-
| clientAssertionSigningKey | `string` or `CryptoKey` | Private key for use with `private_key_jwt` clients. |
119-
| clientAssertionSigningAlg | `string` | The algorithm used to sign the client assertion JWT. |
118+
| clientAssertionSigningKey | `string` or `CryptoKey` | Private key for use with `private_key_jwt` clients. This can also be specified via the `AUTH0_CLIENT_ASSERTION_SIGNING_KEY` environment variable. |
119+
| clientAssertionSigningAlg | `string` | The algorithm used to sign the client assertion JWT. This can also be provided via the `AUTH0_CLIENT_ASSERTION_SIGNING_ALG` environment variable. |
120120
| appBaseUrl | `string` | The URL of your application (e.g.: `http://localhost:3000`). If it's not specified, it will be loaded from the `APP_BASE_URL` environment variable. |
121121
| secret | `string` | A 32-byte, hex-encoded secret used for encrypting cookies. If it's not specified, it will be loaded from the `AUTH0_SECRET` environment variable. |
122122
| signInReturnToPath | `string` | The path to redirect the user to after successfully authenticating. Defaults to `/`. |
@@ -351,7 +351,12 @@ export default function Component() {
351351

352352
### On the server (App Router)
353353

354-
On the server, the `getAccessToken()` helper can be used in Server Components, Server Routes, Server Actions, and middleware to get an access token to call external APIs, like so:
354+
On the server, the `getAccessToken()` helper can be used in Server Routes, Server Actions, Server Components, and middleware to get an access token to call external APIs.
355+
356+
> [!IMPORTANT]
357+
> Server Components cannot set cookies. Calling `getAccessToken()` in a Server Component will cause the access token to be refreshed, if it is expired, and the updated token set will not to be persisted.
358+
359+
For example:
355360

356361
```tsx
357362
import { NextResponse } from "next/server"
@@ -374,7 +379,7 @@ export async function GET() {
374379

375380
### On the server (Pages Router)
376381

377-
On the server, the `getAccessToken(req)` helper can be used in `getServerSideProps`, API routes, and middleware to get an access token to call external APIs, like so:
382+
On the server, the `getAccessToken(req, res)` helper can be used in `getServerSideProps`, API routes, and middleware to get an access token to call external APIs, like so:
378383

379384
```tsx
380385
import type { NextApiRequest, NextApiResponse } from "next"
@@ -386,7 +391,7 @@ export default async function handler(
386391
res: NextApiResponse<{ message: string }>
387392
) {
388393
try {
389-
const token = await auth0.getAccessToken(req)
394+
const token = await auth0.getAccessToken(req, res)
390395
// call external API with token...
391396
} catch (err) {
392397
// err will be an instance of AccessTokenError if an access token could not be obtained
@@ -434,11 +439,11 @@ The SDK exposes hooks to enable you to provide custom logic that would be run at
434439

435440
The `beforeSessionSaved` hook is run right before the session is persisted. It provides a mechanism to modify the session claims before persisting them.
436441

437-
The hook recieves a `SessionData` object and must return a Promise that resolves to a `SessionData` object: `(session: SessionData) => Promise<SessionData>`. For example:
442+
The hook recieves a `SessionData` object and an ID token. The function must return a Promise that resolves to a `SessionData` object: `(session: SessionData) => Promise<SessionData>`. For example:
438443

439444
```ts
440445
export const auth0 = new Auth0Client({
441-
async beforeSessionSaved(session) {
446+
async beforeSessionSaved(session, idToken) {
442447
return {
443448
...session,
444449
user: {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@auth0/nextjs-auth0",
3-
"version": "4.0.0-beta.12",
3+
"version": "4.0.0-beta.13",
44
"description": "Auth0 Next.js SDK",
55
"scripts": {
66
"build": "tsc",

src/server/auth-client.test.ts

Lines changed: 80 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -435,18 +435,7 @@ ca/T0LLtgmbMmxSv/MmzIg==
435435
}
436436
)
437437

438-
const expiresAt = Math.floor(Date.now() / 1000) - 10 * 24 * 60 * 60 // expired 10 days ago
439-
const updatedTokenSet = {
440-
accessToken: "at_456",
441-
refreshToken: "rt_456",
442-
expiresAt,
443-
}
444-
authClient.getTokenSet = vi
445-
.fn()
446-
.mockResolvedValue([null, updatedTokenSet])
447-
448438
const response = await authClient.handler(request)
449-
expect(authClient.getTokenSet).toHaveBeenCalled()
450439

451440
// assert session has been updated
452441
const updatedSessionCookie = response.cookies.get("__session")
@@ -460,8 +449,8 @@ ca/T0LLtgmbMmxSv/MmzIg==
460449
sub: DEFAULT.sub,
461450
},
462451
tokenSet: {
463-
accessToken: "at_456",
464-
refreshToken: "rt_456",
452+
accessToken: "at_123",
453+
refreshToken: "rt_123",
465454
expiresAt: expect.any(Number),
466455
},
467456
internal: {
@@ -516,70 +505,6 @@ ca/T0LLtgmbMmxSv/MmzIg==
516505
const updatedSessionCookie = response.cookies.get("__session")
517506
expect(updatedSessionCookie).toBeUndefined()
518507
})
519-
520-
it("should pass the request through if there was an error fetching the updated token set", async () => {
521-
const secret = await generateSecret(32)
522-
const transactionStore = new TransactionStore({
523-
secret,
524-
})
525-
const sessionStore = new StatelessSessionStore({
526-
secret,
527-
528-
rolling: true,
529-
absoluteDuration: 3600,
530-
inactivityDuration: 1800,
531-
})
532-
const authClient = new AuthClient({
533-
transactionStore,
534-
sessionStore,
535-
536-
domain: DEFAULT.domain,
537-
clientId: DEFAULT.clientId,
538-
clientSecret: DEFAULT.clientSecret,
539-
540-
secret,
541-
appBaseUrl: DEFAULT.appBaseUrl,
542-
543-
fetch: getMockAuthorizationServer(),
544-
})
545-
546-
const session: SessionData = {
547-
user: { sub: DEFAULT.sub },
548-
tokenSet: {
549-
accessToken: DEFAULT.accessToken,
550-
refreshToken: DEFAULT.refreshToken,
551-
expiresAt: 123456,
552-
},
553-
internal: {
554-
sid: DEFAULT.sid,
555-
createdAt: Math.floor(Date.now() / 1000),
556-
},
557-
}
558-
const sessionCookie = await encrypt(session, secret)
559-
const headers = new Headers()
560-
headers.append("cookie", `__session=${sessionCookie}`)
561-
const request = new NextRequest(
562-
"https://example.com/dashboard/projects",
563-
{
564-
method: "GET",
565-
headers,
566-
}
567-
)
568-
569-
authClient.getTokenSet = vi
570-
.fn()
571-
.mockResolvedValue([
572-
new Error("error fetching updated token set"),
573-
null,
574-
])
575-
576-
const response = await authClient.handler(request)
577-
expect(authClient.getTokenSet).toHaveBeenCalled()
578-
579-
// assert session has not been updated
580-
const updatedSessionCookie = response.cookies.get("__session")
581-
expect(updatedSessionCookie).toBeUndefined()
582-
})
583508
})
584509

585510
describe("with custom routes", async () => {
@@ -2839,6 +2764,84 @@ ca/T0LLtgmbMmxSv/MmzIg==
28392764
})
28402765

28412766
describe("beforeSessionSaved hook", async () => {
2767+
it("should be called with the correct arguments", async () => {
2768+
const state = "transaction-state"
2769+
const code = "auth-code"
2770+
2771+
const secret = await generateSecret(32)
2772+
const transactionStore = new TransactionStore({
2773+
secret,
2774+
})
2775+
const sessionStore = new StatelessSessionStore({
2776+
secret,
2777+
})
2778+
const mockBeforeSessionSaved = vi.fn().mockResolvedValue({
2779+
user: {
2780+
sub: DEFAULT.sub,
2781+
},
2782+
internal: {
2783+
sid: DEFAULT.sid,
2784+
expiresAt: expect.any(Number),
2785+
},
2786+
})
2787+
const authClient = new AuthClient({
2788+
transactionStore,
2789+
sessionStore,
2790+
2791+
domain: DEFAULT.domain,
2792+
clientId: DEFAULT.clientId,
2793+
clientSecret: DEFAULT.clientSecret,
2794+
2795+
secret,
2796+
appBaseUrl: DEFAULT.appBaseUrl,
2797+
2798+
fetch: getMockAuthorizationServer(),
2799+
2800+
beforeSessionSaved: mockBeforeSessionSaved,
2801+
})
2802+
2803+
const url = new URL("/auth/callback", DEFAULT.appBaseUrl)
2804+
url.searchParams.set("code", code)
2805+
url.searchParams.set("state", state)
2806+
2807+
const headers = new Headers()
2808+
const transactionState: TransactionState = {
2809+
nonce: "nonce-value",
2810+
maxAge: 3600,
2811+
codeVerifier: "code-verifier",
2812+
responseType: "code",
2813+
state: state,
2814+
returnTo: "/dashboard",
2815+
}
2816+
headers.set(
2817+
"cookie",
2818+
`__txn_${state}=${await encrypt(transactionState, secret)}`
2819+
)
2820+
const request = new NextRequest(url, {
2821+
method: "GET",
2822+
headers,
2823+
})
2824+
2825+
await authClient.handleCallback(request)
2826+
expect(mockBeforeSessionSaved).toHaveBeenCalledWith(
2827+
{
2828+
user: expect.objectContaining({
2829+
sub: DEFAULT.sub,
2830+
}),
2831+
tokenSet: {
2832+
accessToken: DEFAULT.accessToken,
2833+
refreshToken: DEFAULT.refreshToken,
2834+
expiresAt: expect.any(Number),
2835+
},
2836+
internal: {
2837+
sid: expect.any(String),
2838+
createdAt: expect.any(Number),
2839+
},
2840+
},
2841+
expect.any(String)
2842+
)
2843+
})
2844+
28422845
it("should use the return value of the hook as the session data", async () => {
28432846
const state = "transaction-state"
28442847
const code = "auth-code"

src/server/auth-client.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import { TransactionState, TransactionStore } from "./transaction-store"
2323
import { filterClaims } from "./user"
2424

2525
export type BeforeSessionSavedHook = (
26-
session: SessionData
26+
session: SessionData,
27+
idToken: string | null
2728
) => Promise<SessionData>
2829

2930
type OnCallbackContext = {
@@ -114,7 +115,7 @@ export class AuthClient {
114115
private clientSecret?: string
115116
private clientAssertionSigningKey?: string | CryptoKey
116117
private clientAssertionSigningAlg: string
117-
private issuer: string
118+
private domain: string
118119
private authorizationParameters: AuthorizationParameters
119120
private pushedAuthorizationRequests: boolean
120121

@@ -147,11 +148,7 @@ export class AuthClient {
147148
this.sessionStore = options.sessionStore
148149

149150
// authorization server
150-
this.issuer =
151-
options.domain.startsWith("http://") ||
152-
options.domain.startsWith("https://")
153-
? options.domain
154-
: `https://${options.domain}`
151+
this.domain = options.domain
155152
this.clientMetadata = { client_id: options.clientId }
156153
this.clientSecret = options.clientSecret
157154
this.authorizationParameters = options.authorizationParameters || {
@@ -224,20 +221,10 @@ export class AuthClient {
224221
const session = await this.sessionStore.get(req.cookies)
225222

226223
if (session) {
227-
// refresh the access token, if necessary
228-
const [error, updatedTokenSet] = await this.getTokenSet(
229-
session.tokenSet
230-
)
231-
232-
if (error) {
233-
return res
234-
}
235-
236224
// we pass the existing session (containing an `createdAt` timestamp) to the set method
237225
// which will update the cookie's `maxAge` property based on the `createdAt` time
238226
await this.sessionStore.set(req.cookies, res.cookies, {
239227
...session,
240-
tokenSet: updatedTokenSet,
241228
})
242229
}
243230

@@ -452,8 +439,14 @@ export class AuthClient {
452439
const res = await this.onCallback(null, onCallbackCtx, session)
453440

454441
if (this.beforeSessionSaved) {
455-
const { user } = await this.beforeSessionSaved(session)
456-
session.user = user || {}
442+
const updatedSession = await this.beforeSessionSaved(
443+
session,
444+
oidcRes.id_token ?? null
445+
)
446+
session = {
447+
...updatedSession,
448+
internal: session.internal,
449+
}
457450
} else {
458451
session.user = filterClaims(idTokenClaims)
459452
}
@@ -878,4 +871,11 @@ export class AuthClient {
878871
? oauth.PrivateKeyJwt(clientPrivateKey)
879872
: oauth.ClientSecretPost(this.clientSecret!)
880873
}
874+
875+
private get issuer(): string {
876+
return this.domain.startsWith("http://") ||
877+
this.domain.startsWith("https://")
878+
? this.domain
879+
: `https://${this.domain}`
880+
}
881881
}

0 commit comments

Comments
 (0)