Authentication

View as Markdown
Enterprise feature

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

Self-hosted Fern documentation supports authentication via environment variables set in your Dockerfile. When no auth environment variables are configured, your docs are fully public.

Two authentication modes are available:

  • Password authentication - Simple shared-password protection
  • Basic token verification - JWT-based authentication with a custom login flow

Password authentication

Password authentication protects your docs behind a simple password prompt. Users must enter the correct password to view the documentation.

Add the following environment variables to your Dockerfile:

1FROM fernapi/fern-self-hosted:<latest-tag>
2
3COPY fern/ /fern/
4
5ENV FERN_AUTH_TYPE="password"
6ENV FERN_AUTH_SECRET="<YOUR PASSWORD>"
7
8RUN fern-generate

Replace <YOUR PASSWORD> with the password users will enter to access the docs.

When a user visits the documentation, they are redirected to a login page where they must enter the password. After entering the correct password, they can browse the docs freely.

Basic token verification

Basic token verification uses JWTs (JSON Web Tokens) to authenticate users. This is useful when you want to integrate your docs with an existing authentication system, such as your own login portal.

How it works

  1. An unauthenticated user visits the docs and is redirected to your login page (FERN_AUTH_REDIRECT).
  2. Your login page authenticates the user (e.g., checking credentials against your database).
  3. After successful authentication, your server creates a signed JWT using the shared secret.
  4. Your server sends the user to the Fern callback endpoint (/api/fern-docs/auth/jwt/callback) with the JWT. This can be done via a GET redirect with the token as a query parameter, or via a POST request with the token in an application/x-www-form-urlencoded body.
  5. The Fern docs container verifies the JWT signature and issuer, sets a session cookie, and redirects the user to the docs.

Configuration

Add the following environment variables to your Dockerfile:

1FROM fernapi/fern-self-hosted:<latest-tag>
2
3COPY fern/ /fern/
4
5ENV FERN_AUTH_TYPE="basic_token_verification"
6ENV FERN_AUTH_SECRET="my-test-secret-at-least-32-chars-long"
7ENV FERN_AUTH_ISSUER="https://my-test-issuer"
8ENV FERN_AUTH_REDIRECT="https://your-login-page.com/login"
9
10RUN fern-generate
VariableDescription
FERN_AUTH_TYPEMust be basic_token_verification
FERN_AUTH_SECRETThe shared secret used to sign and verify JWTs. Must be at least 32 characters long.
FERN_AUTH_ISSUERThe issuer claim (iss) in the JWT. Must match between your signing server and the Fern container.
FERN_AUTH_REDIRECTThe URL where unauthenticated users are redirected to log in.

Building your login server

Your login server is responsible for authenticating users and redirecting them back to the docs with a signed JWT.

