Skip to main content

Server-Side Authentication

This guide demonstrates how to implement server-side authentication with Crossmint, including session management, token refresh, and protected routes.

Demo Repositories

Next.js SSR

Complete Next.js server-side auth example

Node.js + Express

Express server with auth middleware

Installation

npm install @crossmint/server-sdk

Environment Variables

# Server-side API key (keep this secret!)
SERVER_CROSSMINT_API_KEY=your_server_api_key

# Optional: Port configuration
PORT=3001
Never expose your server API key in client-side code or commit it to version control.

Node.js Implementation

Basic HTTP Server

Create a basic authentication server with Node.js:
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
import http from "http";
import dotenv from "dotenv";

dotenv.config();

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

const server = http.createServer(async (req, res) => {
    // Handle token refresh
    if (req.method === "POST" && req.url === "/refresh") {
        try {
            await crossmintAuth.handleCustomRefresh(req, res);
        } catch (error) {
            console.error("Error refreshing token", error);
        }
        res.end();
        return;
    }

    // Handle logout
    if (req.method === "POST" && req.url === "/logout") {
        try {
            await crossmintAuth.logout(req, res);
        } catch (error) {
            console.error("Error logging out", error);
        }
        res.end();
        return;
    }

    // Protected route - validate session
    try {
        const { jwt, refreshToken, userId } = await crossmintAuth.getSession(req, res);
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ jwt, refreshToken, userId }));
    } catch (error) {
        console.error(error);
        res.writeHead(500, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ error: error.message }));
    }
});

const PORT = process.env.PORT || 3001;

server.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

Express Server

Implement authentication with Express middleware:
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
import express from "express";
import dotenv from "dotenv";

dotenv.config();

const app = express();

// Create Crossmint instances
const crossmint = createCrossmint({
    apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
});
const crossmintAuth = CrossmintAuth.from(crossmint);

app.use(express.json());

// Token refresh endpoint
app.post("/refresh", async (req, res) => {
    try {
        await crossmintAuth.handleCustomRefresh(req, res);
    } catch (error) {
        console.error("Error refreshing token", error);
    }
    res.end();
});

// Logout endpoint
app.post("/logout", async (req, res) => {
    try {
        await crossmintAuth.logout(req, res);
    } catch (error) {
        console.error("Error logging out", error);
    }
    res.end();
});

// Authentication middleware
const authMiddleware = async (req, res, next) => {
    if (req.method !== "GET") {
        next();
        return;
    }

    try {
        const { jwt, userId } = await crossmintAuth.getSession(req, res);
        req.user = { userId, jwt };
        next();
    } catch (error) {
        console.error(error);
        res.status(401).json({ error: "Authentication failed" });
    }
};

app.use(authMiddleware);

// Protected route
app.get("/protected", (req, res) => {
    res.json({ message: "Protected route", userId: req.user.userId });
});

const PORT = process.env.PORT || 3001;

app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

Next.js Implementation

API Routes

Implement authentication API routes in Next.js:
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
import type { NextRequest } from "next/server";

export async function POST(request: NextRequest) {
    try {
        const crossmint = createCrossmint({
            apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
        });
        const crossmintAuth = CrossmintAuth.from(crossmint, {
            cookieOptions: {
                httpOnly: true, // Secure cookies
            },
        });
        const response = await crossmintAuth.handleCustomRefresh(request);

        return response as Response;
    } catch (error) {
        console.error("Refresh error:", error);
        return Response.json(
            { error: "Authentication failed" },
            { status: 401 }
        );
    }
}

Server Components

Access auth session in Next.js Server Components:
import { cookies } from "next/headers";
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";

export async function getAuthSession(refreshRoute?: string) {
    const cookieStore = await cookies();
    const jwt = cookieStore.get("crossmint-jwt")?.value;
    const refreshToken = cookieStore.get("crossmint-refresh-token")?.value;

    if (!refreshToken) {
        return null;
    }

    try {
        const crossmint = createCrossmint({
            apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
        });
        const crossmintAuth = CrossmintAuth.from(crossmint, {
            refreshRoute,
        });

        const session = await crossmintAuth.getSession({
            jwt,
            refreshToken,
        });
        return session;
    } catch (error) {
        console.error("Session error:", error);
        return null;
    }
}

