@gdnaio/cognito-auth
v1.2.1
Published
Reusable AWS Cognito auth library for React and Next.js
Downloads
44
Readme
@gdnaio/cognito-auth
Reusable AWS Cognito auth library for React and Next.js using Hosted UI + PKCE.
Core project overview
- Security-first: Authorization Code + PKCE via Cognito Hosted UI
- Token lifecycle: access/id tokens + refresh support, auto-refresh scheduling
- Storage modes: memory, localStorage, and cookie-first (when using Next.js routes)
- Next.js support: Edge middleware, route handlers for login/callback/logout, server session util
- Lightweight: framework-agnostic core, no Amplify; uses
joseandcross-fetch
Public API
- Provider:
CognitoAuthProvider({ config })region,userPoolId,clientId,domain,redirectUri,signOutRedirectUri,id?scopes?(default:['openid','email','profile']),storage?('cookie'|'localStorage'|'memory')cookieNames?,clockSkewSec?,onAuthStateChange?
- Hook:
useAuth()→{ isAuthenticated, user, id, signIn, signOut, handleRedirectCallback, getAccessToken, getIdToken }handleRedirectCallback(authCode?, callbackUrl?): IfauthCodeis provided, exchanges the code directly without PKCE and skips reading params from the URL. When provided,callbackUrloverrides theredirect_uriused for token exchange.
- Component:
RequireAuth– guards children withfallback - Next.js helpers (subpath
@gdnaio/cognito-auth/next):createCognitoMiddleware(cfg)getServerSession(cfg)createLoginRoute(cfg),createAuthCallbackRoute(cfg),createLogoutRoute(cfg)
Notes:
config.idis captured once on provider mount and exposed asidfromuseAuth. Subsequent changes toconfig.idare ignored (a warning is logged).
Install
npm i @gdnaio/cognito-auth jose cross-fetchPeer deps:
- react >=18
- next >=13 (optional)
Configuration notes
- Hosted UI (redirect-based) requires
domainandredirectUri. - SRP (username/password) does not require
domainorredirectUri; onlyregion,userPoolId,clientIdare needed. - If you use both flows, keep all fields.
- In Next.js, cookie TTLs set in the callback route are configurable via
cookieMaxAgeSeconds(access/id) andrefreshCookieMaxAgeSeconds(refresh). - Client auto-refresh (based on token expiry) is available via
autoRefreshToken: true(default false). Supported forstorage='localStorage'(andmemoryif a refresh token is present). For HTTP-only cookie setups, prefer server refresh.
Auto-logout (SPA)
- For
storage='localStorage'(andmemory), the provider schedules at access-token expiry. Behavior:- When
autoRefreshToken: true, it tries a refresh at that moment; if refresh succeeds, the session continues; if it fails, it signs out (clears tokens and redirects tosignOutRedirectUri). - When
autoRefreshToken: false, it signs out immediately at access-token expiry (clears tokens and redirects). - Cookie/HTTP-only flows should rely on server/middleware expiry handling instead.
- When
Subscribe to token updates (including auto-refresh) using useAuth():
const { onTokensUpdated } = useAuth()
useEffect(() => {
const unsubscribe = onTokensUpdated((tokens, { reason }) => {
if (reason === 'refresh') {
console.log('tokens refreshed', tokens)
}
})
return unsubscribe
}, [onTokensUpdated])Usage in React (SPA)
- Wrap your app with the provider
import { CognitoAuthProvider } from '@gdnaio/cognito-auth'
export function Root() {
return (
<CognitoAuthProvider
config={{
region: 'us-east-1',
userPoolId: 'us-east-1_XXXX',
clientId: 'xxxxxxxx',
domain: 'https://your-domain.auth.us-east-1.amazoncognito.com',
redirectUri: 'http://localhost:5173/auth/callback',
signOutRedirectUri: 'http://localhost:5173/',
storage: 'localStorage',
}}
>
<App />
</CognitoAuthProvider>
)
}- Handle the OAuth callback once after redirect
import { useEffect } from 'react'
import { useAuth } from '@gdnaio/cognito-auth'
export default function CallbackPage() {
const { handleRedirectCallback } = useAuth()
useEffect(() => {
handleRedirectCallback().finally(() => {
window.history.replaceState({}, '', '/')
})
}, [handleRedirectCallback])
return null
}Alternate: supply an explicit authorization code and optional callback URL override
import { useEffect } from 'react'
import { useAuth } from '@gdnaio/cognito-auth'
export default function CallbackPage() {
const { handleRedirectCallback } = useAuth()
useEffect(() => {
const url = new URL(window.location.href)
const authCode = url.searchParams.get('code') || undefined
const callbackUrl = 'https://your-app.example.com/auth/callback' // optional override
if (authCode) {
handleRedirectCallback(authCode, callbackUrl).finally(() => {
window.history.replaceState({}, '', '/')
})
} else {
handleRedirectCallback().finally(() => {
window.history.replaceState({}, '', '/')
})
}
}, [handleRedirectCallback])
return null
}Notes:
- When
authCodeis provided, the library exchanges the code without a PKCE verifier and skips URL param parsing. callbackUrl, when provided, overrides theredirect_uriused in the token exchange.- Exchanging a code without PKCE works only if your Cognito App Client allows it (PKCE not enforced for your flow).
- Use auth state and actions
import { useAuth, RequireAuth } from '@gdnaio/cognito-auth'
export function Page() {
const { isAuthenticated, user, signIn, signOut } = useAuth()
return (
<main>
{!isAuthenticated ? (
<button onClick={() => signIn()}>Sign in</button>
) : (
<>
<pre>{JSON.stringify(user, null, 2)}</pre>
<button onClick={() => signOut()}>Sign out</button>
</>
)}
<RequireAuth fallback={<p>Please sign in</p>}>
<p>Protected content</p>
</RequireAuth>
</main>
)
}Tokens auto-refresh automatically just before expiry when using storage='localStorage'.
Notes:
- Client auto-refresh runs automatically based on token expiry. For HTTP-only cookie setups, prefer server refresh (Next.js callback/cookie flow).
Optional: attach an Axios interceptor
import axios from 'axios'
import { createAxiosInterceptor, useAuth } from '@gdnaio/cognito-auth'
const api = axios.create()
const { getAccessToken } = useAuth()
createAxiosInterceptor(api, getAccessToken)Optional: Embedded username/password (SRP) login
If you prefer an in-app form instead of the Hosted UI, enable SRP by calling signInWithPassword provided by the context. Your app must have the Cognito App Client configured to allow SRP (no client secret) and the password-based flow enabled.
import { useState } from 'react'
import { useAuth } from '@gdnaio/cognito-auth'
export function PasswordLogin() {
const { signInWithPassword, isAuthenticated, user, signOut } = useAuth()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
return (
<div>
{!isAuthenticated ? (
<form
onSubmit={e => {
e.preventDefault()
signInWithPassword?.(username, password)
}}
>
<input value={username} onChange={e => setUsername(e.target.value)} placeholder='email or username' />
<input value={password} onChange={e => setPassword(e.target.value)} type='password' placeholder='password' />
<button type='submit'>Sign in</button>
</form>
) : (
<>
<pre>{JSON.stringify(user, null, 2)}</pre>
<button onClick={() => signOut()}>Sign out</button>
</>
)}
</div>
)
}Notes:
- SRP happens fully in the browser via
amazon-cognito-identity-js. - If MFA, new password, or custom challenges are required, the method throws codes like
MFA_REQUIREDorNEW_PASSWORD_REQUIRED. Extend the flow to handle those as needed.
Usage in Next.js (App Router)
- Add route handlers
// app/api/auth/login/route.ts
import { createLoginRoute } from '@gdnaio/cognito-auth/next'
export const GET = createLoginRoute({
domain: process.env.NEXT_PUBLIC_COGNITO_DOMAIN!,
clientId: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
redirectUri: process.env.NEXT_PUBLIC_COGNITO_REDIRECT_URI!,
scopes: ['openid', 'email', 'profile'],
})// app/api/auth/callback/route.ts
import { createAuthCallbackRoute } from '@gdnaio/cognito-auth/next'
export const GET = createAuthCallbackRoute({
region: process.env.NEXT_PUBLIC_AWS_REGION!,
userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID!,
clientId: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
domain: process.env.NEXT_PUBLIC_COGNITO_DOMAIN!,
redirectUri: process.env.NEXT_PUBLIC_COGNITO_REDIRECT_URI!,
// Optional cookie TTL configuration (defaults: access/id 1h, refresh 30d)
cookieMaxAgeSeconds: 60 * 30, // 30 minutes for access/id tokens
refreshCookieMaxAgeSeconds: 7 * 24 * 3600, // 7 days for refresh token
})// app/api/auth/logout/route.ts
import { createLogoutRoute } from '@gdnaio/cognito-auth/next'
export const GET = createLogoutRoute({
domain: process.env.NEXT_PUBLIC_COGNITO_DOMAIN!,
clientId: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
signOutRedirectUri: process.env.NEXT_PUBLIC_SIGNOUT_REDIRECT_URI!,
})- Protect routes with middleware
// middleware.ts
import { createCognitoMiddleware } from '@gdnaio/cognito-auth/next'
export default createCognitoMiddleware({
region: process.env.NEXT_PUBLIC_AWS_REGION!,
userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID!,
audience: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
publicRoutes: ['/api/auth', '/public'],
})- Wrap application with provider
// app/layout.tsx
import { CognitoAuthProvider } from '@gdnaio/cognito-auth'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<CognitoAuthProvider
config={{
region: process.env.NEXT_PUBLIC_AWS_REGION!,
userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID!,
clientId: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
domain: process.env.NEXT_PUBLIC_COGNITO_DOMAIN!,
redirectUri: process.env.NEXT_PUBLIC_COGNITO_REDIRECT_URI!,
signOutRedirectUri: process.env.NEXT_PUBLIC_SIGNOUT_REDIRECT_URI!,
scopes: ['openid', 'email', 'profile'],
storage: 'cookie',
}}
>
{children}
</CognitoAuthProvider>
</body>
</html>
)
}SSR/API usage (optional):
import { getServerSession } from '@gdnaio/cognito-auth/next'
export async function GET() {
const session = await getServerSession({
region: process.env.NEXT_PUBLIC_AWS_REGION!,
userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID!,
audience: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
})
// use session?.claims
}Examples
- Next.js (App Router):
examples/next-app - React SPA (Vite):
examples/react-spa
Each example has its own README with environment variables and run commands.
Notes
- Hosted UI + PKCE used by default; SRP is available when you need embedded forms
- For cookie flow in Next.js, use HTTPS in production and set correct redirect/logout URIs in Cognito
