Claude
Skills
Sign in
Back

jwt-integration

Included with Lifetime
$97 forever

Set up JWT authentication for both .NET APIs and React microfrontends with proper token handling and authorization

Web Dev

What this skill does


# 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
```
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

Related in Web Dev