Skip to content

Commit e4eb63a

Browse files
falcowinklerndom91balazsorban44
authored
docs: add example authenticating against 3rd-party APIs (#10761)
* external api example * external api example only when keycloak is used * Refactor code, prettify 'Make api request' button and add description * remove superflous comments * extract RefreshAccessToken * extract example backend url * Point to CNAME for example backend * fix incorrect expiry time variable in token * chore(docs): cleanup netsuite doc page * chore(docs): revert to [email protected] * Add Draft documentation on integrating third party backends. * Revert "chore(docs): revert to [email protected]" This reverts commit 8d3bb0b. * Revert "chore(docs): cleanup netsuite doc page" This reverts commit 92cd262. * Remove refresh token * Move route to proxy level * Delete apps/examples/nextjs/package-lock.json * add link to docu * implement review comments * remove superflous log --------- Co-authored-by: Nico Domino <[email protected]> Co-authored-by: Balázs Orbán <[email protected]>
1 parent 03f4a17 commit e4eb63a

File tree

5 files changed

+173
-1
lines changed

5 files changed

+173
-1
lines changed

apps/examples/nextjs/.env.local.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ AUTH_GOOGLE_SECRET=
1616
AUTH_TWITTER_ID=
1717
AUTH_TWITTER_SECRET=
1818

19+
# THIRD_PARTY_API_EXAMPLE_BACKEND= # Read more at https://authjs.dev/guides/integrating-third-party-backends
20+
1921
# AUTH_TRUST_HOST=1 # Read more at https://authjs.dev/getting-started/deployment#auth_trust_host
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { auth } from "@/auth"
2+
import { NextRequest } from "next/server"
3+
4+
// Review if we need this, and why
5+
function stripContentEncoding(result: Response) {
6+
const responseHeaders = new Headers(result.headers)
7+
responseHeaders.delete("content-encoding")
8+
9+
return new Response(result.body, {
10+
status: result.status,
11+
statusText: result.statusText,
12+
headers: responseHeaders,
13+
})
14+
}
15+
16+
export async function handler(request: NextRequest) {
17+
const session = await auth()
18+
19+
const headers = new Headers(request.headers)
20+
headers.set("Authorization", `Bearer ${session?.accessToken}`)
21+
22+
let backendUrl =
23+
process.env.THIRD_PARTY_API_EXAMPLE_BACKEND ??
24+
"https://authjs-third-party-backend.authjs.dev/"
25+
26+
let url = request.nextUrl.href.replace(request.nextUrl.origin, backendUrl)
27+
let result = await fetch(url, { headers, body: request.body })
28+
console.log("fetched", result)
29+
return stripContentEncoding(result)
30+
}
31+
32+
export const dynamic = "force-dynamic"
33+
34+
export { handler as GET, handler as POST }

apps/examples/nextjs/auth.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import NextAuth from "next-auth"
2+
import "next-auth/jwt"
23

34
import Apple from "next-auth/providers/apple"
45
import Auth0 from "next-auth/providers/auth0"
@@ -76,11 +77,30 @@ export const config = {
7677
if (pathname === "/middleware-example") return !!auth
7778
return true
7879
},
79-
jwt({ token, trigger, session }) {
80+
jwt({ token, trigger, session, account }) {
8081
if (trigger === "update") token.name = session.user.name
82+
if (account?.provider === "keycloak") {
83+
return { ...token, accessToken: account.access_token }
84+
}
8185
return token
8286
},
87+
async session({ session, token }) {
88+
session.accessToken = token.accessToken
89+
return session
90+
},
8391
},
8492
} satisfies NextAuthConfig
8593

8694
export const { handlers, auth, signIn, signOut } = NextAuth(config)
95+
96+
declare module "next-auth" {
97+
interface Session {
98+
accessToken?: string
99+
}
100+
}
101+
102+
declare module "next-auth/jwt" {
103+
interface JWT {
104+
accessToken?: string
105+
}
106+
}

