Skip to content

Commit 9951b74

Browse files
Merge branch 'main' into fix/pass-through-enable=access-token-endpoint
2 parents 3aa5c08 + e855497 commit 9951b74

File tree

11 files changed

+960
-69
lines changed

11 files changed

+960
-69
lines changed

EXAMPLES.md

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
- [Passing authorization parameters](#passing-authorization-parameters-1)
3737
- [The `returnTo` parameter](#the-returnto-parameter-1)
3838
- [Redirecting the user after authentication](#redirecting-the-user-after-authentication-1)
39+
- [Getting access tokens for connections](#getting-access-tokens-for-connections)
40+
- [On the server (App Router)](#on-the-server-app-router-3)
41+
- [On the server (Pages Router)](#on-the-server-pages-router-3)
42+
- [Middleware](#middleware-3)
3943

4044
## Passing authorization parameters
4145

@@ -770,7 +774,6 @@ const sessionCookieValue = await generateSessionCookie(
770774
)
771775
```
772776

773-
774777
## Programmatically starting interactive login
775778

776779
Additionally to the ability to initialize the interactive login process by redirecting the user to the built-in `auth/login` endpoint,
@@ -834,3 +837,137 @@ export const GET = async (req: NextRequest) => {
834837

835838
> [!NOTE]
836839
> The URLs specified as `returnTo` parameters must be registered in your client's **Allowed Callback URLs**.
840+
841+
842+
## Getting access tokens for connections
843+
You can retrieve an access token for a connection using the `getAccessTokenForConnection()` method, which accepts an object with the following properties:
844+
- `connection`: The federated connection for which an access token should be retrieved.
845+
- `login_hint`: The optional login_hint parameter to pass to the `/authorize` endpoint.
846+
847+
### On the server (App Router)
848+
849+
On the server, the `getAccessTokenForConnection()` helper can be used in Server Routes, Server Actions and Server Components to get an access token for a connection.
850+
851+
> [!IMPORTANT]
852+
> Server Components cannot set cookies. Calling `getAccessTokenForConnection()` 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.
853+
>
854+
> It is recommended to call `getAccessTokenForConnection(req, res)` in the middleware if you need to refresh the token in a Server Component as this will ensure the token is refreshed and correctly persisted.
855+
856+
For example:
857+
858+
```ts
859+
import { NextResponse } from "next/server"
860+
861+
import { auth0 } from "@/lib/auth0"
862+
863+
export async function GET() {
864+
try {
865+
const token = await auth0.getAccessTokenForConnection({ connection: 'google-oauth2' })
866+
// call external API with token...
867+
} catch (err) {
868+
// err will be an instance of AccessTokenError if an access token could not be obtained
869+
}
870+
871+
return NextResponse.json({
872+
message: "Success!",
873+
})
874+
}
875+
```
876+
877+
Upon further calls for the same provider, the cached value will be used until it expires.
878+
879+
### On the server (Pages Router)
880+
881+
On the server, the `getAccessTokenForConnection({}, req, res)` helper can be used in `getServerSideProps` and API routes to get an access token for a connection, like so:
882+
883+
```ts
884+
import type { NextApiRequest, NextApiResponse } from "next"
885+
886+
import { auth0 } from "@/lib/auth0"
887+
888+
export default async function handler(
889+
req: NextApiRequest,
890+
res: NextApiResponse<{ message: string }>
891+
) {
892+
try {
893+
const token = await auth0.getAccessTokenForConnection({ connection: 'google-oauth2' }, req, res)
894+
} catch (err) {
895+
// err will be an instance of AccessTokenError if an access token could not be obtained
896+
}
897+
898+
res.status(200).json({ message: "Success!" })
899+
}
900+
```
901+
902+
### Middleware
903+
904+
In middleware, the `getAccessTokenForConnection({}, req, res)` helper can be used to get an access token for a connection, like so:
905+
906+
```tsx
907+
import { NextRequest, NextResponse } from "next/server"
908+
909+
import { auth0 } from "@/lib/auth0"
910+
911+
export async function middleware(request: NextRequest) {
912+
const authRes = await auth0.middleware(request)
913+
914+
if (request.nextUrl.pathname.startsWith("/auth")) {
915+
return authRes
916+
}
917+
918+
const session = await auth0.getSession(request)
919+
920+
if (!session) {
921+
// user is not authenticated, redirect to login page
922+
return NextResponse.redirect(new URL("/auth/login", request.nextUrl.origin))
923+
}
924+
925+
const accessToken = await auth0.getAccessTokenForConnection({ connection: 'google-oauth2' }, request, authRes)
926+
927+
// the headers from the auth middleware should always be returned
928+
return authRes
929+
}
930+
```
931+
932+
> [!IMPORTANT]
933+
> The `request` and `response` objects must be passed as a parameters to the `getAccessTokenForConnection({}, request, response)` method when called from a middleware to ensure that the refreshed access token can be accessed within the same request.
934+
935+
If you are using the Pages Router and are calling the `getAccessTokenForConnection` method in both the middleware and an API Route or `getServerSideProps`, it's recommended to propagate the headers from the middleware, as shown below. This will ensure that calling `getAccessTokenForConnection` in the API Route or `getServerSideProps` will not result in the access token being refreshed again.
936+
937+
```ts
938+
import { NextRequest, NextResponse } from "next/server"
939+
940+
import { auth0 } from "@/lib/auth0"
941+
942+
export async function middleware(request: NextRequest) {
943+
const authRes = await auth0.middleware(request)
944+
945+
if (request.nextUrl.pathname.startsWith("/auth")) {
946+
return authRes
947+
}
948+
949+
const session = await auth0.getSession(request)
950+
951+
if (!session) {
952+
// user is not authenticated, redirect to login page
953+
return NextResponse.redirect(new URL("/auth/login", request.nextUrl.origin))
954+
}
955+
956+
const accessToken = await auth0.getAccessTokenForConnection({ connection: 'google-oauth2' }, request, authRes)
957+
958+
// create a new response with the updated request headers
959+
const resWithCombinedHeaders = NextResponse.next({
960+
request: {
961+
headers: request.headers,
962+
},
963+
})
964+
965+
// set the response headers (set-cookie) from the auth response
966+
authRes.headers.forEach((value, key) => {
967+
resWithCombinedHeaders.headers.set(key, value)
968+
})
969+
970+
// the headers from the auth middleware should always be returned
971+
return resWithCombinedHeaders
972+
}
973+
```

examples/with-shadcn/middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { NextRequest } from "next/server"
33
import { auth0 } from "./lib/auth0"
44

55
export async function middleware(request: NextRequest) {
6-
return await auth0.middleware(request)
6+
return await auth0.middleware(request);
77
}
88

99
export const config = {

examples/with-shadcn/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12-
"@auth0/nextjs-auth0": "^4.0.0",
12+
"@auth0/nextjs-auth0": "^4.0.1",
1313
"@radix-ui/react-avatar": "^1.1.1",
1414
"@radix-ui/react-collapsible": "^1.1.1",
1515
"@radix-ui/react-dialog": "^1.1.2",

examples/with-shadcn/pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/errors/index.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,49 @@ export class AccessTokenError extends SdkError {
9898
this.code = code;
9999
}
100100
}
101+
102+
/**
103+
* Enum representing error codes related to access tokens for connections.
104+
*/
105+
export enum AccessTokenForConnectionErrorCode {
106+
/**
107+
* The session is missing.
108+
*/
109+
MISSING_SESSION = "missing_session",
110+
111+
/**
112+
* The refresh token is missing.
113+
*/
114+
MISSING_REFRESH_TOKEN = "missing_refresh_token",
115+
116+
/**
117+
* Failed to exchange the refresh token.
118+
*/
119+
FAILED_TO_EXCHANGE = "failed_to_exchange_refresh_token"
120+
}
121+
122+
/**
123+
* Error class representing an access token for connection error.
124+
* Extends the `SdkError` class.
125+
*/
126+
export class AccessTokenForConnectionError extends SdkError {
127+
/**
128+
* The error code associated with the access token error.
129+
*/
130+
public code: string;
131+
public cause?: OAuth2Error;
132+
133+
/**
134+
* Constructs a new `AccessTokenForConnectionError` instance.
135+
*
136+
* @param code - The error code.
137+
* @param message - The error message.
138+
* @param cause - The OAuth2 cause of the error.
139+
*/
140+
constructor(code: string, message: string, cause?: OAuth2Error) {
141+
super(message);
142+
this.name = "AccessTokenForConnectionError";
143+
this.code = code;
144+
this.cause = cause;
145+
}
146+
}

0 commit comments

Comments
 (0)