Skip to main content
Custom refresh routes allow you to store authentication material in HttpOnly cookies tied to your domain, providing enhanced security by preventing client-side JavaScript from accessing sensitive tokens.

Why Use Custom Refresh Routes?

Custom refresh routes provide several security benefits:
  • HttpOnly cookies: Refresh tokens are stored in HttpOnly cookies, preventing XSS attacks from accessing them
  • Domain-specific: Cookies are tied to your domain, not a third-party domain
  • Secure transmission: Configure cookies with Secure and SameSite flags for additional protection
  • Centralized control: Manage token refresh logic on your own backend
The JWT token cannot be HttpOnly as it needs to be accessible to the client SDK for API calls. Only the refresh token is stored in an HttpOnly cookie.
Source: README.md:83 Configure cookie options when initializing CrossmintAuth:
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";

const crossmint = createCrossmint({
    apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
});

const crossmintAuth = CrossmintAuth.from(crossmint, {
    cookieOptions: {
        httpOnly: true,
        sameSite: "Strict",
        secure: true,
        domain: ".example.com",
    },
});
Source: README.md:72
httpOnly
boolean
default:"false"
When true, makes the refresh token cookie inaccessible to client-side JavaScript. The JWT cookie will always be accessible to the client SDK.
secure
boolean
default:"false"
When true, cookies are only sent over HTTPS connections. Always set to true in production.
sameSite
'Strict' | 'Lax' | 'None'
default:"'Lax'"
Controls when cookies are sent with cross-site requests:
  • Strict: Cookies are only sent in first-party contexts
  • Lax: Cookies are sent with top-level navigations
  • None: Cookies are sent with all requests (requires secure: true)
domain
string
The domain for which the cookies are valid. Use a leading dot (e.g., .example.com) to include subdomains.
Source: cookies.ts:83

Setting Up a Custom Refresh Route

1

Configure CrossmintAuth with cookie options

Initialize CrossmintAuth with your desired cookie configuration:
const crossmintAuth = CrossmintAuth.from(crossmint, {
    refreshRoute: "/api/refresh",
    cookieOptions: {
        httpOnly: true,
        sameSite: "Strict",
        secure: process.env.NODE_ENV === "production",
    },
});
Source: README.md:106
2

Create the refresh endpoint

Implement the refresh route handler using handleCustomRefresh():
// app/api/refresh/route.ts
import { NextRequest } from "next/server";
import { crossmintAuth } from "@/lib/crossmint";

export async function POST(request: NextRequest) {
    return await crossmintAuth.handleCustomRefresh(request);
}
Source: README.md:92
3

Configure the client SDK

Update your client SDK configuration to use the custom refresh route:
import { CrossmintAuthClient } from "@crossmint/client-sdk-auth";

const authClient = CrossmintAuthClient.from(crossmint, {
    refreshRoute: "/api/refresh",
});

How Custom Refresh Works

When a token needs to be refreshed:
1

Client requests refresh

The client SDK sends a POST request to your custom refresh route.
2

Extract refresh token

The server extracts the refresh token from either the request body or cookies:
const body = await requestAdapter.getBody();
const { refresh: tokenFromBody } = body ?? {};
const authCookies = getAuthCookies(request, { throwError: false });
const tokenFromCookies = authCookies?.refreshToken;
const refreshToken = tokenFromBody ?? tokenFromCookies;
Source: CrossmintAuthServer.ts:89
3

Refresh the token

The server calls the Crossmint API to refresh the token:
const refreshedAuthMaterial = await this.refresh(refreshToken);
4

Store in cookies

The new authentication material is stored in cookies with your configured options:
this.storeAuthMaterial(response, refreshedAuthMaterial);
5

Return response

The response with updated cookies is returned to the client.
Source: CrossmintAuthServer.ts:79

Setting Up a Custom Logout Route

When using HttpOnly cookies, logout must happen server-side:
// app/api/logout/route.ts
import { NextRequest } from "next/server";
import { crossmintAuth } from "@/lib/crossmint";

export async function POST(request: NextRequest) {
    return await crossmintAuth.logout(request);
}
Source: README.md:116

Error Handling

The handleCustomRefresh() method automatically handles errors:
  • Returns a 401 Unauthorized response if the refresh token is invalid
  • Clears authentication cookies on error
  • Logs error details to the console
try {
    const response = await crossmintAuth.handleCustomRefresh(request);
    return response;
} catch (error) {
    // Automatically handled - cookies are cleared and 401 is returned
}
Source: CrossmintAuthServer.ts:113

Complete Example

Here’s a complete example with custom refresh and logout routes:
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";

const crossmint = createCrossmint({
    apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
});

export const crossmintAuth = CrossmintAuth.from(crossmint, {
    refreshRoute: "/api/auth/refresh",
    cookieOptions: {
        httpOnly: true,
        sameSite: "Strict",
        secure: process.env.NODE_ENV === "production",
        domain: process.env.COOKIE_DOMAIN,
    },
});

Best Practices

Set secure: true in production to ensure cookies are only transmitted over HTTPS:
cookieOptions: {
    secure: process.env.NODE_ENV === "production",
}
Use sameSite: "Strict" for maximum protection against CSRF attacks, unless you need cross-site functionality:
cookieOptions: {
    sameSite: "Strict",
}
If your app uses subdomains, set the domain with a leading dot:
cookieOptions: {
    domain: ".example.com", // Works for app.example.com, api.example.com, etc.
}
Always handle refresh errors by redirecting users to login:
try {
    await crossmintAuth.getSession(request, response);
} catch (error) {
    return redirect("/login");
}

Next Steps

Authentication

Learn more about authentication methods

Session Management

Understand session validation and token refresh

Build docs developers (and LLMs) love