AlphaBlock Auth — Developer Integration Guide

Last updated: April 2026

Overview

AlphaBlock Auth provides authentication and user identity for all AlphaBlock projects via a centralised hub at auth.alphablock.io. Your project does not handle user accounts, passwords, or sessions directly. Instead it redirects users to the hub, receives a signed JWT back, and uses that token to identify the user and determine their access level.

Prerequisites

  1. Your project is registered in the AlphaBlock Auth admin panel
  2. You have been issued a PROJECT_ID (public) and a PROJECT_SECRET (private)
  3. Your callback URL has been added to the project's permitted callbackUrls list
  4. The JWKS endpoint is accessible at auth.alphablock.io/.well-known/jwks.json

Environment Variables

# .env.local

ALPHABLOCK_PROJECT_ID=your_project_id
ALPHABLOCK_PROJECT_SECRET=your_project_secret
ALPHABLOCK_AUTH_URL=https://auth.alphablock.io
ALPHABLOCK_CALLBACK_URL=https://your-project.alphablock.io/auth/callback

Step 1 — Redirect to the Auth Hub

When a user needs to authenticate, redirect them to the AlphaBlock Auth hub.

// lib/auth/getAuthUrl.ts

export function getAuthUrl(): string {
  const base = process.env.ALPHABLOCK_AUTH_URL
  const projectId = process.env.ALPHABLOCK_PROJECT_ID
  const callbackUrl = process.env.ALPHABLOCK_CALLBACK_URL

  const params = new URLSearchParams({
    projectId: projectId!,
    callbackUrl: callbackUrl!,
  })

  return `${base}/authorize?${params.toString()}`
}

Step 2 — Handle the Callback

Create a route handler at /auth/callback that receives the one-time code and exchanges it for a signed JWT.

// app/auth/callback/route.ts

