This application now uses OAuth 2.0 Single Sign-On (SSO) via auth.zestacademy.tech for authentication. Users authenticate once with their ZestAcademy account and can access all integrated platforms:
- zestacademy.tech
- zestfolio.tech
- zestcompilers.tech
1. User clicks "Login with ZestAcademy"
→ Browser redirects to auth.zestacademy.tech/authorize
2. User authenticates on auth server (if not already logged in)
→ Auth server redirects back with authorization code
3. Backend exchanges code for access token (server-to-server)
→ Token stored in HTTP-only cookie
4. Client uses token to access user info
→ Token validated on each request
Add these to your .env.local:
# OAuth SSO Configuration
NEXT_PUBLIC_AUTH_SERVER_URL=https://auth.zestacademy.tech
NEXT_PUBLIC_OAUTH_CLIENT_ID=zestcompilers
OAUTH_CLIENT_SECRET=your_oauth_client_secret
NEXT_PUBLIC_REDIRECT_URI=https://zestcompilers.tech/api/auth/callback
JWT_SECRET=your_jwt_secret_for_validation
COOKIE_SECRET=your_cookie_encryption_secretRegister your client application with the auth server:
- Client ID:
zestcompilers - Redirect URI:
https://zestcompilers.tech/api/auth/callback - Allowed Scopes:
openid profile email - Response Types:
code - Grant Types:
authorization_code
Initiates OAuth flow by redirecting to auth server.
Flow:
- Generates CSRF state token
- Stores state in HTTP-only cookie
- Redirects to
auth.zestacademy.tech/authorize
Parameters sent to auth server:
client_id: Your application identifierredirect_uri: Where to send user after authresponse_type:code(authorization code flow)scope:openid profile emailstate: CSRF protection token
Handles OAuth callback from auth server.
Query Parameters:
code: Authorization code (exchange for token)state: CSRF state token (must match cookie)error: Error code if authentication failed
Flow:
- Validates CSRF state token
- Exchanges authorization code for access token (backend only)
- Stores access token in HTTP-only cookie
- Redirects to home page
Security:
- Client secret never exposed to browser
- Token exchange happens server-side only
- CSRF protection via state validation
Returns current user information from JWT.
Response:
{
"user": {
"id": "user-id",
"email": "user@example.com",
"name": "User Name",
"picture": "https://..."
}
}Errors:
401: Not authenticated or invalid token
Logs out user and clears session.
Query Parameters:
global: Set totruefor global logout across all platforms
Flow:
- Clears local HTTP-only cookie
- If
global=true, calls auth server logout endpoint - Redirects to home page
-
No Passwords Stored or Transmitted
- Users authenticate only on
auth.zestacademy.tech - This app never handles passwords
- Users authenticate only on
-
No JWTs in localStorage
- All tokens stored in HTTP-only cookies
- Cookies not accessible via JavaScript
-
Backend-Only Token Exchange
- Authorization code exchanged server-side
- Client secret never exposed to browser
-
Token Expiration Enforced
- JWT expiry (
exp) validated on each request - Expired tokens rejected automatically
- JWT expiry (
-
Issuer & Audience Validation
iss(issuer) must beauth.zestacademy.techaud(audience) must match client ID
{
httpOnly: true, // Not accessible via JavaScript
secure: true, // HTTPS only in production
sameSite: 'lax', // CSRF protection
maxAge: 604800, // 7 days
path: '/'
}- State parameter generated for each login attempt
- Stored in HTTP-only cookie
- Validated on callback
- Prevents unauthorized redirects
import Link from 'next/link'
import { Button } from '@/components/ui/button'
<Link href="/api/auth/login">
<Button>Login with ZestAcademy</Button>
</Link>The UserProfile component automatically:
- Fetches current user from
/api/auth/me - Displays user avatar and info
- Provides logout button
import { UserProfile } from '@/components/layout/UserProfile'
<UserProfile />To protect an API route:
import { NextRequest, NextResponse } from 'next/server'
import { getTokenFromCookies } from '@/lib/cookie-utils'
import { validateJWT } from '@/lib/jwt-utils'
export async function GET(request: NextRequest) {
const token = getTokenFromCookies(request.headers.get('cookie'))
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const validation = validateJWT(token)
if (!validation.valid) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 })
}
// Access user info: validation.payload.sub, .email, etc.
// ... your protected route logic
}- User visits zestcompilers.tech
- Clicks "Login with ZestAcademy"
- Redirected to auth.zestacademy.tech
- Enters credentials (if not already logged in)
- Grants consent (first time only)
- Redirected back to zestcompilers.tech (logged in)
Local Logout:
- Clears session for this app only
- User still logged in on other platforms
Global Logout:
- Clears session on auth server
- User logged out from all platforms
For local testing, you can use mock auth server or update redirect URIs:
NEXT_PUBLIC_AUTH_SERVER_URL=http://localhost:3001
NEXT_PUBLIC_REDIRECT_URI=http://localhost:3000/api/auth/callback- Login redirects to auth server correctly
- After auth, user redirected back with code
- Token stored in HTTP-only cookie
- User info displayed in profile
- Protected routes require authentication
- Logout clears cookie
- Global logout invalidates session on auth server
- Token expiration handled gracefully
- CSRF state validation works
Cause: State cookie not persisting or tampered with
Solutions:
- Check cookie settings (domain, secure, sameSite)
- Ensure cookies enabled in browser
- Verify domain matches redirect URI
Cause: Invalid client credentials or authorization code
Solutions:
- Verify
OAUTH_CLIENT_SECRETis correct - Check authorization code hasn't expired (usually 10 min)
- Ensure redirect URI matches exactly
Cause: JWT validation failing
Solutions:
- Verify
NEXT_PUBLIC_AUTH_SERVER_URLmatches JWTissclaim - Ensure
NEXT_PUBLIC_OAUTH_CLIENT_IDmatches JWTaudclaim
Cause: Cookie not being sent or token expired
Solutions:
- Check cookie domain and path settings
- Verify token hasn't expired
- Ensure HTTP-only cookie flag not preventing legitimate access
If migrating from Firebase Authentication:
- Users will need to re-authenticate using SSO
- Old Firebase tokens will be invalid
- Update any code referencing Firebase auth:
auth.currentUser→/api/auth/mesignInWithPopup()→/api/auth/loginsignOut()→/api/auth/logout
For auth server issues or OAuth client registration:
- Contact: auth-support@zestacademy.tech
- Documentation: https://docs.zestacademy.tech/sso
✅ Implemented:
- HTTP-only cookies for token storage
- CSRF protection via state parameter
- Server-side token exchange
- JWT validation (expiry, issuer, audience)
- Secure cookie flags in production
- No client secrets in frontend code
- Implement token refresh mechanism
- Add rate limiting to auth endpoints
- Monitor for suspicious login patterns
- Regular security audits
- Keep dependencies updated
This implementation follows:
- OAuth 2.0 RFC 6749
- OpenID Connect Core 1.0
- OWASP Authentication Guidelines
- GDPR data protection requirements