This document provides comprehensive guidance for the authentication implementation in LangConnect Client using NextAuth.js with Supabase.
LangConnect Client uses NextAuth.js with JWT strategy for authentication, integrated with Supabase as the authentication provider. The system implements automatic token refresh to maintain user sessions seamlessly.
┌─────────────┐ ┌──────────────┐ ┌───────────┐
│ Browser │────▶│ NextAuth │────▶│ Supabase │
│ │◀────│ (JWT) │◀────│ Auth │
└─────────────┘ └──────────────┘ └───────────┘
│ │
│ httpOnly cookie │ refresh token
│ (encrypted JWT) │ stored in JWT
│ │
▼ ▼
Only accessToken Auto refresh when
exposed to client accessToken expires
-
NextAuth Configuration (
/next-connect-ui/src/lib/auth.ts)- JWT strategy for session management
- Credentials provider for email/password authentication
- Callbacks for JWT and session handling
-
Backend Auth API (
/langconnect/api/auth.py)- Supabase integration for user management
- Token generation and validation
- Refresh token endpoint
-
Client Hooks (
/next-connect-ui/src/hooks/use-auth.ts)- React hooks for authentication state
- Login/logout/register functions
- Session management
Current Implementation:
- Access token: Stored in NextAuth JWT and exposed to client session
- Refresh token: Currently not stored (needs implementation)
Recommended Implementation:
// In JWT callback
async jwt({ token, user }) {
if (user) {
return {
...token,
accessToken: user.accessToken,
refreshToken: user.refreshToken,
accessTokenExpires: Date.now() + (60 * 60 * 1000), // 1 hour
}
}
// Return previous token if not expired
if (Date.now() < token.accessTokenExpires) {
return token
}
// Refresh the token
return await refreshAccessToken(token)
}async function refreshAccessToken(token) {
try {
const response = await fetch(`${API_URL}/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
refresh_token: token.refreshToken
})
})
const refreshed = await response.json()
if (!response.ok) {
throw new Error(refreshed.detail || 'Failed to refresh token')
}
return {
...token,
accessToken: refreshed.access_token,
refreshToken: refreshed.refresh_token,
accessTokenExpires: Date.now() + (60 * 60 * 1000), // 1 hour
}
} catch (error) {
console.error('Error refreshing access token', error)
return {
...token,
error: "RefreshAccessTokenError",
}
}
}async session({ session, token }) {
if (token && session.user) {
session.user.id = token.id as string
session.user.email = token.email as string
session.user.name = token.name as string
session.user.accessToken = token.accessToken as string
// Check for refresh errors
if (token.error) {
session.error = token.error
}
}
return session
}// next-auth.d.ts
import "next-auth"
declare module "next-auth" {
interface Session {
user: User
error?: string
}
interface User {
id: string
email: string
name: string
accessToken: string
refreshToken?: string
}
}
declare module "next-auth/jwt" {
interface JWT {
id: string
email: string
name: string
accessToken: string
refreshToken: string
accessTokenExpires: number
error?: string
}
}- Never expose refresh tokens to the client: Only store in server-side JWT
- Use httpOnly cookies: NextAuth automatically handles this
- Implement token rotation: Always get new refresh token when refreshing
- Handle refresh failures gracefully: Redirect to login on refresh failure
- Log authentication events: Monitor for suspicious activity
- Implement rate limiting: Prevent brute force attacks
- Short-lived access tokens: Supabase default is 1 hour
- Secure token transmission: Always use HTTPS
- Validate tokens server-side: Don't trust client-side validation
// Test token refresh function
describe('refreshAccessToken', () => {
it('should refresh valid token', async () => {
// Mock API response
const mockToken = {
refreshToken: 'valid-refresh-token',
accessToken: 'old-access-token',
}
const refreshed = await refreshAccessToken(mockToken)
expect(refreshed.accessToken).not.toBe('old-access-token')
expect(refreshed.refreshToken).toBeDefined()
})
it('should handle refresh failure', async () => {
// Mock API error
const mockToken = {
refreshToken: 'invalid-refresh-token',
}
const result = await refreshAccessToken(mockToken)
expect(result.error).toBe('RefreshAccessTokenError')
})
})- Test login flow with token storage
- Test automatic refresh on expired token
- Test logout and token cleanup
- Test full authentication flow
- Test protected route access
- Test token expiration handling
To implement refresh token support:
- Update NextAuth configuration with new callbacks
- Update TypeScript types
- Implement refresh token function
- Update axios interceptor to handle token refresh
- Test thoroughly with expired tokens
Implement logging for:
- Successful logins
- Token refresh attempts
- Authentication failures
- Suspicious patterns
-
Implement refresh token rotation
- Generate new refresh token on each use
- Invalidate old refresh tokens
-
Add session extension
- Allow users to extend session before expiry
- Implement "Remember me" functionality
-
Multi-device session management
- Track active sessions per user
- Allow users to revoke sessions
-
Enhanced security features
- Two-factor authentication
- Device fingerprinting
- Anomaly detection