import { NextRequest, NextResponse } from 'next/server'
import { cookies } from 'next/headers'

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url)
  const code = searchParams.get('code')

  if (!code) {
    return NextResponse.redirect(new URL('/login?error=missing_code', request.url))
  }

  const tokenRes = await fetch(`${process.env.ALPHABLOCK_AUTH_URL}/api/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      code,
      projectId: process.env.ALPHABLOCK_PROJECT_ID,
      projectSecret: process.env.ALPHABLOCK_PROJECT_SECRET,
    }),
  })

  if (!tokenRes.ok) {
    return NextResponse.redirect(new URL('/login?error=token_exchange_failed', request.url))
  }

  const { token } = await tokenRes.json()

  const cookieStore = await cookies()
  cookieStore.set('ab_session', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    path: '/',
    maxAge: 60 * 60 * 24 * 7,
  })

  return NextResponse.redirect(new URL('/dashboard', request.url))
}

Step 3 — Verify the JWT

Install jose: npm install jose

// lib/auth/verifyToken.ts

import { createRemoteJWKSet, jwtVerify } from 'jose'

const JWKS = createRemoteJWKSet(
  new URL('https://auth.alphablock.io/.well-known/jwks.json')
)

export type AlphaBlockSession = {
  userId: string
  email: string
  walletAddress: string | null
  discordId: string | null
  pricingTier: 'genesis' | 'founding' | 'early' | 'core' | 'standard' | 'open'
  slotNumber: number
  accessLevel: 'tools' | 'full'
  billingCycle: 'monthly' | 'biannual' | 'lifetime'
  roles: string[]
  tierExpiry: string | null
  issuedBy: string
  iat: number
  exp: number
}

export async function verifyToken(token: string): Promise<AlphaBlockSession | null> {
  try {
    const { payload } = await jwtVerify(token, JWKS, {
      issuer: 'auth.alphablock.io',
    })
    return payload as unknown as AlphaBlockSession
  } catch {
    return null
  }
}

Step 4 — Read the Session

// lib/auth/getSession.ts

import { cookies } from 'next/headers'
import { verifyToken, AlphaBlockSession } from './verifyToken'

export async function getSession(): Promise<AlphaBlockSession | null> {
  const cookieStore = await cookies()
  const token = cookieStore.get('ab_session')?.value

  if (!token) return null

  return verifyToken(token)
}

Step 5 — Protect Routes with Middleware

// middleware.ts  (project root)

import { NextRequest, NextResponse } from 'next/server'
import { verifyToken } from '@/lib/auth/verifyToken'

const PROTECTED_PATHS = ['/dashboard', '/tools', '/settings']
const FULL_ACCESS_PATHS = ['/discord', '/perks', '/support']

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  const isProtected = PROTECTED_PATHS.some(p => pathname.startsWith(p))
  const requiresFull = FULL_ACCESS_PATHS.some(p => pathname.startsWith(p))

  if (!isProtected && !requiresFull) return NextResponse.next()

  const token = request.cookies.get('ab_session')?.value
  if (!token) return NextResponse.redirect(new URL('/login', request.url))

  const session = await verifyToken(token)
  if (!session) {
    const response = NextResponse.redirect(new URL('/login', request.url))
    response.cookies.delete('ab_session')
    return response
  }

  if (requiresFull && session.accessLevel !== 'full') {
    return NextResponse.redirect(new URL('/upgrade', request.url))
  }

  return NextResponse.next()
}

Step 6 — Log Out

// app/auth/logout/route.ts

import { NextRequest, NextResponse } from 'next/server'
import { cookies } from 'next/headers'

export async function GET(request: NextRequest) {
  const cookieStore = await cookies()
  cookieStore.delete('ab_session')
  return NextResponse.redirect(new URL('/login', request.url))
}

Access Control Reference

PropertyTypeUse for
accessLevel'tools' | 'full'Gating Discord, perks, support
pricingTierstringTier-based feature flags
tierExpirystring | nullActive subscription check
billingCyclestringDisplaying subscription info
rolesstring[]Custom role-based access

Check subscription active

export function isSubscriptionActive(session: AlphaBlockSession): boolean {
  if (session.billingCycle === 'lifetime') return true
  if (!session.tierExpiry) return false
  return new Date(session.tierExpiry) > new Date()
}

Check minimum tier

const TIER_RANK: Record<string, number> = {
  genesis: 1, founding: 2, early: 3, core: 4, standard: 5, open: 6,
}

export function meetsMinimumTier(
  session: AlphaBlockSession,
  minimum: string
): boolean {
  return (TIER_RANK[session.pricingTier] ?? 99) <= (TIER_RANK[minimum] ?? 99)
}

Files to Create

  • .env.local
  • lib/auth/getAuthUrl.ts
  • lib/auth/verifyToken.ts
  • lib/auth/getSession.ts
  • app/auth/callback/route.ts
  • app/auth/logout/route.ts
  • middleware.ts

Only dependency: jose. Do not install NextAuth or any other auth library into consuming projects.

Endpoints

EndpointMethodDescription
/authorizeGETBegin auth flow
/api/tokenPOSTExchange code for JWT
/.well-known/jwks.jsonGETPublic key set

POST /api/token

// Request
{
  "code": "one-time code from callback URL",
  "projectId": "your project ID",
  "projectSecret": "your project secret"
}

// Response
{
  "token": "signed JWT string"
}

Error Codes

StatusMeaning
400Missing required fields
401Invalid projectSecret
403Project not active or not registered
410Code expired or already used

Notes for AI Assistants

  • Do not install NextAuth into consuming projects.
  • jose is the only auth dependency.
  • Session cookie is named ab_session, is httpOnly.
  • Always use createRemoteJWKSet pointing to the JWKS endpoint.
  • The callback route must be a Route Handler, not a Server Component.
  • getSession() is async — always await it.
  • tierExpiry is null for lifetime subscribers.
  • accessLevel is the primary access gate.
  • Payment processing is stubbed — do not implement payments in consuming projects.