### Install Dependencies (Bash) Source: https://context7.com/fastly/compute-js-auth/llms.txt Command to install project dependencies using npm, typically run before building or deploying the Fastly Compute@Edge service. ```bash # Install dependencies npm install ``` -------------------------------- ### Local Development with Hot Reload (npm) Source: https://context7.com/fastly/compute-js-auth/llms.txt Starts a local development server with hot-reloading capabilities using an npm script. This is an alternative to using the Fastly CLI directly for local development. ```bash npm run dev ``` -------------------------------- ### Callback URL Configuration Source: https://context7.com/fastly/compute-js-auth/llms.txt Provides examples of how to configure the callback URL for your deployed Fastly Compute service. This URL is used by the Identity Provider (IdP) to redirect users after authentication. ```text # After deployment, add callback URL to your IdP: # https://your-app.edgecompute.app/callback # For local dev: http://127.0.0.1:7676/callback ``` -------------------------------- ### Local Development with Hot Reload (Fastly CLI) Source: https://context7.com/fastly/compute-js-auth/llms.txt Starts a local development server with hot-reloading capabilities using the Fastly CLI. This command watches for file changes and automatically rebuilds and restarts the service. ```bash fastly compute serve --watch ``` -------------------------------- ### Deploy to Fastly Source: https://context7.com/fastly/compute-js-auth/llms.txt Deploys the built Fastly Compute service to the Fastly platform. This command initiates the deployment process and requires initial configuration prompts. ```bash fastly compute publish ``` -------------------------------- ### Build Service (npm) Source: https://context7.com/fastly/compute-js-auth/llms.txt Builds the Fastly Compute service using an npm script. This command compiles the necessary assets and prepares the service for deployment. ```bash npm run build ``` -------------------------------- ### Fastly Service Configuration (fastly.toml) Source: https://context7.com/fastly/compute-js-auth/llms.txt Configures the Fastly Compute@Edge service, including author information, service description, language, build scripts, local server backends (identity provider and origin), and config store contents for JWKS and OpenID configuration. ```toml # fastly.toml authors = ["your-email@example.com"] description = "OAuth 2.0 edge authentication" language = "javascript" manifest_version = 3 name = "compute-js-auth" [scripts] build = "npm run build" post_init = "npm install" [local_server.backends] [local_server.backends.idp] url = "https://accounts.google.com" [local_server.backends.origin] url = "https://your-origin.example.com/" [local_server.config_stores] [local_server.config_stores.compute_js_auth_config] format = "inline-toml" [local_server.config_stores.compute_js_auth_config.contents] jwks = '{"keys":[...]}' openid_configuration = '{"issuer":"https://accounts.google.com",...}' [setup.backends] [setup.backends.idp] address = "accounts.google.com" description = "Identity provider authorization server" [setup.backends.origin] address = "your-origin.example.com" description = "Content or application origin" ``` -------------------------------- ### Load Service Configuration (JavaScript) Source: https://context7.com/fastly/compute-js-auth/llms.txt Loads service configuration, including OAuth credentials and OpenID Connect metadata, from Fastly Config and Secret Stores. It supports loading sensitive values like client secrets from environment variables or the Secret Store. ```javascript import { ConfigStore } from 'fastly:config-store' import { SecretStore } from 'fastly:secret-store' import { env } from 'fastly:env' const CONFIG_STORE_NAME = 'compute_js_auth_config' const SECRET_STORE_NAME = 'compute_js_auth_secrets' const USE_SECRET_STORE = false // Load from environment variables (compile time) or Secret Store const CLIENT_ID = env('CLIENT_ID') const CLIENT_SECRET = env('CLIENT_SECRET') const NONCE_SECRET = env('NONCE_SECRET') export const loadConfig = async () => ({ config: { clientId: USE_SECRET_STORE ? await getSecret('client_id') : CLIENT_ID, clientSecret: USE_SECRET_STORE ? await getSecret('client_secret') : CLIENT_SECRET, introspectAccessToken: false, // Use userinfo endpoint instead of edge validation jwtAccessToken: false, // Set true if access token is JWT format callbackPath: '/callback', codeChallengeMethod: 'S256', stateParameterLength: 10, scope: 'openid', nonceSecret: new TextEncoder().encode( USE_SECRET_STORE ? await getSecret('nonce_secret') : NONCE_SECRET ) }, jwks: loadJsonFromConfigStore('jwks'), openidConfiguration: loadJsonFromConfigStore('openid_configuration') }) const loadJsonFromConfigStore = key => { const store = new ConfigStore(CONFIG_STORE_NAME) return JSON.parse(store.get(key)) } export const getSecret = async key => { const secrets = new SecretStore(SECRET_STORE_NAME) const secret = await secrets.get(key) return secret?.plaintext() } ``` -------------------------------- ### Environment Variable Configuration (.env file) Source: https://context7.com/fastly/compute-js-auth/llms.txt Defines environment variables for configuring the OAuth client ID, client secret, and nonce secret. These variables are essential for local development and deployment, allowing customization of the identity provider and security parameters. ```bash # .env file CLIENT_ID=YOUR_IDP_CLIENT_ID CLIENT_SECRET=YOUR_OPTIONAL_CLIENT_SECRET NONCE_SECRET=ARandomStringOfYourChoosingThatsHardToGuess # Generate a secure nonce secret: # dd if=/dev/random bs=32 count=1 | base64 ``` -------------------------------- ### Handle Incoming Requests and OAuth Flow in JavaScript Source: https://context7.com/fastly/compute-js-auth/llms.txt The main request handler intercepts incoming requests, manages the OAuth flow state, validates JWT ID tokens using JWKS, and proxies authenticated requests to the origin backend. It generates PKCE challenges and redirects users to the identity provider for authentication. Dependencies include @fastly/js-compute, pkce-challenge, and local utility modules for cookies, configuration, responses, and JWT handling. It returns a temporary redirect to the IdP or a proxied request to the origin. ```javascript /// import { env } from 'fastly:env' import pkceChallenge from 'pkce-challenge' import * as cookie from './lib/cookie' import * as config from './lib/config' import * as responses from './lib/responses' import * as util from './lib/util' import * as jwt from './lib/jwt' addEventListener('fetch', event => event.respondWith(handleRequest(event))) async function handleRequest(event) { const req = event.request const url = new URL(req.url) const cookies = cookie.parse(req.headers.get('cookie')) const settings = await config.loadConfig() // Check for existing valid tokens if (cookies.access_token && cookies.id_token) { // Validate JWT ID token await jwt.validateToken(settings.jwks, cookies.id_token, { issuer: settings.openidConfiguration.issuer, audience: settings.config.clientId }) // Forward authenticated request to origin with tokens in headers req.headers.set('fastly-access-token', cookies.access_token) req.headers.set('fastly-id-token', cookies.id_token) return fetch(req, { backend: 'origin' }) } // Start OAuth flow - generate PKCE and redirect to IdP const pkce = await pkceChallenge() const state = `${url.pathname}${url.search}${util.generateRandomStr(10)}` const { stateAndNonce, nonce } = await jwt.generateNonceFromState( settings.config.nonceSecret, state ) const authReqUrl = new URL(settings.openidConfiguration.authorization_endpoint) authReqUrl.searchParams.set('client_id', settings.config.clientId) authReqUrl.searchParams.set('code_challenge', pkce.code_challenge) authReqUrl.searchParams.set('code_challenge_method', 'S256') authReqUrl.searchParams.set('redirect_uri', `${url.protocol}//${url.host}/callback`) authReqUrl.searchParams.set('response_type', 'code') authReqUrl.searchParams.set('scope', 'openid') authReqUrl.searchParams.set('state', stateAndNonce) authReqUrl.searchParams.set('nonce', nonce) return responses.temporaryRedirect(authReqUrl.toString(), { codeVerifierCookie: cookie.persistent('code_verifier', pkce.code_verifier), stateCookie: cookie.persistent('state', state) }) } ``` -------------------------------- ### Manage Secure Cookies for OAuth State and Tokens (JavaScript) Source: https://context7.com/fastly/compute-js-auth/llms.txt Provides functions to serialize, deserialize, and manage secure cookies for OAuth state, code verifier, and tokens. It uses the 'cookie' package and Fastly environment variables to apply security attributes like '__Secure-' prefix, HttpOnly, and Secure flags, which are crucial for production environments. ```javascript import cookie from 'cookie' import { env } from 'fastly:env' // Use __Secure- prefix in production (requires HTTPS) const getPrefix = () => (env('FASTLY_SERVICE_VERSION') ? '__Secure-' : '') const attrs = () => ({ path: '/', sameSite: 'lax', secure: Boolean(env('FASTLY_SERVICE_VERSION')), HttpOnly: true }) // Session cookie (expires when browser closes) export const session = (name, value) => cookie.serialize(`${getPrefix()}${name}`, value, attrs()) // Persistent cookie with max-age export const persistent = (name, value, maxAge) => cookie.serialize(`${getPrefix()}${name}`, value, { ...attrs(), maxAge }) // Expired cookie (for deletion) export const expired = name => persistent(name, 'expired', 0) // Parse cookies, filtering by secure prefix export const parse = (cookieHeader, onlyWithPrefix = true) => { if (!cookieHeader) return {} const cookies = cookie.parse(cookieHeader) const prefix = getPrefix() return onlyWithPrefix && prefix ? Object.keys(cookies).reduce((acc, key) => { if (key.startsWith(prefix)) { acc[key.slice(prefix.length)] = cookies[key] } return acc }, {}) : cookies } // Example: Set token cookies after successful auth const tokenCookies = { accessTokenCookie: persistent('access_token', auth.access_token, auth.expires_in), idTokenCookie: persistent('id_token', auth.id_token, auth.expires_in) } ``` -------------------------------- ### Generate OAuth HTTP Responses with Cookies (JavaScript) Source: https://context7.com/fastly/compute-js-auth/llms.txt Provides utility functions for creating OAuth-specific HTTP responses, including 401 Unauthorized errors and 307 Temporary Redirects. These functions automatically handle the clearing or setting of necessary authentication cookies. ```javascript import * as cookie from './cookie' // 401 Unauthorized response - clears all auth cookies export const unauthorized = body => { const res = new Response(body, { status: 401 }) res.headers.append('Set-Cookie', cookie.expired('access_token')) res.headers.append('Set-Cookie', cookie.expired('id_token')) res.headers.append('Set-Cookie', cookie.expired('code_verifier')) res.headers.append('Set-Cookie', cookie.expired('state')) return res } // 307 Temporary Redirect with cookie management export const temporaryRedirect = (location, { accessTokenCookie = cookie.expired('access_token'), idTokenCookie = cookie.expired('id_token'), codeVerifierCookie = cookie.expired('code_verifier'), stateCookie = cookie.expired('state') } = {}) => { const res = new Response(`Redirecting to ${location}`, { status: 307 }) res.headers.set('Location', location) res.headers.append('Set-Cookie', accessTokenCookie) res.headers.append('Set-Cookie', idTokenCookie) res.headers.append('Set-Cookie', codeVerifierCookie) res.headers.append('Set-Cookie', stateCookie) return res } // Example: Redirect to IdP for authentication return temporaryRedirect('https://accounts.google.com/o/oauth2/v2/auth?...', { codeVerifierCookie: cookie.persistent('code_verifier', pkce.code_verifier), stateCookie: cookie.persistent('state', state) }) // Example: Redirect back to original URL with tokens return temporaryRedirect('/dashboard', { accessTokenCookie: cookie.persistent('access_token', tokens.access_token, 3600), idTokenCookie: cookie.persistent('id_token', tokens.id_token, 3600) }) ``` -------------------------------- ### Generate Nonce from State for CSRF Protection (JavaScript) Source: https://context7.com/fastly/compute-js-auth/llms.txt Creates a time-limited JWT that encodes both the OAuth state parameter and a random nonce to prevent CSRF and replay attacks. It uses the 'jose' library for JWT creation and signing. ```javascript import * as jose from 'jose' import { generateRandomStr } from './util' // Creates a 5-minute validity nonce JWT encoding the state export async function generateNonceFromState(nonceSecret, state) { const nonce = generateRandomStr(30) const stateAndNonce = await new jose.SignJWT({ nonce }) .setSubject(state) // Original request path encoded in subject .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime('5m') // Valid for 5 minutes .sign(nonceSecret) return { stateAndNonce, nonce } } // Verify nonce and retrieve original state export async function getClaimedState(nonceSecret, stateAndNonce) { const { payload } = await jose.jwtVerify(stateAndNonce, nonceSecret) return payload?.sub // Returns original request path } // Example usage in OAuth flow: const nonceSecret = new TextEncoder().encode('your-secret-key') const state = '/protected/resource?query=value' + generateRandomStr(10) const { stateAndNonce, nonce } = await generateNonceFromState(nonceSecret, state) // stateAndNonce is sent to IdP as 'state' parameter // nonce is sent as OpenID 'nonce' parameter // On callback, verify the state const claimedState = await getClaimedState(nonceSecret, stateAndNonce) // Returns: '/protected/resource?query=valueRANDOM10' ``` -------------------------------- ### Validate JWT Token (JavaScript) Source: https://context7.com/fastly/compute-js-auth/llms.txt Validates a JSON Web Token (JWT) by retrieving the appropriate public key from a JWKS (JSON Web Key Set), verifying the signature, and checking standard claims like issuer and audience. It uses the 'jose' library for cryptographic operations. ```javascript import * as jose from 'jose' // Validates a JWT and verifies its claims // Returns: decoded payload if valid, throws on invalid export async function validateToken(jwks, tokenString, verificationOptions) { // Decode header to get key ID (kid) const header = jose.decodeProtectedHeader(tokenString) // Find matching JWK by key ID const publicKey = jwks.keys?.find(k => k.kid === header.kid) if (!publicKey) { throw new Error(`No matching JWK found for identifier ${header.kid}`) } // Import JWK to runtime key format const jwk = await jose.importJWK(publicKey, header.alg) // Verify signature and claims (expiration, issued-at, etc.) const { payload } = await jose.jwtVerify(tokenString, jwk, verificationOptions) return payload } // Example usage: const settings = await loadConfig() try { const payload = await validateToken(settings.jwks, cookies.id_token, { issuer: 'https://accounts.google.com', audience: 'YOUR_CLIENT_ID.apps.googleusercontent.com' }) console.log('User email:', payload.email) console.log('Token expires:', new Date(payload.exp * 1000)) } catch (err) { console.error('Token validation failed:', err.message) return responses.unauthorized('Invalid token') } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.