For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Book a demoLog inStart for free
  • Getting started
    • Overview
    • How it works
    • Quickstart
    • Project structure
    • Customer showcase
    • Changelog
  • Configuration
    • Overview
    • Site-level settings
    • Page-level settings
  • Writing content
    • Markdown basics
    • Rich media in Markdown
    • Fern Editor
    • Reusable snippets
  • AI features
    • Overview
    • Fern Writer
    • AI-generated examples
    • Markdown access
      • Overview
      • Customize LLM output
      • Agent directives
      • Analytics and integration
    • MCP server
    • API catalog discovery
      • Overview
        • Password protection
        • SSO
        • JWT
        • OAuth
  • Public API
    • GETJWT from Fern API key
    • GETAlgolia search credentials
    • GETCurrent user information
  • Fern Writer API
    • GETGet Fern Writer Install Link
Checking status...
SOC2Soc 2 Type II
© 2026 Fern • Birch Solutions, Inc., a Postman company

Documentation

SDKsDocsAsk FernCLI Reference

API Definitions

OpenAPIAsyncAPIOpenRPCgRPC

Resources

BlogSupportPricing

Company

Brand KitPrivacy PolicyTerms of Service
LogoLogo
Book a demoLog inStart for free
On this page
  • How it works
  • Configuration
AuthenticationSetup

Set up JWT

Self-managed authentication integrated with your login system

||View as Markdown|
Was this page helpful?
Edit this page
Previous

Single Sign-On

Next

Set up OAuth

Enterprise feature

This feature is available only for the Enterprise plan. To get started, reach out to support@buildwithfern.com.

With JWT, you manage the entire auth flow. This involves building and signing a fern_token cookie that integrates your docs with your existing login system. Like OAuth, JWT enables:

  • Login only — gate docs behind authentication
  • RBAC — restrict content by user role
  • API key injection — pre-fill API keys in the API Explorer

How it works

  1. A user clicks Login on your docs site and is redirected to your authentication page.
  2. After authentication, your system signs a JWT with a secret key from Fern and sets it as a fern_token cookie.
  3. Fern reads the token to determine the user’s access and credentials.
Architecture diagram

Configuration

1

Get your secret key

Reach out to Fern to get your secret key and send them the URL of your authentication page. This is where users are redirected after clicking Login.

2

Build the fern claim

The JWT payload must include a fern claim. What you include in the token’s fern claim controls which features are enabled: login only, RBAC, or API key injection.

1{
2 "fern": {}
3}
3

Set the fern_token cookie

Add logic to your service to sign the JWT and set it as a fern_token cookie when a user logs in.

Example: Complete callback endpoint

This Next.js endpoint handles the callback from your authentication page. It reads the state parameter to determine where to redirect the user, mints a fern_token JWT using jose, sets it as a cookie, and redirects the user back to the docs.

app/api/fern-token/route.ts
1import { SignJWT } from "jose";
2import { cookies } from "next/headers";
3import { type NextRequest, NextResponse } from "next/server";
4
5export async function GET(req: NextRequest): Promise<NextResponse> {
6 const domain = getDomain(req); // your logic to determine the docs domain
7
8 // use the state param to determine redirect location
9 const returnTo = req.nextUrl.searchParams.get("state");
10 const redirectLocation = returnTo ?? `https://${domain}`;
11
12 // fetch the user's API key, roles, and secret (from your config or database)
13 const apiKey = await getApiKeyForUser();
14 const roles = await getRolesForUser();
15 const secret = await getSecretForDomain(domain);
16
17 if (!secret) {
18 // redirect with an error if credentials are missing
19 const url = new URL(redirectLocation);
20 url.searchParams.set("error", "missing_credentials");
21 return NextResponse.redirect(url);
22 }
23
24 // mint the JWT using the secret key
25 const fernToken = await mintFernToken({ secret, apiKey, roles });
26
27 if (!fernToken) {
28 const url = new URL(redirectLocation);
29 url.searchParams.set("error", "token_creation_failed");
30 return NextResponse.redirect(url);
31 }
32
33 // set the fern_token as a cookie on the docs domain
34 const cookieJar = await cookies();
35 cookieJar.set("fern_token", fernToken, {
36 httpOnly: true,
37 secure: true,
38 sameSite: "lax",
39 domain,
40 });
41
42 // redirect the user back to the docs
43 return NextResponse.redirect(redirectLocation);
44}
45
46const encoder = new TextEncoder();
47
48async function mintFernToken({
49 secret,
50 apiKey,
51 roles,
52}: {
53 secret: string;
54 apiKey?: string;
55 roles?: string[];
56}): Promise<string> {
57 const fern: Record<string, unknown> = {};
58
59 if (roles) {
60 fern.roles = roles;
61 }
62
63 if (apiKey) {
64 fern.playground = {
65 initial_state: {
66 auth: {
67 bearer_token: apiKey,
68 },
69 },
70 };
71 }
72
73 return await new SignJWT({ fern })
74 .setProtectedHeader({ alg: "HS256", typ: "JWT" })
75 .setIssuedAt()
76 .setExpirationTime("1d") // set to any value
77 .setIssuer("https://buildwithfern.com")
78 .sign(encoder.encode(secret)); // sign using the secret provided by Fern
79}
4

Enable RBAC or API key injection (optional)

Once your fern_token is working, configure the features you need:

  • Role-based access control — define roles in docs.yml and restrict navigation items or page content by role.
  • API key injection — configure the playground payload, including custom headers, multiple API keys, and per-environment credentials.