Skip to main content
The @ave-id/sdk/server module provides server-side helpers for exchanging authorization codes and refresh tokens using your client secret. These functions should only be called from your backend to keep your secret secure.
Never use server helpers in client-side code. Your clientSecret must remain confidential.

Import Server Helpers

import {
  exchangeCodeServer,
  refreshTokenServer,
  exchangeDelegatedTokenServer,
} from "@ave-id/sdk/server";

Why Use Server Helpers?

Server-side token exchange is more secure than client-side exchange because:
  1. Client secret protection: Secrets never leave your backend
  2. Token security: Tokens can be stored in HTTP-only cookies
  3. Better control: You can validate and log token exchanges

Quick Start

Exchange an authorization code for tokens on your server:
import { exchangeCodeServer } from "@ave-id/sdk/server";

const tokens = await exchangeCodeServer(
  {
    clientId: "YOUR_CLIENT_ID",
    clientSecret: process.env.AVE_CLIENT_SECRET!,
    redirectUri: "https://yourapp.com/callback",
  },
  { code: "CODE_FROM_CALLBACK" }
);

console.log(tokens.access_token);

API Reference

exchangeCodeServer

Exchanges an authorization code for access tokens using client credentials.
function exchangeCodeServer(
  config: ServerConfig,
  payload: { code: string }
): Promise<TokenResponse>
config
ServerConfig
required
Server configuration object
payload.code
string
required
Authorization code from the OAuth callback
Returns: TokenResponse object
access_token
string
Opaque access token
access_token_jwt
string
JWT version of the access token
id_token
string
OIDC ID token (if openid scope requested)
refresh_token
string
Refresh token (if offline_access scope requested)
expires_in
number
Token lifetime in seconds
scope
string
Granted scopes (space-separated)
Example:
// app/api/auth/callback/route.ts
import { NextRequest, NextResponse } from "next/server";
import { exchangeCodeServer } from "@ave-id/sdk/server";

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const code = searchParams.get("code");

  if (!code) {
    return NextResponse.json({ error: "Missing code" }, { status: 400 });
  }

  try {
    const tokens = await exchangeCodeServer(
      {
        clientId: process.env.AVE_CLIENT_ID!,
        clientSecret: process.env.AVE_CLIENT_SECRET!,
        redirectUri: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback`,
      },
      { code }
    );

    // Store tokens in HTTP-only cookie
    const response = NextResponse.redirect(
      new URL("/dashboard", request.url)
    );
    response.cookies.set("ave_access_token", tokens.access_token, {
      httpOnly: true,
      secure: true,
      sameSite: "lax",
      maxAge: tokens.expires_in,
    });

    if (tokens.refresh_token) {
      response.cookies.set("ave_refresh_token", tokens.refresh_token, {
        httpOnly: true,
        secure: true,
        sameSite: "lax",
        maxAge: 60 * 60 * 24 * 30, // 30 days
      });
    }

    return response;
  } catch (error) {
    return NextResponse.json(
      { error: "Token exchange failed" },
      { status: 500 }
    );
  }
}

refreshTokenServer

Exchanges a refresh token for new access tokens using client credentials.
function refreshTokenServer(
  config: ServerConfig,
  payload: { refreshToken: string }
): Promise<TokenResponse>
config
ServerConfig
required
Server configuration (same as exchangeCodeServer)
payload.refreshToken
string
required
The refresh token from a previous token response
Returns: TokenResponse with fresh tokens Example:
import { refreshTokenServer } from "@ave-id/sdk/server";

// Middleware to refresh expired tokens
app.use(async (req, res, next) => {
  const accessToken = req.cookies.ave_access_token;
  const refreshToken = req.cookies.ave_refresh_token;

  // Check if access token is expired (you should decode and check exp claim)
  const isExpired = checkIfExpired(accessToken);

  if (isExpired && refreshToken) {
    try {
      const newTokens = await refreshTokenServer(
        {
          clientId: process.env.AVE_CLIENT_ID!,
          clientSecret: process.env.AVE_CLIENT_SECRET!,
          redirectUri: "http://localhost:3000/auth/callback",
        },
        { refreshToken }
      );

      // Update cookies
      res.cookie("ave_access_token", newTokens.access_token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
        maxAge: newTokens.expires_in * 1000,
      });
    } catch (error) {
      // Refresh failed, redirect to login
      return res.redirect("/login");
    }
  }

  next();
});

exchangeDelegatedTokenServer

Exchanges a user’s access token for a delegated token to access third-party resources.
function exchangeDelegatedTokenServer(
  config: ServerConfig,
  payload: {
    subjectToken: string;
    requestedResource: string;
    requestedScope: string;
    actor?: Record<string, unknown>;
  }
): Promise<DelegationTokenResponse>
config
ServerConfig
required
Server configuration
payload
object
required
Returns: DelegationTokenResponse
access_token
string
Delegated access token for the target resource
token_type
'Bearer'
Always Bearer
expires_in
number
Token lifetime in seconds
scope
string
Granted scopes for the resource
audience
string
Token audience
target_resource
string
The resource identifier
communication_mode
'user_present' | 'background'
Delegation communication mode
Example:
import { exchangeDelegatedTokenServer } from "@ave-id/sdk/server";

// API endpoint to get user's GitHub repos
app.get("/api/github/repos", async (req, res) => {
  const userToken = req.cookies.ave_access_token;

  try {
    // Get delegated GitHub token
    const delegation = await exchangeDelegatedTokenServer(
      {
        clientId: process.env.AVE_CLIENT_ID!,
        clientSecret: process.env.AVE_CLIENT_SECRET!,
        redirectUri: "http://localhost:3000/auth/callback",
      },
      {
        subjectToken: userToken,
        requestedResource: "github.com",
        requestedScope: "repo",
      }
    );

    // Use delegated token to call GitHub API
    const githubResponse = await fetch("https://api.github.com/user/repos", {
      headers: {
        Authorization: `Bearer ${delegation.access_token}`,
      },
    });

    const repos = await githubResponse.json();
    res.json(repos);
  } catch (error) {
    res.status(500).json({ error: "Failed to fetch GitHub repos" });
  }
});

Environment Variables

Store your Ave credentials in environment variables:
# .env
AVE_CLIENT_ID=your_client_id
AVE_CLIENT_SECRET=your_client_secret_here
NEXT_PUBLIC_APP_URL=https://yourapp.com
Never commit your .env file to version control. Add it to .gitignore.

Security Best Practices

Use HTTP-only, secure cookies to store tokens:
res.cookie("ave_access_token", tokens.access_token, {
  httpOnly: true,  // Prevent JavaScript access
  secure: true,    // HTTPS only
  sameSite: "lax", // CSRF protection
  maxAge: tokens.expires_in * 1000,
});
Always validate that callback URLs match your configured redirect URIs:
const allowedRedirects = [
  "https://yourapp.com/auth/callback",
  "https://staging.yourapp.com/auth/callback",
];

if (!allowedRedirects.includes(redirectUri)) {
  throw new Error("Invalid redirect URI");
}
Use refresh tokens to rotate access tokens before they expire:
// Check token expiry before API calls
if (isTokenExpired(accessToken) && refreshToken) {
  const newTokens = await refreshTokenServer(config, { refreshToken });
  // Update stored tokens
}
Implement rate limiting to prevent abuse:
import rateLimit from "express-rate-limit";

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 requests per window
});

app.get("/auth/callback", authLimiter, async (req, res) => {
  // Handle callback
});

See Also

OAuth Flow

Learn about the OAuth protocol

Client Helpers

Initiate login flows from the browser

Build docs developers (and LLMs) love