or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication-lifecycle.mddevice-management.mdindex.mdmulti-factor-authentication.mdoauth-social-authentication.mdpassword-management.mdserver-side-apis.mdsession-management.mduser-management.mdwebauthn-credentials.md
tile.json

session-management.mddocs/

Session Management

Authentication session and token management including automatic refresh, secure storage, and AWS credentials integration.

Fetch Auth Session

Get the current authentication session including tokens and AWS credentials.

function fetchAuthSession(options?: FetchAuthSessionOptions): Promise<AuthSession>;

interface FetchAuthSessionOptions {
  forceRefresh?: boolean;
}

interface AuthSession {
  tokens?: AuthTokens;
  credentials?: AWSCredentials;
  identityId?: string;
  userSub?: string;
}

interface AuthTokens {
  accessToken: JWT;
  idToken?: JWT;
  refreshToken?: string;
}

interface AWSCredentials {
  accessKeyId: string;
  secretAccessKey: string;
  sessionToken?: string;
  expiration?: Date;
}

Usage Example

import { fetchAuthSession } from "@aws-amplify/auth";

// Get current session
const session = await fetchAuthSession();

if (session.tokens) {
  console.log("User is signed in");
  console.log("Access token expires:", session.tokens.accessToken.payload.exp);
  console.log("User ID:", session.userSub);
  
  // Access AWS credentials for API calls
  if (session.credentials) {
    console.log("AWS credentials available");
    console.log("Identity ID:", session.identityId);
  }
} else {
  console.log("User not signed in");
}

// Force token refresh
const refreshedSession = await fetchAuthSession({ 
  forceRefresh: true 
});

Decode JWT

Decode and inspect JWT tokens without verification.

function decodeJWT(token: string): JWT;

interface JWT {
  header: JWTHeader;
  payload: JWTPayload;
  signature: string;
  toString(): string;
}

interface JWTHeader {
  alg: string;
  typ: string;
  kid?: string;
}

interface JWTPayload {
  sub: string;
  aud: string;
  iss: string;
  exp: number;
  iat: number;
  token_use: 'access' | 'id';
  scope?: string;
  [key: string]: any;
}

Usage Example

import { fetchAuthSession, decodeJWT } from "@aws-amplify/auth";

const session = await fetchAuthSession();

if (session.tokens?.accessToken) {
  const decoded = decodeJWT(session.tokens.accessToken.toString());
  
  console.log("Token issuer:", decoded.payload.iss);
  console.log("Token audience:", decoded.payload.aud);
  console.log("Token expires:", new Date(decoded.payload.exp * 1000));
  console.log("Token scopes:", decoded.payload.scope);
  
  // Check if token is expired
  const isExpired = decoded.payload.exp * 1000 < Date.now();
  console.log("Token expired:", isExpired);
}

Session State Management

Managing authentication state across your application:

import { fetchAuthSession, getCurrentUser } from "@aws-amplify/auth";

class SessionManager {
  private session: AuthSession | null = null;
  private sessionListeners: ((session: AuthSession | null) => void)[] = [];
  
  // Initialize session on app start
  async initialize() {
    try {
      await this.refreshSession();
    } catch (error) {
      console.log("No active session");
      this.session = null;
      this.notifyListeners();
    }
  }
  
  // Get current session
  async getSession(): Promise<AuthSession | null> {
    if (!this.session) {
      try {
        await this.refreshSession();
      } catch (error) {
        return null;
      }
    }
    
    // Check if tokens are about to expire (within 5 minutes)
    if (this.session?.tokens?.accessToken) {
      const expiryTime = this.session.tokens.accessToken.payload.exp * 1000;
      const fiveMinutesFromNow = Date.now() + (5 * 60 * 1000);
      
      if (expiryTime < fiveMinutesFromNow) {
        try {
          await this.refreshSession(true);
        } catch (error) {
          console.log("Token refresh failed:", error);
          this.session = null;
        }
      }
    }
    
    return this.session;
  }
  
