# Securing ASP.NET Core APIs with JWT and OAuth2

Security is the part of API development where "it works" and "it's actually safe" can look identical right up until someone finds the gap. I've worked on fintech and insurance platforms where a missed authorization check isn't a minor bug, it's a real exposure. Here's how I approach authentication and authorization in [ASP.NET](http://ASP.NET) Core, and the mistakes I've seen most often.

### Authentication vs authorization: keep the distinction sharp

Authentication answers "who is this?" Authorization answers "what are they allowed to do?" Conflating the two is where a lot of security gaps start. A user can be perfectly authenticated with a valid token and still have no business accessing another customer's policy data. I've reviewed code where a valid JWT was treated as sufficient proof that a request was allowed to proceed, with no check on whether the authenticated user actually owned the resource being requested.

### JWT setup, done properly

csharp

```csharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = config["Jwt:Issuer"],
            ValidAudience = config["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"]))
        };
    });
```

Every one of those `Validate*` flags matters. I've seen `ValidateLifetime` left false during development and never turned back on, which means expired tokens work forever. Small oversight, serious consequence.

### Authorization at the group level, not endpoint by endpoint

csharp

```csharp
app.MapGroup("/api/policies")
   .RequireAuthorization("PolicyOwner")
   .MapPolicyEndpoints();
```

Applying authorization at the group level means nothing slips through because someone added a new endpoint and forgot to decorate it individually. I've caught this exact gap during a security review before, a new endpoint added under time pressure, authorization decoration simply forgotten.

### Resource-level authorization, not just role checks

Role-based checks answer "is this user an admin." They don't answer "does this user own this specific policy." For that, I use policy-based authorization with resource-specific handlers:

csharp

```csharp
public class PolicyOwnerHandler : AuthorizationHandler<PolicyOwnerRequirement, Policy>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, PolicyOwnerRequirement requirement, Policy resource)
    {
        if (resource.CustomerId == context.User.GetUserId())
            context.Succeed(requirement);

        return Task.CompletedTask;
    }
}
```

This is the check that actually prevents one customer from accessing another customer's data with a perfectly valid token. Role checks alone won't catch that.

### Refresh tokens: convenience without giving up control

Short-lived access tokens paired with refresh tokens balance usability against exposure. If an access token leaks, the damage window is small. Refresh tokens should be stored securely, rotated on use, and revocable, so a compromised refresh token doesn't become a permanent backdoor.

### Rate limiting auth endpoints specifically

Login and token endpoints are prime targets for credential stuffing and brute force attempts. I rate limit these more aggressively than general API endpoints, using the built-in rate limiting middleware, because the cost of a false positive here is much lower than the cost of an unthrottled login endpoint.

csharp

```csharp
app.MapGroup("/api/auth")
   .RequireRateLimiting("strict");
```

### OAuth2 for third-party integrations

When external partners need access to specific parts of your API, I don't hand out long-lived API keys with full access. Authorization code flow with scoped permissions lets a partner request exactly the access they need, and lets you revoke it without touching anyone else's integration.

### The habit that matters most

None of these individual pieces are exotic. What actually prevents incidents is treating security review as a standing part of the development process, not a one-time audit before launch. New endpoints get the same scrutiny as the first ones. That discipline, more than any single technique, is what's kept the systems I've built out of the wrong kind of headlines.
