JWT Integration Skill
Use this skill to implement JWT authentication and authorization across StyleMate microservices including both .NET APIs and React frontends.
When to Use
- •Setting up JWT authentication in new services
- •Configuring authorization policies in .NET APIs
- •Implementing token interceptors in React apps
- •Troubleshooting authentication issues
- •Adding role-based access control
What This Skill Does
1. .NET API JWT Setup
Configures:
- •JWT validation in Program.cs
- •Authorization policies for roles
- •Claims extraction from tokens
- •Business isolation via businessId claim
- •Secure token validation parameters
2. React JWT Integration
Sets up:
- •Axios interceptors for token injection
- •Automatic token refresh on 401
- •Token storage (httpOnly cookies or secure storage)
- •Route guards based on JWT claims
- •Business context from token
3. Authorization Policies
Creates policies for:
- •RequireOwnerOrAdmin
- •RequireManagerOrAbove
- •RequireStaffAccess
- •RequireEmailConfirmed
- •RequireBusiness
4. Route Metadata
Configures route metadata with:
- •Allowed roles array
- •Email confirmation requirements
- •Business association requirements
- •Two-factor auth requirements
Expected Inputs
- •Service context name
- •Required authorization policies
- •JWT issuer and audience configuration
- •Roles to support
Deliverables
- •.NET Program.cs JWT configuration
- •React Axios interceptor setup
- •Authorization policies
- •Route guard implementation
- •Token refresh logic
Example Usage
code
Set up JWT authentication for the appointments service. It needs Owner, Admin, and Staff access levels. All endpoints should require a valid business association.
.NET API Configuration
Program.cs JWT Setup
csharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]))
};
});
Authorization Policies
csharp
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("RequireOwnerOrAdmin", policy =>
policy.RequireClaim("role", "Owner", "Admin"));
options.AddPolicy("RequireManagerOrAbove", policy =>
policy.RequireClaim("role", "Owner", "Admin", "Manager"));
options.AddPolicy("RequireBusiness", policy =>
policy.RequireClaim("business_id"));
options.AddPolicy("RequireEmailConfirmed", policy =>
policy.RequireClaim("email_confirmed", "true"));
});
Controller Usage
csharp
[ApiController]
[Route("api/appointments/[controller]")]
[Authorize] // Requires valid JWT
public class AppointmentsController : ControllerBase
{
[HttpGet]
[Authorize(Policy = "RequireStaffAccess")]
public async Task<IActionResult> GetAll()
{
var businessId = User.FindFirst("business_id")?.Value;
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// Query filtered by businessId
var appointments = await _service.GetByBusinessAsync(Guid.Parse(businessId));
return Ok(appointments);
}
}
React Frontend Configuration
Axios Interceptor Setup
typescript
import axios from 'axios';
const apiClient = axios.create({
baseURL: '/api/appointments',
withCredentials: true // For httpOnly cookies
});
// Request interceptor - Add JWT token
apiClient.interceptors.request.use(
(config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor - Handle token refresh
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const { data } = await axios.post('/api/auth/refresh');
localStorage.setItem('accessToken', data.token);
originalRequest.headers.Authorization = `Bearer ${data.token}`;
return apiClient(originalRequest);
} catch (refreshError) {
// Redirect to login
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
export default apiClient;
Route Guards
typescript
import { RouteObject } from 'react-router-dom';
interface RouteMetadata {
label: string;
allowedRoles: string[];
requireEmailConfirmed: boolean;
requireBusiness: boolean;
requireTwoFactor?: boolean;
}
export const routes: RouteObject[] = [
{
path: '/appointments',
element: <AppointmentsPage />,
handle: {
label: 'Appointments',
allowedRoles: ['Owner', 'Admin', 'Staff'],
requireEmailConfirmed: true,
requireBusiness: true
} as RouteMetadata
}
];
JWT Claims Hook
typescript
import { jwtDecode } from 'jwt-decode';
interface JwtPayload {
sub: string;
email: string;
role: string;
business_id: string;
email_confirmed: boolean;
}
export function useJwtClaims() {
const token = localStorage.getItem('accessToken');
if (!token) return null;
try {
return jwtDecode<JwtPayload>(token);
} catch {
return null;
}
}
Security Checklist
- • JWT tokens validated on all endpoints
- • Tokens not stored in localStorage (use httpOnly cookies)
- • Token refresh implemented
- • Expired tokens handled gracefully
- • Business isolation enforced via claims
- • CORS properly configured
- • HTTPS required in production
- • Sensitive claims not exposed client-side
Common Issues
Issue: 401 on all requests
Cause: Token not being sent or invalid signature Fix: Check Axios interceptor, verify JWT_SECRET matches
Issue: Can access other business data
Cause: Missing business_id filter Fix: Always filter queries by businessId from JWT
Issue: Token expired errors
Cause: No refresh logic Fix: Implement token refresh in interceptor
Issue: CORS errors
Cause: Missing credentials or wrong origin Fix: Set withCredentials: true and configure CORS properly