When the Fern container redirects an unauthenticated user, it appends the following query parameters to FERN_AUTH_REDIRECT:

  • redirect_uri - The callback URL on the Fern docs container (e.g., https://docs.example.com/api/fern-docs/auth/jwt/callback)
  • state - The page the user was trying to access

Your server must:

  1. Authenticate the user.
  2. Create a JWT signed with the same FERN_AUTH_SECRET using the HS256 algorithm.
  3. Send the user to the redirect_uri with the JWT as fern_token and the original state as the return-to path. You can use either method:
    • GET redirect: Append fern_token and state as query parameters.
    • POST form submission: Submit fern_token and state as application/x-www-form-urlencoded fields. POST avoids exposing the token in URLs and server logs.

The JWT payload must include the following claims:

ClaimDescription
fernAn empty object {} (required by the Fern verifier)
issThe issuer, must match FERN_AUTH_ISSUER
iatIssued-at timestamp (seconds since epoch)
expExpiration timestamp (seconds since epoch)

Here is an example Node.js (Express) server that signs a JWT and redirects to the callback:

1const express = require("express");
2const crypto = require("crypto");
3
4const app = express();
5
6const SECRET = "my-test-secret-at-least-32-chars-long";
7const ISSUER = "https://my-test-issuer";
8
9function base64url(input) {
10 const buf = Buffer.isBuffer(input) ? input : Buffer.from(input, "utf8");
11 return buf.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
12}
13
14function createFernJWT(secret, issuer) {
15 const header = { alg: "HS256", typ: "JWT" };
16 const now = Math.floor(Date.now() / 1000);
17 const payload = {
18 fern: {},
19 iat: now,
20 exp: now + 30 * 24 * 60 * 60, // 30 days
21 iss: issuer,
22 };
23
24 const headerB64 = base64url(JSON.stringify(header));
25 const payloadB64 = base64url(JSON.stringify(payload));
26 const signature = crypto
27 .createHmac("sha256", secret)
28 .update(`${headerB64}.${payloadB64}`)
29 .digest();
30
31 return `${headerB64}.${payloadB64}.${base64url(signature)}`;
32}
33
34app.get("/login", (req, res) => {
35 const redirectUri = req.query.redirect_uri;
36 const state = req.query.state || "/";
37
38 // TODO: Add your own authentication logic here
39 // (e.g., check session, verify credentials, show a login form, etc.)
40
41 const token = createFernJWT(SECRET, ISSUER);
42
43 const callbackUrl = new URL(redirectUri);
44 callbackUrl.searchParams.set("fern_token", token);
45 callbackUrl.searchParams.set("state", state);
46
47 res.redirect(callbackUrl.toString());
48});
49
50app.listen(3001, () => {
51 console.log("Login server running on http://localhost:3001");
52});

The FERN_AUTH_SECRET in your login server must exactly match the secret set in the Dockerfile. If they differ, JWT verification will fail and users will not be able to log in.

Testing with the built-in test login page

The self-hosted container includes a built-in test login page that you can enable for development and testing. This lets you verify the authentication flow without building your own login server.

Add the following environment variables to your Dockerfile:

1FROM fernapi/fern-self-hosted:<latest-tag>
2
3COPY fern/ /fern/
4
5ENV FERN_AUTH_TYPE="basic_token_verification"
6ENV FERN_AUTH_SECRET="my-test-secret-at-least-32-chars-long"
7ENV FERN_AUTH_ISSUER="https://my-test-issuer"
8ENV FERN_AUTH_REDIRECT="http://localhost:3000/__test-login"
9ENV FERN_AUTH_TEST_LOGIN="true"
10
11RUN fern-generate

Setting FERN_AUTH_TEST_LOGIN="true" enables the /__test-login endpoint on the container. When FERN_AUTH_REDIRECT points to this endpoint, unauthenticated users see a test login page with a single “Login with Test” button. Clicking the button mints a valid JWT and completes the authentication flow.

The test login page is intended for development and testing only. Do not enable FERN_AUTH_TEST_LOGIN in production environments.

Page-level access control

By default, all pages require authentication when auth is enabled. Use FERN_AUTH_ALLOWLIST and FERN_AUTH_DENYLIST to control which pages require login. Both accept comma-separated regex patterns matched against page paths.

VariableDescription
FERN_AUTH_ALLOWLISTPages matching these patterns are publicly accessible without login.
FERN_AUTH_DENYLISTPages matching these patterns require login. Takes precedence over the allowlist.

For example, to make all pages publicly accessible:

1ENV FERN_AUTH_ALLOWLIST="/(.*)"

To make only API Reference pages require login:

1ENV FERN_AUTH_DENYLIST="/api-reference/(.*)"

API key injection

You can enable API key injection in the API Explorer for self-hosted deployments. This shows a Login button in the API Explorer so users can authenticate and have their API keys auto-populated, without requiring login for the entire documentation site.

Add FERN_API_KEY_INJECTION_ENABLED to your Dockerfile alongside the basic token verification variables:

1FROM fernapi/fern-self-hosted:<latest-tag>
2
3COPY fern/ /fern/
4
5ENV FERN_AUTH_TYPE="basic_token_verification"
6ENV FERN_AUTH_SECRET="my-test-secret-at-least-32-chars-long"
7ENV FERN_AUTH_ISSUER="https://my-test-issuer"
8ENV FERN_AUTH_REDIRECT="https://your-login-page.com/login"
9ENV FERN_API_KEY_INJECTION_ENABLED="true"
10ENV FERN_AUTH_ALLOWLIST="/(.*)"
11
12RUN fern-generate

With FERN_AUTH_ALLOWLIST="/(.*)", all doc pages are publicly accessible (no login wall), but the API Explorer still shows the Login button. When a user logs in, their JWT’s fern payload is read and the API key is injected into the API Explorer. See Autopopulate API keys for the full fern payload reference.