  // Refresh session
  async refreshSession(forceRefresh = false) {
    this.session = await fetchAuthSession({ forceRefresh });
    this.notifyListeners();
    return this.session;
  }
  
  // Check if user is authenticated
  async isAuthenticated(): Promise<boolean> {
    const session = await this.getSession();
    return !!(session?.tokens?.accessToken);
  }
  
  // Get AWS credentials
  async getCredentials(): Promise<AWSCredentials | null> {
    const session = await this.getSession();
    return session?.credentials || null;
  }
  
  // Listen to session changes
  onSessionChange(listener: (session: AuthSession | null) => void) {
    this.sessionListeners.push(listener);
    
    // Return unsubscribe function
    return () => {
      const index = this.sessionListeners.indexOf(listener);
      if (index > -1) {
        this.sessionListeners.splice(index, 1);
      }
    };
  }
  
  private notifyListeners() {
    this.sessionListeners.forEach(listener => {
      listener(this.session);
    });
  }
  
  // Clear session on sign out
  clearSession() {
    this.session = null;
    this.notifyListeners();
  }
}

// Global session manager instance
export const sessionManager = new SessionManager();

// Usage in React component
import { useEffect, useState } from 'react';

function useAuthSession() {
  const [session, setSession] = useState<AuthSession | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Initialize session
    sessionManager.getSession().then(session => {
      setSession(session);
      setLoading(false);
    });
    
    // Listen to session changes
    const unsubscribe = sessionManager.onSessionChange(session => {
      setSession(session);
      setLoading(false);
    });
    
    return unsubscribe;
  }, []);
  
  return { session, loading, isAuthenticated: !!session?.tokens };
}

Token Types and Usage

Understanding different token types and their purposes:

import { fetchAuthSession, decodeJWT } from "@aws-amplify/auth";

class TokenManager {
  async analyzeTokens() {
    const session = await fetchAuthSession();
    
    if (!session.tokens) {
      console.log("No tokens available");
      return;
    }
    
    // Access Token - for API authorization
    if (session.tokens.accessToken) {
      const accessToken = decodeJWT(session.tokens.accessToken.toString());
      console.log("=== ACCESS TOKEN ===");
      console.log("Purpose: API authorization");
      console.log("Expires:", new Date(accessToken.payload.exp * 1000));
      console.log("Scopes:", accessToken.payload.scope);
      console.log("Client ID:", accessToken.payload.client_id);
    }
    
    // ID Token - user identity information
    if (session.tokens.idToken) {
      const idToken = decodeJWT(session.tokens.idToken.toString());
      console.log("=== ID TOKEN ===");
      console.log("Purpose: User identity");
      console.log("Email:", idToken.payload.email);
      console.log("Email verified:", idToken.payload.email_verified);
      console.log("Name:", idToken.payload.name);
      console.log("Groups:", idToken.payload['cognito:groups']);
    }
    
    // Refresh Token - for obtaining new tokens
    if (session.tokens.refreshToken) {
      console.log("=== REFRESH TOKEN ===");
      console.log("Purpose: Token refresh");
      console.log("Available: Yes");
    }
  }
  
  // Extract user info from ID token
  getUserInfo(): any {
    const session = this.getCurrentSession();
    if (session?.tokens?.idToken) {
      const idToken = decodeJWT(session.tokens.idToken.toString());
      return {
        sub: idToken.payload.sub,
        email: idToken.payload.email,
        emailVerified: idToken.payload.email_verified,
        name: idToken.payload.name,
        givenName: idToken.payload.given_name,
        familyName: idToken.payload.family_name,
        groups: idToken.payload['cognito:groups'] || [],
        customAttributes: this.extractCustomAttributes(idToken.payload)
      };
    }
    return null;
  }
  
