auth-patterns
Secure authentication and authorization patterns including JWT, OAuth2, guards, and role-based access control
What this skill does
# Authentication & Authorization Patterns
Complete guide to secure authentication and authorization in Angular applications.
## Table of Contents
1. [Authentication Basics](#authentication-basics)
2. [JWT Implementation](#jwt-implementation)
3. [OAuth2 & Social Login](#oauth2--social-login)
4. [Token Management](#token-management)
5. [Route Guards](#route-guards)
6. [Role-Based Access Control](#role-based-access-control)
7. [HTTP Interceptors](#http-interceptors)
8. [Session Management](#session-management)
---
## Authentication Basics
### Authentication Flow
```
1. User enters credentials
2. Frontend sends to backend
3. Backend validates credentials
4. Backend generates JWT token
5. Frontend stores token securely
6. Frontend includes token in API requests
7. Backend validates token on each request
```
### Secure Authentication Service
```typescript
@Injectable({ providedIn: 'root' })
export class AuthService {
private readonly TOKEN_KEY = 'auth_token';
private currentUserSubject = new BehaviorSubject<User | null>(null);
public currentUser$ = this.currentUserSubject.asObservable();
constructor(
private http: HttpClient,
private router: Router
) {
// Initialize user from token on app start
this.loadUserFromToken();
}
login(email: string, password: string): Observable<AuthResponse> {
return this.http.post<AuthResponse>('/api/auth/login', {
email,
password
}).pipe(
tap(response => {
this.setSession(response);
}),
catchError(error => {
console.error('Login failed:', error);
return throwError(() => error);
})
);
}
logout(): void {
// Clear token
localStorage.removeItem(this.TOKEN_KEY);
// Clear user state
this.currentUserSubject.next(null);
// Redirect to login
this.router.navigate(['/login']);
// Optional: Call backend to invalidate token
this.http.post('/api/auth/logout', {}).subscribe();
}
isAuthenticated(): boolean {
const token = this.getToken();
if (!token) return false;
// Check if token is expired
return !this.isTokenExpired(token);
}
getToken(): string | null {
return localStorage.getItem(this.TOKEN_KEY);
}
private setSession(authResult: AuthResponse): void {
// Store token
localStorage.setItem(this.TOKEN_KEY, authResult.token);
// Update current user
this.currentUserSubject.next(authResult.user);
}
private loadUserFromToken(): void {
const token = this.getToken();
if (token && !this.isTokenExpired(token)) {
// Decode token to get user info
const decoded = this.decodeToken(token);
this.currentUserSubject.next(decoded.user);
}
}
private isTokenExpired(token: string): boolean {
try {
const decoded = this.decodeToken(token);
const expiryTime = decoded.exp * 1000; // Convert to milliseconds
return Date.now() >= expiryTime;
} catch {
return true;
}
}
private decodeToken(token: string): any {
try {
const payload = token.split('.')[1];
return JSON.parse(atob(payload));
} catch {
return null;
}
}
}
```
---
## JWT Implementation
### JWT Structure
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // Header
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. // Payload
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c // Signature
```
### JWT Payload
```typescript
interface JwtPayload {
sub: string; // Subject (user ID)
email: string; // User email
name: string; // User name
role: string; // User role
iat: number; // Issued at
exp: number; // Expiration time
}
```
### JWT Service
```typescript
@Injectable({ providedIn: 'root' })
export class JwtService {
private readonly SECRET_KEY = 'your-secret-key'; // Server-side only!
// Decode JWT (client-side)
decode(token: string): any {
try {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid token format');
}
const payload = parts[1];
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
return JSON.parse(decoded);
} catch (error) {
console.error('Failed to decode token:', error);
return null;
}
}
// Check if token is expired
isExpired(token: string): boolean {
const decoded = this.decode(token);
if (!decoded || !decoded.exp) return true;
const expiryTime = decoded.exp * 1000;
return Date.now() >= expiryTime;
}
// Get expiry date
getExpiryDate(token: string): Date | null {
const decoded = this.decode(token);
if (!decoded || !decoded.exp) return null;
return new Date(decoded.exp * 1000);
}
// Get time until expiry
getTimeUntilExpiry(token: string): number {
const expiryDate = this.getExpiryDate(token);
if (!expiryDate) return 0;
return expiryDate.getTime() - Date.now();
}
}
```
### Token Refresh
```typescript
@Injectable({ providedIn: 'root' })
export class TokenRefreshService {
private refreshInProgress = false;
private refreshSubject = new Subject<string>();
constructor(
private http: HttpClient,
private authService: AuthService
) {
// Auto-refresh before expiry
this.setupAutoRefresh();
}
refreshToken(): Observable<string> {
if (this.refreshInProgress) {
// Return existing refresh observable
return this.refreshSubject.pipe(
filter(token => !!token),
take(1)
);
}
this.refreshInProgress = true;
return this.http.post<{ token: string }>('/api/auth/refresh', {
refreshToken: this.authService.getRefreshToken()
}).pipe(
tap(response => {
this.authService.setToken(response.token);
this.refreshInProgress = false;
this.refreshSubject.next(response.token);
}),
catchError(error => {
this.refreshInProgress = false;
this.authService.logout();
return throwError(() => error);
})
);
}
private setupAutoRefresh(): void {
// Refresh token 5 minutes before expiry
const REFRESH_BEFORE_EXPIRY = 5 * 60 * 1000; // 5 minutes
interval(60000).pipe( // Check every minute
filter(() => this.authService.isAuthenticated()),
switchMap(() => {
const token = this.authService.getToken();
if (!token) return of(null);
const timeUntilExpiry = this.getTimeUntilExpiry(token);
if (timeUntilExpiry <= REFRESH_BEFORE_EXPIRY) {
return this.refreshToken();
}
return of(null);
})
).subscribe();
}
private getTimeUntilExpiry(token: string): number {
const decoded = this.decodeToken(token);
if (!decoded?.exp) return 0;
return (decoded.exp * 1000) - Date.now();
}
}
```
---
## OAuth2 & Social Login
### OAuth2 Flow
```
1. User clicks "Login with Google"
2. Redirect to OAuth provider (Google)
3. User authorizes app
4. Provider redirects back with authorization code
5. Exchange code for access token
6. Use token to get user info
7. Create session in your app
```
### Social Login Service
```typescript
@Injectable({ providedIn: 'root' })
export class SocialAuthService {
constructor(
private http: HttpClient,
private authService: AuthService
) {}
// Google OAuth2
loginWithGoogle(): void {
const clientId = environment.googleClientId;
const redirectUri = `${window.location.origin}/auth/google/callback`;
const scope = 'openid email profile';
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${clientId}&` +
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
`response_type=code&` +
`scope=${encodeURIComponent(scope)}`;
window.location.href = authUrl;
}
// Handle OAuth callback
handleOAuthCallback(code: string, pRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.