Skip to content

docs: add example authenticating against 3rd-party APIs #10761

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f4de400
external api example
falcowinkler Apr 25, 2024
10540f8
external api example only when keycloak is used
falcowinkler Apr 26, 2024
1bc746f
Refactor code, prettify 'Make api request' button and add description
falcowinkler Apr 29, 2024
e3cd4b4
remove superflous comments
falcowinkler Apr 29, 2024
0ba53ef
Merge branch 'nextauthjs:main' into main
falcowinkler Apr 29, 2024
d0b8860
extract RefreshAccessToken
falcowinkler Apr 29, 2024
ddafc2c
extract example backend url
falcowinkler Apr 29, 2024
d6c510f
Point to CNAME for example backend
falcowinkler Apr 29, 2024
bf18f3d
Merge branch 'main' into main
ndom91 Apr 30, 2024
87328ba
fix incorrect expiry time variable in token
falcowinkler Apr 30, 2024
92cd262
chore(docs): cleanup netsuite doc page
ndom91 Apr 30, 2024
8d3bb0b
chore(docs): revert to [email protected]
ndom91 Apr 30, 2024
87a4b31
Add Draft documentation on integrating third party backends.
falcowinkler May 2, 2024
e5437f9
Revert "chore(docs): revert to [email protected]"
falcowinkler May 2, 2024
49ecb6f
Revert "chore(docs): cleanup netsuite doc page"
falcowinkler May 2, 2024
56f6812
Merge remote-tracking branch 'upstream/main'
falcowinkler May 2, 2024
660c5ad
Remove refresh token
falcowinkler May 2, 2024
f0dca6a
Move route to proxy level
falcowinkler May 2, 2024
bc7a85e
Delete apps/examples/nextjs/package-lock.json
balazsorban44 May 2, 2024
d464075
add link to docu
falcowinkler May 2, 2024
227ccda
implement review comments
falcowinkler May 2, 2024
ea24708
remove superflous log
falcowinkler May 2, 2024
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
2 changes: 2 additions & 0 deletions apps/examples/nextjs/.env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ AUTH_GOOGLE_SECRET=
AUTH_TWITTER_ID=
AUTH_TWITTER_SECRET=

# THIRD_PARTY_API_EXAMPLE_BACKEND= # Read more at https://authjs.dev/guides/integrating-third-party-backends

# AUTH_TRUST_HOST=1 # Read more at https://authjs.dev/getting-started/deployment#auth_trust_host
34 changes: 34 additions & 0 deletions apps/examples/nextjs/app/[...proxy]/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { auth } from "@/auth"
import { NextRequest } from "next/server"

// Review if we need this, and why
function stripContentEncoding(result: Response) {
const responseHeaders = new Headers(result.headers)
responseHeaders.delete("content-encoding")

return new Response(result.body, {
status: result.status,
statusText: result.statusText,
headers: responseHeaders,
})
}

export async function handler(request: NextRequest) {
const session = await auth()

const headers = new Headers(request.headers)
headers.set("Authorization", `Bearer ${session?.accessToken}`)

let backendUrl =
process.env.THIRD_PARTY_API_EXAMPLE_BACKEND ??
"https://authjs-third-party-backend.authjs.dev/"

let url = request.nextUrl.href.replace(request.nextUrl.origin, backendUrl)
let result = await fetch(url, { headers, body: request.body })
console.log("fetched", result)
return stripContentEncoding(result)
}

export const dynamic = "force-dynamic"

export { handler as GET, handler as POST }
22 changes: 21 additions & 1 deletion apps/examples/nextjs/auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import NextAuth from "next-auth"
import "next-auth/jwt"