  private extractCustomAttributes(payload: any): Record<string, any> {
    const customAttrs: Record<string, any> = {};
    
    Object.keys(payload).forEach(key => {
      if (key.startsWith('custom:')) {
        customAttrs[key.replace('custom:', '')] = payload[key];
      }
    });
    
    return customAttrs;
  }
}

AWS Credentials Integration

Using session credentials with AWS SDK:

import { fetchAuthSession } from "@aws-amplify/auth";
import { S3Client, ListObjectsV2Command } from "@aws-sdk/client-s3";

class AWSIntegration {
  async createS3Client(): Promise<S3Client> {
    const session = await fetchAuthSession();
    
    if (!session.credentials) {
      throw new Error("No AWS credentials available");
    }
    
    return new S3Client({
      region: 'us-east-1',
      credentials: {
        accessKeyId: session.credentials.accessKeyId,
        secretAccessKey: session.credentials.secretAccessKey,
        sessionToken: session.credentials.sessionToken
      }
    });
  }
  
  async listS3Objects(bucket: string) {
    const s3Client = await this.createS3Client();
    
    const command = new ListObjectsV2Command({
      Bucket: bucket,
      MaxKeys: 10
    });
    
    try {
      const response = await s3Client.send(command);
      return response.Contents || [];
    } catch (error) {
      console.log("S3 operation failed:", error);
      throw error;
    }
  }
  
  // Generic AWS API call helper
  async withAWSCredentials<T>(
    operation: (credentials: AWSCredentials) => Promise<T>
  ): Promise<T> {
    const session = await fetchAuthSession();
    
    if (!session.credentials) {
      throw new Error("No AWS credentials available");
    }
    
    return operation(session.credentials);
  }
}

// Usage
const awsIntegration = new AWSIntegration();

// List S3 objects
const objects = await awsIntegration.listS3Objects('my-bucket');

// Use with any AWS SDK client
await awsIntegration.withAWSCredentials(async (credentials) => {
  const dynamoClient = new DynamoDBClient({
    region: 'us-east-1',
    credentials: {
      accessKeyId: credentials.accessKeyId,
      secretAccessKey: credentials.secretAccessKey,
      sessionToken: credentials.sessionToken
    }
  });
  
  // Perform DynamoDB operations
});

Error Handling

Handle session-related errors gracefully:

import { fetchAuthSession, AuthError } from "@aws-amplify/auth";

try {
  const session = await fetchAuthSession();
} catch (error) {
  if (error instanceof AuthError) {
    switch (error.name) {
      case 'NotAuthorizedException':
        console.log('User not signed in');
        // Redirect to sign-in
        break;
      case 'UserNotConfirmedException':
        console.log('User email not confirmed');
        // Redirect to confirmation
        break;
      case 'PasswordResetRequiredException':
        console.log('Password reset required');
        // Redirect to password reset
        break;
      case 'UserNotFoundException':
        console.log('User not found');
        // Clear stored credentials
        break;
      case 'NetworkError':
        console.log('Network error occurred');
        // Show offline message
        break;
      default:
        console.log('Session error:', error.message);
    }
  }
}

// Handle token refresh failures
try {
  await fetchAuthSession({ forceRefresh: true });
} catch (error) {
  if (error instanceof AuthError && error.name === 'NotAuthorizedException') {
    // Refresh token expired, user needs to sign in again
    console.log('Session expired, please sign in again');
    // Clear local session and redirect to sign-in
  }
}

Best Practices

Token Security

  • Never log or expose tokens in client-side code
  • Use secure storage for refresh tokens
  • Implement proper token rotation
  • Monitor token expiration and refresh proactively

Performance Optimization

  • Cache session data appropriately
  • Minimize unnecessary token refreshes
  • Use singleton pattern for session management
  • Implement proper cleanup on sign-out

Error Recovery

  • Implement graceful degradation for network errors
  • Provide clear user feedback for authentication issues
  • Handle edge cases like clock skew and network timeouts
  • Implement proper retry logic for transient failures