diff --git a/apps/examples/nextjs/.env.local.example b/apps/examples/nextjs/.env.local.example index b038ef2eab..b754b2f5a7 100644 --- a/apps/examples/nextjs/.env.local.example +++ b/apps/examples/nextjs/.env.local.example @@ -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 \ No newline at end of file diff --git a/apps/examples/nextjs/app/[...proxy]/route.tsx b/apps/examples/nextjs/app/[...proxy]/route.tsx new file mode 100644 index 0000000000..ae20cd18da --- /dev/null +++ b/apps/examples/nextjs/app/[...proxy]/route.tsx @@ -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 } diff --git a/apps/examples/nextjs/auth.ts b/apps/examples/nextjs/auth.ts index 058baed467..6d465e3bcd 100644 --- a/apps/examples/nextjs/auth.ts +++ b/apps/examples/nextjs/auth.ts @@ -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" @@ -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 + } +} diff --git a/apps/examples/nextjs/components/client-example.tsx b/apps/examples/nextjs/components/client-example.tsx index 686ccd2d86..a1610451c3 100644 --- a/apps/examples/nextjs/components/client-example.tsx +++ b/apps/examples/nextjs/components/client-example.tsx @@ -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 (
@@ -71,6 +82,34 @@ export default function ClientExample() { to provide the session data.

+
+

Third-party backend integration

+

+ Press the button below to send a request to our{" "} + + example backend + + . +

+
+

Note: This example only works when using the Keycloak provider.

+ +
+

+ Read more{" "} + + here + +

+
{apiResponse}
+
+ {status === "loading" ? (
Loading...
) : ( diff --git a/docs/pages/guides/integrating-third-party-backends.mdx b/docs/pages/guides/integrating-third-party-backends.mdx new file mode 100644 index 0000000000..4f4f619f43 --- /dev/null +++ b/docs/pages/guides/integrating-third-party-backends.mdx @@ -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(/*/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).