GitHub

Auth (Auth0)

Pulse integrates with Auth0 — and any OAuth 2.0 provider — using plain HTTP redirects and a token exchange. No client-side auth SDK is required. Protected routes enforce access through guard, which runs before any data fetcher can execute.

No Auth0 SDK required. The OAuth flow is plain HTTP redirects and a token exchange fetch — no client-side library needed.

Setup

Register your application in Auth0 and note your credentials. Store them in environment variables — never hardcode them in specs.

# .env
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_CLIENT_ID=your_client_id
AUTH0_CLIENT_SECRET=your_client_secret
AUTH0_CALLBACK_URL=http://localhost:3000/auth/callback

Login route

The login route is a raw response spec that redirects the browser to Auth0's authorization endpoint.

// src/pages/auth/login.js
const {
  AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CALLBACK_URL
} = process.env

export default {
  route: '/auth/login',
  contentType: 'text/html',

  render: (ctx) => {
    const params = new URLSearchParams({
      response_type: 'code',
      client_id:     AUTH0_CLIENT_ID,
      redirect_uri:  AUTH0_CALLBACK_URL,
      scope:         'openid profile email',
      state:         crypto.randomUUID(),
    })
    ctx.setHeader('Location', `https://${AUTH0_DOMAIN}/authorize?${params}`)
    return { redirect: `https://${AUTH0_DOMAIN}/authorize?${params}` }
  },
}

Callback route

Auth0 redirects back to /auth/callback with a code query parameter. The server exchanges it for tokens, sets a session cookie, and redirects to the app.

// src/pages/auth/callback.js
const {
  AUTH0_DOMAIN, AUTH0_CLIENT_ID,
  AUTH0_CLIENT_SECRET, AUTH0_CALLBACK_URL
} = process.env

export default {
  route: '/auth/callback',
  contentType: 'text/html',

  server: {
    session: async (ctx) => {
      const { code } = ctx.query
      if (!code) return null

      // Exchange auth code for tokens
      const res = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          grant_type:    'authorization_code',
          client_id:     AUTH0_CLIENT_ID,
          client_secret: AUTH0_CLIENT_SECRET,
          redirect_uri:  AUTH0_CALLBACK_URL,
          code,
        }),
      })

      if (!res.ok) return null
      const { access_token, id_token } = await res.json()

      // Set a secure session cookie with the access token
      ctx.setCookie('session', access_token, {
        httpOnly: true,
        secure:   process.env.NODE_ENV === 'production',
        sameSite: 'Lax',
        maxAge:   86400, // 24 hours
      })

      return access_token
    },
  },

  render: (ctx, server) => {
    if (!server.session) return { redirect: '/auth/login' }
    return { redirect: '/' }
  },
}

Logout route

Clear the session cookie and redirect to Auth0's logout endpoint to invalidate the session there too.

// src/pages/auth/logout.js
const { AUTH0_DOMAIN, AUTH0_CLIENT_ID } = process.env

export default {
  route: '/auth/logout',
  contentType: 'text/html',

  render: (ctx) => {
    // Expire the session cookie
    ctx.setCookie('session', '', { maxAge: 0 })

    const returnTo = encodeURIComponent('http://localhost:3000')
    return { redirect: `https://${AUTH0_DOMAIN}/v2/logout?client_id=${AUTH0_CLIENT_ID}&returnTo=${returnTo}` }
  },
}

Protecting routes

Use guard to verify the session token before any server data is fetched. For production, verify the JWT signature locally rather than calling Auth0 on every request.

// src/pages/dashboard.js
export default {
  route: '/dashboard',

  guard: async (ctx) => {
    if (!ctx.cookies.session) return { redirect: '/auth/login' }

    // Optional: verify JWT signature for production
    // const user = await verifyJwt(ctx.cookies.session)
    // if (!user) return { redirect: '/auth/login' }
  },

  server: {
    profile: async (ctx) => fetchUserProfile(ctx.cookies.session),
  },

  state: {},
  view: (state, server) => `
    <main id="main-content">
      <h1>Welcome, ${server.profile.name}</h1>
    </main>
  `,
}
Guard runs before server data fetchers — if the session is invalid the profile fetch never happens.

ctx reference

MethodDescription
ctx.cookies.sessionRead the session cookie set during OAuth callback
ctx.setCookie(name, value, opts)Set a response cookie — used in callback and logout routes
ctx.setHeader(name, value)Set an arbitrary response header
setCookie optionTypeDescription
httpOnlybooleanPrevents JS access — always use for session cookies
securebooleanHTTPS only — set true in production
sameSite"Lax" | "Strict" | "None"Lax works for most OAuth flows
maxAgenumberLifetime in seconds — omit for session cookie
pathstringDefaults to /
domainstringScope to a domain — omit for current host