Authentication and Authorization
Authentication and Authorization in ASP.NET Core
Table of Contents
- JWT (JSON Web Tokens) Implementation
- OAuth 2.0 and OpenID Connect
- Role-Based and Policy-Based Authorization
- Claims-Based Identity
- Interview Questions
JWT (JSON Web Tokens) Implementation
Understanding JWT
JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between two parties. They are commonly used for authentication and information exchange in web applications.
JWT Structure
A JWT consists of three parts separated by dots:
- Header: Contains the type of token and signing algorithm
- Payload: Contains the claims
- Signature: Used to verify the token hasn't been altered
Implementing JWT Authentication
// Program.cs
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = 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:Key"]))
};
// Handle token validation events
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
return context.Response.WriteAsync("Invalid token");
},
OnTokenValidated = context =>
{
// Custom validation logic
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
// Add custom claims or validate existing ones
return Task.CompletedTask;
}
};
});
JWT Token Generation
public class JwtTokenService
{
private readonly IConfiguration _configuration;
public JwtTokenService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateToken(User user)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Role, user.Role)
};
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
Refresh Token Implementation
public class RefreshToken
{
public string Token { get; set; }
public string UserId { get; set; }
public DateTime Expires { get; set; }
public bool IsRevoked { get; set; }
}
public class TokenService
{
private readonly AppDbContext _context;
private readonly JwtTokenService _jwtTokenService;
public async Task<TokenResponse> RefreshTokenAsync(string refreshToken)
{
var storedToken = await _context.RefreshTokens
.FirstOrDefaultAsync(rt => rt.Token == refreshToken && !rt.IsRevoked);
if (storedToken == null || storedToken.Expires < DateTime.UtcNow)
throw new SecurityTokenException("Invalid refresh token");
// Revoke old refresh token
storedToken.IsRevoked = true;
_context.RefreshTokens.Update(storedToken);
// Generate new tokens
var user = await _context.Users.FindAsync(storedToken.UserId);
var newAccessToken = _jwtTokenService.GenerateToken(user);
var newRefreshToken = GenerateRefreshToken(user.Id);
_context.RefreshTokens.Add(newRefreshToken);
await _context.SaveChangesAsync();
return new TokenResponse
{
AccessToken = newAccessToken,
RefreshToken = newRefreshToken.Token
};
}
private RefreshToken GenerateRefreshToken(string userId)
{
return new RefreshToken
{
Token = Guid.NewGuid().ToString(),
UserId = userId,
Expires = DateTime.UtcNow.AddDays(7),
IsRevoked = false
};
}
}
JWT Security Considerations
// Secure key management
public class SecureJwtService
{
public void ConfigureJwtValidation(JwtBearerOptions options)
{
// Use strong keys
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
Environment.GetEnvironmentVariable("JWT_SECRET_KEY")));
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://yourdomain.com",
ValidAudience = "https://yourapi.com",
IssuerSigningKey = key,
ClockSkew = TimeSpan.Zero, // No tolerance for expired tokens
// Additional security
RequireSignedTokens = true,
RequireExpirationTime = true,
ValidateActor = false // Set to true if using actor tokens
};
}
}
Handling Token Expiration and Revocation
public class TokenBlacklistService
{
private readonly IDistributedCache _cache;
public TokenBlacklistService(IDistributedCache cache)
{
_cache = cache;
}
public async Task BlacklistTokenAsync(string token, DateTime expiration)
{
var key = $"blacklisted_{token}";
await _cache.SetStringAsync(key, "revoked",
new DistributedCacheEntryOptions
{
AbsoluteExpiration = expiration
});
}
public async Task<bool> IsTokenBlacklistedAsync(string token)
{
var key = $"blacklisted_{token}";
var result = await _cache.GetStringAsync(key);
return result == "revoked";
}
}
// In logout endpoint
[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
var token = Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token);
await _tokenBlacklistService.BlacklistTokenAsync(token, jwtToken.ValidTo);
return Ok();
}
JWT Best Practices
- Use HTTPS for token transmission
- Store tokens securely (HttpOnly cookies for web apps)
- Implement token expiration and refresh mechanisms
- Validate all claims on the server
- Use strong signing keys and rotate them regularly
- Implement token revocation for logout
- Monitor token usage and detect anomalies
OAuth 2.0 and OpenID Connect
OAuth 2.0 Overview
OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service. It works by delegating user authentication to the service that hosts the user account.
OAuth 2.0 Flows
- Authorization Code Flow (most secure for web apps)
- Implicit Flow (for SPAs)
- Resource Owner Password Credentials (not recommended)
- Client Credentials (for machine-to-machine)
Implementing Authorization Code Flow
// Program.cs
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "OAuth";
})
.AddCookie()
.AddOAuth("OAuth", options =>
{
options.ClientId = builder.Configuration["OAuth:ClientId"];
options.ClientSecret = builder.Configuration["OAuth:ClientSecret"];
options.CallbackPath = new PathString("/signin-oauth");
options.AuthorizationEndpoint = "https://provider.com/oauth/authorize";
options.TokenEndpoint = "https://provider.com/oauth/token";
options.UserInformationEndpoint = "https://provider.com/userinfo";
options.SaveTokens = true;
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
// Get user info from provider
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
// Map provider claims to application claims
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.RootElement.GetString("id")));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, user.RootElement.GetString("name")));
context.Identity.AddClaim(new Claim(ClaimTypes.Email, user.RootElement.GetString("email")));
}
};
});
OAuth 2.0 Client Implementation
public class OAuthClientService
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
public OAuthClientService(HttpClient httpClient, IConfiguration configuration)
{
_httpClient = httpClient;
_configuration = configuration;
}
public async Task<TokenResponse> ExchangeCodeForTokenAsync(string code, string redirectUri)
{
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, _configuration["OAuth:TokenEndpoint"]);
var parameters = new Dictionary<string, string>
{
["grant_type"] = "authorization_code",
["code"] = code,
["redirect_uri"] = redirectUri,
["client_id"] = _configuration["OAuth:ClientId"],
["client_secret"] = _configuration["OAuth:ClientSecret"]
};
tokenRequest.Content = new FormUrlEncodedContent(parameters);
var response = await _httpClient.SendAsync(tokenRequest);
response.EnsureSuccessStatusCode();
var tokenData = await response.Content.ReadFromJsonAsync<TokenResponse>();
return tokenData;
}
public string GetAuthorizationUrl(string state)
{
var queryParams = new Dictionary<string, string>
{
["response_type"] = "code",
["client_id"] = _configuration["OAuth:ClientId"],
["redirect_uri"] = _configuration["OAuth:RedirectUri"],
["scope"] = "openid profile email",
["state"] = state
};
var queryString = string.Join("&",
queryParams.Select(kvp => $"{kvp.Key}={Uri.EscapeDataString(kvp.Value)}"));
return $"{_configuration["OAuth:AuthorizationEndpoint"]}?{queryString}";
}
}
OpenID Connect
OpenID Connect is an identity layer built on top of OAuth 2.0 that provides authentication and user information.
OpenID Connect Implementation
// Program.cs
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = "https://identityserver.com";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("roles");
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
// Custom claims transformation
var identity = context.Principal.Identity as ClaimsIdentity;
// Add custom claims
identity.AddClaim(new Claim("custom_claim", "custom_value"));
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Home/Error");
context.HandleResponse();
return Task.CompletedTask;
}
};
});
Identity Server Integration
public class IdentityServerConfig
{
public static IEnumerable<IdentityResource> IdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource("roles", new[] { "role" })
};
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedScopes = { "api1" }
},
new Client
{
ClientId = "webclient",
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets = { new Secret("secret".Sha256()) },
RedirectUris = { "https://localhost:5001/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:5001/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
}
Custom OpenID Connect Handler
public class CustomOpenIdConnectHandler : OpenIdConnectHandler
{
public CustomOpenIdConnectHandler(IOptionsMonitor<OpenIdConnectOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
// Custom challenge logic
var redirectUri = properties.RedirectUri;
if (string.IsNullOrEmpty(redirectUri))
{
redirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
}
var propertiesString = Options.StateDataFormat.Protect(properties);
var authorizationEndpoint = BuildChallengeUrl(properties, propertiesString);
// Add custom parameters
var challengeUrl = QueryHelpers.AddQueryString(authorizationEndpoint,
"custom_param", "custom_value");
Response.Redirect(challengeUrl);
}
}
OAuth 2.0 and OpenID Connect Best Practices
- Use Authorization Code Flow for web applications
- Validate state parameter to prevent CSRF
- Use PKCE (Proof Key for Code Exchange) for SPAs
- Store tokens securely and handle refresh
- Validate tokens on every request
- Implement proper logout and token revocation
- Use HTTPS for all OAuth communications
Role-Based and Policy-Based Authorization
Role-Based Authorization
Role-based authorization assigns permissions based on user roles.
Implementing Role-Based Authorization
// In controller
[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
[HttpGet]
public IActionResult Index() => View();
}
// Multiple roles
[Authorize(Roles = "Admin,Manager")]
public IActionResult Reports() => View();
// Role-based policies
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdminRole", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("RequireManagerOrAdmin", policy =>
policy.RequireRole("Manager", "Admin"));
});
// Usage
[Authorize(Policy = "RequireAdminRole")]
public IActionResult AdminOnly() => View();
Custom Role Manager
public class CustomRoleManager
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public async Task<bool> AssignRoleAsync(string userId, string roleName)
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null) return false;
// Ensure role exists
if (!await _roleManager.RoleExistsAsync(roleName))
{
await _roleManager.CreateAsync(new IdentityRole(roleName));
}
var result = await _userManager.AddToRoleAsync(user, roleName);
return result.Succeeded;
}
public async Task<bool> RemoveRoleAsync(string userId, string roleName)
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null) return false;
var result = await _userManager.RemoveFromRoleAsync(user, roleName);
return result.Succeeded;
}
public async Task<IList<string>> GetUserRolesAsync(string userId)
{
var user = await _userManager.FindByIdAsync(userId);
return await _userManager.GetRolesAsync(user);
}
}
Policy-Based Authorization
Policy-based authorization provides more granular control using requirements and handlers.
Implementing Policy-Based Authorization
// Define requirements
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
public class AgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
var dateOfBirthClaim = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth);
if (dateOfBirthClaim == null)
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
var age = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-age))
{
age--;
}
if (age >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// Register policies
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
options.AddPolicy("AdminOrOwner", policy =>
{
policy.RequireRole("Admin");
policy.Requirements.Add(new ResourceOwnerRequirement());
});
});
builder.Services.AddSingleton<IAuthorizationHandler, AgeHandler>();
builder.Services.AddSingleton<IAuthorizationHandler, ResourceOwnerHandler>();
// Usage
[Authorize(Policy = "AtLeast18")]
public IActionResult AdultContent() => View();
Complex Policy Requirements
public class BusinessHoursRequirement : IAuthorizationRequirement
{
public TimeSpan StartTime { get; }
public TimeSpan EndTime { get; }
public BusinessHoursRequirement(TimeSpan startTime, TimeSpan endTime)
{
StartTime = startTime;
EndTime = endTime;
}
}
public class BusinessHoursHandler : AuthorizationHandler<BusinessHoursRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
BusinessHoursRequirement requirement)
{
var currentTime = DateTime.Now.TimeOfDay;
if (currentTime >= requirement.StartTime && currentTime <= requirement.EndTime)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// Resource-based authorization
public class ResourceOwnerRequirement : IAuthorizationRequirement { }
public class ResourceOwnerHandler : AuthorizationHandler<ResourceOwnerRequirement, Order>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
ResourceOwnerRequirement requirement,
Order resource)
{
if (context.User.FindFirstValue(ClaimTypes.NameIdentifier) == resource.UserId.ToString())
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// Usage
[Authorize(Policy = "BusinessHours")]
public IActionResult PlaceOrder() => View();
[Authorize("AdminOrOwner")]
public IActionResult EditOrder(int orderId)
{
var order = _orderService.GetOrder(orderId);
// The order will be passed to the authorization handler
return View(order);
}
Dynamic Authorization
public class DynamicAuthorizationService
{
private readonly IAuthorizationService _authorizationService;
public DynamicAuthorizationService(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public async Task<bool> CanUserAccessResourceAsync(ClaimsPrincipal user, string resourceId, string permission)
{
var requirement = new PermissionRequirement(permission);
var resource = new ResourceData { Id = resourceId };
var authorizationResult = await _authorizationService.AuthorizeAsync(
user, resource, requirement);
return authorizationResult.Succeeded;
}
}
// In controller
public async Task<IActionResult> EditDocument(int documentId)
{
var canAccess = await _dynamicAuthService.CanUserAccessResourceAsync(
User, documentId.ToString(), "Edit");
if (!canAccess)
{
return Forbid();
}
var document = await _documentService.GetDocumentAsync(documentId);
return View(document);
}
Authorization Best Practices
- Use policies for complex authorization logic
- Implement resource-based authorization for fine-grained control
- Avoid role explosion by using claims and policies
- Cache authorization results when appropriate
- Log authorization failures for security monitoring
- Test authorization logic thoroughly
Claims-Based Identity
Understanding Claims
Claims are statements about a subject (user) that are made by an issuer. They provide a flexible way to represent identity information.
Claims Principal and Identity
// Creating claims
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "john.doe"),
new Claim(ClaimTypes.Email, "john.doe@example.com"),
new Claim(ClaimTypes.Role, "User"),
new Claim("Department", "IT"),
new Claim("ClearanceLevel", "Secret")
};
var identity = new ClaimsIdentity(claims, "Custom");
var principal = new ClaimsPrincipal(identity);
// Accessing claims
var userName = User.FindFirstValue(ClaimTypes.Name);
var department = User.FindFirstValue("Department");
var roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value);
// Checking claims
if (User.HasClaim(c => c.Type == "ClearanceLevel" && c.Value == "Secret"))
{
// User has secret clearance
}
Custom Claims Transformation
public class ClaimsTransformer : IClaimsTransformation
{
private readonly AppDbContext _context;
public ClaimsTransformer(AppDbContext context)
{
_context = context;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = principal.Identity as ClaimsIdentity;
if (identity == null) return principal;
var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (string.IsNullOrEmpty(userId)) return principal;
var user = await _context.Users
.Include(u => u.UserRoles)
.ThenInclude(ur => ur.Role)
.FirstOrDefaultAsync(u => u.Id == userId);
if (user != null)
{
// Add roles as claims
foreach (var userRole in user.UserRoles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, userRole.Role.Name));
}
// Add custom claims
identity.AddClaim(new Claim("Department", user.Department));
identity.AddClaim(new Claim("ManagerId", user.ManagerId?.ToString()));
}
return principal;
}
}
// Register in Program.cs
builder.Services.AddScoped<IClaimsTransformation, ClaimsTransformer>();
Claims-Based Authorization
// Claims-based policies
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ITDepartment", policy =>
policy.RequireClaim("Department", "IT"));
options.AddPolicy("HighClearance", policy =>
policy.RequireClaim("ClearanceLevel", "TopSecret", "Secret"));
options.AddPolicy("ManagerOnly", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c => c.Type == "IsManager" && c.Value == "true")));
});
// Usage
[Authorize(Policy = "ITDepartment")]
public IActionResult ITResources() => View();
[Authorize(Policy = "HighClearance")]
public IActionResult ClassifiedInfo() => View();
Dynamic Claims
public class DynamicClaimsService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly AppDbContext _context;
public DynamicClaimsService(IHttpContextAccessor httpContextAccessor, AppDbContext context)
{
_httpContextAccessor = httpContextAccessor;
_context = context;
}
public async Task AddDynamicClaimsAsync()
{
var user = _httpContextAccessor.HttpContext.User;
var identity = user.Identity as ClaimsIdentity;
if (identity == null) return;
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
var userPermissions = await GetUserPermissionsAsync(userId);
foreach (var permission in userPermissions)
{
identity.AddClaim(new Claim("Permission", permission));
}
}
private async Task<IEnumerable<string>> GetUserPermissionsAsync(string userId)
{
// Query database for user permissions
return await _context.UserPermissions
.Where(up => up.UserId == userId)
.Select(up => up.Permission.Name)
.ToListAsync();
}
}
Claims Serialization and Storage
public class ClaimsSerializer
{
public string SerializeClaims(IEnumerable<Claim> claims)
{
var claimsDictionary = claims.ToDictionary(c => c.Type, c => c.Value);
return JsonSerializer.Serialize(claimsDictionary);
}
public IEnumerable<Claim> DeserializeClaims(string serializedClaims)
{
var claimsDictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(serializedClaims);
return claimsDictionary.Select(kvp => new Claim(kvp.Key, kvp.Value));
}
}
// Storing claims in distributed cache
public class ClaimsCacheService
{
private readonly IDistributedCache _cache;
private readonly ClaimsSerializer _serializer;
public async Task StoreUserClaimsAsync(string userId, IEnumerable<Claim> claims)
{
var serializedClaims = _serializer.SerializeClaims(claims);
await _cache.SetStringAsync($"user_claims_{userId}", serializedClaims,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
});
}
public async Task<IEnumerable<Claim>> GetUserClaimsAsync(string userId)
{
var serializedClaims = await _cache.GetStringAsync($"user_claims_{userId}");
return serializedClaims != null
? _serializer.DeserializeClaims(serializedClaims)
: Enumerable.Empty<Claim>();
}
}
Claims-Based Security Best Practices
- Validate claim issuers and signatures
- Use short-lived claims and refresh mechanisms
- Encrypt sensitive claims when storing
- Implement claim transformation for dynamic scenarios
- Monitor claim usage for security anomalies
- Use standard claim types when possible
Interview Questions
Basic Level (1-2 years experience)
Q1. What is the difference between authentication and authorization?
Answer: Authentication is the process of verifying who a user is (typically through credentials like username/password). Authorization is the process of determining what a user is allowed to do after they've been authenticated. Authentication answers "Who are you?" while authorization answers "What can you do?"
Q2. What is JWT and how does it work?
Answer: JWT (JSON Web Token) is a compact, URL-safe token format used for securely transmitting information between parties. It consists of three parts: Header (algorithm and token type), Payload (claims), and Signature (for verification). The token is signed by the issuer and can be verified by recipients without needing to contact the issuer.
Q3. How do you implement JWT authentication in ASP.NET Core?
Answer: Add JWT bearer authentication in Program.cs using AddJwtBearer(), configure token validation parameters (issuer, audience, signing key), and validate tokens on incoming requests. Generate tokens using JwtSecurityTokenHandler with appropriate claims and signing credentials.
Q4. What are OAuth 2.0 flows?
Answer: OAuth 2.0 defines several flows: Authorization Code (for web apps), Implicit (for SPAs), Resource Owner Password Credentials (not recommended), and Client Credentials (for machine-to-machine). Each flow is designed for different application types and security requirements.
Q5. What is the difference between roles and claims?
Answer: Roles are simple strings that represent user permissions (like "Admin", "User"). Claims are key-value pairs that can represent any information about the user (name, email, department, etc.). Claims are more flexible and can contain richer identity information than roles.
Intermediate Level (2-3 years experience)
Q6. How do you implement refresh tokens with JWT?
Answer: Store refresh tokens securely in database with expiration dates. When access token expires, client sends refresh token to get new access token. Validate refresh token, generate new access/refresh token pair, revoke old refresh token. Implement token blacklist for logout.
Q7. What is OpenID Connect and how does it relate to OAuth 2.0?
Answer: OpenID Connect is an identity layer built on top of OAuth 2.0. While OAuth 2.0 handles authorization, OpenID Connect adds authentication by providing user identity information through standardized claims. It uses OAuth 2.0 flows but adds ID tokens containing user identity.
Q8. How do you implement policy-based authorization?
Answer: Create authorization requirements implementing IAuthorizationRequirement, implement handlers inheriting AuthorizationHandler
Q9. What are the security considerations for JWT implementation?
Answer: Use HTTPS, store tokens securely (HttpOnly cookies), implement token expiration, validate all claims server-side, use strong signing keys, implement token revocation, monitor for anomalies, avoid storing sensitive data in tokens, use appropriate algorithms (RS256 over HS256 for distributed systems).
Q10. How do you handle token expiration and renewal?
Answer: Set reasonable expiration times (15-30 minutes for access tokens), implement refresh token flow, validate token expiration on each request, return 401 for expired tokens, provide token renewal endpoint, handle concurrent refresh attempts, implement sliding expiration if needed.
Advanced Level (3+ years experience)
Q11. How would you implement a custom authentication scheme?
Answer: Create class inheriting AuthenticationHandler
Q12. What strategies do you use for securing APIs with multiple authentication methods?
Answer: Use authentication scheme selection based on request characteristics, implement API key authentication for service-to-service, JWT for user authentication, certificate authentication for high-security scenarios, combine multiple schemes with policy-based authorization, implement proper error handling and logging.
Q13. How do you implement claims transformation in enterprise applications?
Answer: Implement IClaimsTransformation, transform claims based on database lookups, external service calls, or complex business logic, cache transformed claims for performance, handle transformation failures gracefully, implement audit logging for claim changes, consider performance impact of transformations.
Q14. What are the challenges of OAuth 2.0 implementation in microservices?
Answer: Token propagation between services, handling token expiration across service calls, implementing token refresh in distributed systems, managing different token formats, securing inter-service communication, handling service-to-service authentication, implementing proper token validation caching.
Q15. How do you implement fine-grained authorization for complex business rules?
Answer: Use policy-based authorization with custom requirements and handlers, implement resource-based authorization for object-level permissions, use claims to represent complex permissions, implement attribute-based access control (ABAC), consider using external policy engines like OPA, implement caching for performance.
Q16. What monitoring and auditing do you implement for authentication systems?
Answer: Log authentication attempts (success/failure), track token issuance and validation, monitor for suspicious patterns (brute force, unusual locations), implement audit trails for authorization decisions, set up alerts for security events, monitor token usage statistics, implement compliance logging (GDPR, SOX).
Q17. How do you handle authentication in distributed systems?
Answer: Use JWT for stateless authentication, implement token validation services, use API gateways for centralized auth, implement service mesh for inter-service auth, handle token propagation in request chains, implement distributed token blacklisting, consider using identity servers like IdentityServer4.
Q18. What are the performance considerations for claims-based identity?
Answer: Cache claims to avoid repeated database lookups, implement claims transformation efficiently, use distributed caching for claims, minimize claim payload size, consider lazy loading for optional claims, implement proper indexing on user/role tables, monitor claims transformation performance.
Q19. How do you implement multi-tenant authentication and authorization?
Answer: Include tenant ID in tokens and claims, implement tenant-specific claim transformation, use tenant-aware authorization policies, isolate tenant data and permissions, implement tenant-specific token validation, handle tenant switching securely, consider tenant-specific identity providers.
Q20. What security testing do you perform for authentication systems?
Answer: Test for common vulnerabilities (broken auth, JWT attacks, OAuth flaws), implement penetration testing, test token tampering and replay attacks, verify proper error handling, test authorization bypass scenarios, implement automated security scanning, perform threat modeling for authentication flows.
Additional Interview Tips
Authentication Best Practices
- Use established protocols (OAuth 2.0, OpenID Connect) over custom solutions
- Implement proper token management with expiration and revocation
- Validate all inputs and tokens server-side
- Use HTTPS for all authentication communications
- Implement rate limiting to prevent brute force attacks
- Log security events for monitoring and compliance
- Regularly rotate secrets and keys
Authorization Best Practices
- Follow principle of least privilege - grant minimum required permissions
- Use role-based auth for simple scenarios, policy-based for complex
- Implement resource-based authorization for fine-grained control
- Cache authorization results when appropriate
- Test authorization logic thoroughly with unit and integration tests
- Monitor authorization failures for security threats
- Document permission requirements clearly
Common Anti-Patterns to Avoid
- Storing passwords in plain text - always hash and salt
- Using custom authentication when standards exist
- Ignoring token expiration - implement proper lifecycle management
- Role explosion - use claims and policies instead
- Hardcoding permissions - use configuration or database
- Missing input validation - validate all authentication inputs
- Inadequate logging - log all security-relevant events
Security Testing Checklist
- [ ] Test for JWT vulnerabilities (algorithm confusion, none algorithm)
- [ ] Verify OAuth flows work correctly and securely
- [ ] Test authorization bypass scenarios
- [ ] Check for proper error handling (no information leakage)
- [ ] Test token expiration and renewal flows
- [ ] Verify HTTPS is enforced for auth endpoints
- [ ] Test concurrent authentication attempts
- [ ] Check for CSRF protection in auth flows
- [ ] Test session management and logout functionality
- [ ] Verify secure storage of sensitive auth data
Performance Optimization
- [ ] Implement token caching and validation caching
- [ ] Use distributed cache for claims and permissions
- [ ] Optimize database queries for user/role lookups
- [ ] Implement lazy loading for optional claims
- [ ] Use compiled queries for frequent auth checks
- [ ] Monitor authentication performance metrics
- [ ] Implement rate limiting to prevent abuse
- [ ] Cache authorization policy evaluations
Comments
Post a Comment