export async function requireAuth(refreshRoute?: string) {
    const session = await getAuthSession(refreshRoute);
    
    if (!session) {
        throw new Error("Authentication required");
    }
    
    return session;
}

Middleware for Protected Routes

Protect multiple routes with middleware:
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";

// Configure which routes need authentication
export const config = {
    matcher: [
        "/dashboard/:path*",
        "/profile/:path*",
        "/settings/:path*",
    ],
};

export async function middleware(request: NextRequest) {
    const jwt = request.cookies.get("crossmint-jwt")?.value;
    const refreshToken = request.cookies.get("crossmint-refresh-token")?.value;

    // Redirect to login if no refresh token
    if (!refreshToken) {
        return NextResponse.redirect(new URL("/login", request.url));
    }

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

        // Validate and refresh session
        const { jwt: newJwt, refreshToken: newRefreshToken } = 
            await crossmintAuth.getSession({
                jwt,
                refreshToken,
            });

        const response = NextResponse.next();

        // Update cookies if tokens changed
        if (newJwt !== jwt) {
            response.cookies.set("crossmint-jwt", newJwt);
        }
        if (newRefreshToken.secret !== refreshToken) {
            response.cookies.set("crossmint-refresh-token", newRefreshToken.secret);
        }

        return response;
    } catch (error) {
        console.error("Auth middleware error:", error);
        
        // Clear invalid cookies and redirect to login
        const response = NextResponse.redirect(new URL("/login", request.url));
        response.cookies.delete("crossmint-jwt");
        response.cookies.delete("crossmint-refresh-token");
        
        return response;
    }
}

Session Management

Get User Session

import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";

const crossmint = createCrossmint({
    apiKey: process.env.SERVER_CROSSMINT_API_KEY,
});
const crossmintAuth = CrossmintAuth.from(crossmint);

// From request/response objects
const session = await crossmintAuth.getSession(req, res);
console.log(session.userId);
console.log(session.jwt);

// Or with explicit tokens
const session = await crossmintAuth.getSession({
    jwt: "...",
    refreshToken: "...",
});

Handle Token Refresh

// Automatic refresh handling
const crossmintAuth = CrossmintAuth.from(crossmint, {
    refreshRoute: "/api/refresh",
});

// The SDK will automatically refresh tokens when they expire
const session = await crossmintAuth.getSession(req, res);
const crossmintAuth = CrossmintAuth.from(crossmint, {
    cookieOptions: {
        httpOnly: true,      // Prevent XSS attacks
        secure: true,        // HTTPS only
        sameSite: "strict",  // CSRF protection
        path: "/",
        maxAge: 60 * 60 * 24 * 7, // 7 days
    },
});

Security Best Practices

Use httpOnly Cookies

Always use httpOnly cookies for storing tokens to prevent XSS attacks.

Validate Sessions

Validate sessions on every protected route or API call.

Secure Your API Keys

Never expose server API keys in client code or version control.

Use HTTPS

Always use HTTPS in production to protect tokens in transit.

Error Handling

try {
    const session = await crossmintAuth.getSession(req, res);
    // Session is valid
} catch (error) {
    if (error.code === "SESSION_EXPIRED") {
        // Token expired, try refresh
        await crossmintAuth.handleCustomRefresh(req, res);
    } else if (error.code === "INVALID_TOKEN") {
        // Invalid token, require re-authentication
        return res.status(401).json({ error: "Please login again" });
    } else {
        // Other error
        console.error("Auth error:", error);
        return res.status(500).json({ error: "Authentication failed" });
    }
}

Next Steps

Next.js Integration

Full Next.js authentication setup

API Reference

Server SDK API reference

Wallet Creation

Add wallets to your auth flow

Authentication Guide

Complete authentication guide

Troubleshooting

  • Verify your server API key is correct
  • Check that cookies are being sent with requests
  • Ensure the refresh token hasn’t expired
Configure your CORS settings to allow credentials:
app.use(cors({
  origin: 'https://yourdomain.com',
  credentials: true
}));
Check your middleware configuration matcher to ensure it includes the routes you want to protect.

Build docs developers (and LLMs) love