import Apple from "next-auth/providers/apple"
import Auth0 from "next-auth/providers/auth0"
Expand Down Expand Up @@ -76,11 +77,30 @@ export const config = {
if (pathname === "/middleware-example") return !!auth
return true
},
jwt({ token, trigger, session }) {
jwt({ token, trigger, session, account }) {
if (trigger === "update") token.name = session.user.name
if (account?.provider === "keycloak") {
return { ...token, accessToken: account.access_token }
}
return token
},
async session({ session, token }) {
session.accessToken = token.accessToken
return session
},
},
} satisfies NextAuthConfig

export const { handlers, auth, signIn, signOut } = NextAuth(config)

declare module "next-auth" {
interface Session {
accessToken?: string
}
}

declare module "next-auth/jwt" {
interface JWT {
accessToken?: string
}
}
39 changes: 39 additions & 0 deletions apps/examples/nextjs/components/client-example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ const UpdateForm = () => {

export default function ClientExample() {
const { data: session, status } = useSession()
const [apiResponse, setApiResponse] = useState("")

const makeRequestWithToken = async () => {
try {
const response = await fetch("/api/authenticated/greeting")
const data = await response.json()
setApiResponse(JSON.stringify(data, null, 2))
} catch (error) {
setApiResponse("Failed to fetch data: " + error)
}
}

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

<div>
<h2 className="text-xl font-bold">Third-party backend integration</h2>
<p>
Press the button below to send a request to our{" "}
<CustomLink href="https://github.com/nextauthjs/authjs-third-party-backend">
<code>example backend</code>
</CustomLink>
.
</p>
<div className="flex flex-col ">
<p>Note: This example only works when using the Keycloak provider.</p>
<Button
disabled={!session?.accessToken}
className="mt-4 mb-4"
onClick={makeRequestWithToken}
>
Make API Request
</Button>
</div>
<p>
Read more{" "}
<CustomLink href="https://authjs.dev/guides/integrating-third-party-backends">
<code>here</code>
</CustomLink>
</p>
<pre>{apiResponse}</pre>
</div>

{status === "loading" ? (
<div>Loading...</div>
) : (
Expand Down
77 changes: 77 additions & 0 deletions docs/pages/guides/integrating-third-party-backends.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Integrating with third-party backends

When logging in through a provider, you can use the received OAuth tokens to authenticate against a third-party API.
These tokens can be used to authorize requests to backends that are supporting the corresponding provider.

For example:

- GitHub's `access_token` will give you access to GitHub's APIs.
- Self-managed providers (like Keycloak) can be used to authorize against custom third-party backends.

## Storing the token in the session

The token(s) are made availale in the `account` parameter of the jwt callback.
To store them in the session, they can be attached to the token first.

```typescript
jwt({ token, trigger, session, account }) {
if (account?.provider === "my-provider") {
return { ...token, accessToken: account.access_token }
}
// ...
}
```

In order to access the token when making API requests, it needs to be made available to the Auth.js session.

```typescript
async session({ session, token }) {
session.accessToken = token.accessToken
return session
}
```

## Using the token to make authorized API requests

OAuth tokens are commonly attached as `Authorization: Bearer <>` header.
It is recommended to attach this header server side, like a [Route Handler](https://nextjs.org/docs/app/building-your-application/routing/route-handlers).

```typescript
export async function handler(request: NextRequest) {
const session = await auth()
return await fetch(/*<your-backend-url>/api/authenticated/greeting*/, {
headers: { "Authorization": `Bearer ${session?.accessToken}` }
})
// ...
}
```

## Configuring the backend to authorize requests through your provider

Consult your backend framework's documentation on how to verify incoming access tokens.
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.

```javascript
const app = express()
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri:
"https://keycloak.authjs.dev/realms/master/protocol/openid-connect/certs",
}),
issuer: "https://keycloak.authjs.dev/realms/master",
algorithms: ["RS256"],
})
app.get("*", jwtCheck, (req, res) => {
const name = req.auth?.name ?? "unknown name"
res.json({ greeting: `Hello, ${name}!` })
})
// ...
```

## Resources

- Further examples for different backend frameworks can be found [here](https://github.com/nextauthjs/authjs-third-party-backend/tree/main).
- 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).