apps/examples/nextjs/components/client-example.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ const UpdateForm = () => {
4343

4444
export default function ClientExample() {
4545
const { data: session, status } = useSession()
46+
const [apiResponse, setApiResponse] = useState("")
47+
48+
const makeRequestWithToken = async () => {
49+
try {
50+
const response = await fetch("/api/authenticated/greeting")
51+
const data = await response.json()
52+
setApiResponse(JSON.stringify(data, null, 2))
53+
} catch (error) {
54+
setApiResponse("Failed to fetch data: " + error)
55+
}
56+
}
4657

4758
return (
4859
<div className="flex flex-col gap-4">
@@ -71,6 +82,34 @@ export default function ClientExample() {
7182
to provide the session data.
7283
</p>
7384

85+
<div>
86+
<h2 className="text-xl font-bold">Third-party backend integration</h2>
87+
<p>
88+
Press the button below to send a request to our{" "}
89+
<CustomLink href="https://github.com/nextauthjs/authjs-third-party-backend">
90+
<code>example backend</code>
91+
</CustomLink>
92+
.
93+
</p>
94+
<div className="flex flex-col ">
95+
<p>Note: This example only works when using the Keycloak provider.</p>
96+
<Button
97+
disabled={!session?.accessToken}
98+
className="mt-4 mb-4"
99+
onClick={makeRequestWithToken}
100+
>
101+
Make API Request
102+
</Button>
103+
</div>
104+
<p>
105+
Read more{" "}
106+
<CustomLink href="https://authjs.dev/guides/integrating-third-party-backends">
107+
<code>here</code>
108+
</CustomLink>
109+
</p>
110+
<pre>{apiResponse}</pre>
111+
</div>
112+
74113
{status === "loading" ? (
75114
<div>Loading...</div>
76115
) : (
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Integrating with third-party backends
2+
3+
When logging in through a provider, you can use the received OAuth tokens to authenticate against a third-party API.
4+
These tokens can be used to authorize requests to backends that are supporting the corresponding provider.
5+
6+
For example:
7+
8+
- GitHub's `access_token` will give you access to GitHub's APIs.
9+
- Self-managed providers (like Keycloak) can be used to authorize against custom third-party backends.
10+
11+
## Storing the token in the session
12+
13+
The token(s) are made availale in the `account` parameter of the jwt callback.
14+
To store them in the session, they can be attached to the token first.
15+
16+
```typescript
17+
jwt({ token, trigger, session, account }) {
18+
if (account?.provider === "my-provider") {
19+
return { ...token, accessToken: account.access_token }
20+
}
21+
// ...
22+
}
23+
```
24+
25+
In order to access the token when making API requests, it needs to be made available to the Auth.js session.
26+
27+
```typescript
28+
async session({ session, token }) {
29+
session.accessToken = token.accessToken
30+
return session
31+
}
32+
```
33+
34+
## Using the token to make authorized API requests
35+
36+
OAuth tokens are commonly attached as `Authorization: Bearer <>` header.
37+
It is recommended to attach this header server side, like a [Route Handler](https://nextjs.org/docs/app/building-your-application/routing/route-handlers).
38+
39+
```typescript
40+
export async function handler(request: NextRequest) {
41+
const session = await auth()
42+
return await fetch(/*<your-backend-url>/api/authenticated/greeting*/, {
43+
headers: { "Authorization": `Bearer ${session?.accessToken}` }
44+
})
45+
// ...
46+
}
47+
```
48+
49+
## Configuring the backend to authorize requests through your provider
50+
51+
Consult your backend framework's documentation on how to verify incoming access tokens.
52+
Below is an [example](https://github.com/nextauthjs/authjs-third-party-backend/tree/main/backend-express) with Express.js using a [Keycloak](https://providers.authjs.dev/keycloak) instance.
53+
54+
```javascript
55+
const app = express()
56+
const jwtCheck = jwt({
57+
secret: jwks.expressJwtSecret({
58+
cache: true,
59+
rateLimit: true,
60+
jwksRequestsPerMinute: 5,
61+
jwksUri:
62+
"https://keycloak.authjs.dev/realms/master/protocol/openid-connect/certs",
63+
}),
64+
issuer: "https://keycloak.authjs.dev/realms/master",
65+
algorithms: ["RS256"],
66+
})
67+
app.get("*", jwtCheck, (req, res) => {
68+
const name = req.auth?.name ?? "unknown name"
69+
res.json({ greeting: `Hello, ${name}!` })
70+
})
71+
// ...
72+
```
73+
74+
## Resources
75+
76+
- Further examples for different backend frameworks can be found [here](https://github.com/nextauthjs/authjs-third-party-backend/tree/main).
77+
- A full example of how to integrate a client app with a third-party API can be found in the [next-auth-example](https://github.com/nextauthjs/next-auth-example).

0 commit comments

Comments
 (0)