Skip to main content

What is Authentication?

Authentication (often called “AuthN”) is the process of verifying the identity of a user, system, or application. It answers the fundamental question: “Who are you?” This is distinct from authorization (AuthZ), which determines what permissions an authenticated entity has.
Authentication solves the problem of trust in distributed systems by ensuring that only verified identities can access protected resources.

How it works in C#

Basic Auth

Explanation: Basic Authentication transmits credentials as a base64-encoded string in the HTTP Authorization header. While simple to implement, it’s insecure without HTTPS as credentials are effectively sent in plaintext.
C# Code Example:
// Client-side implementation
public class BasicAuthHandler : DelegatingHandler
{
    private readonly string _credentials;

    public BasicAuthHandler(string username, string password)
    {
        _credentials = Convert.ToBase64String(
            Encoding.UTF8.GetBytes($"{username}:{password}"));
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Authorization = 
            new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", _credentials);
        return await base.SendAsync(request, cancellationToken);
    }
}

// Server-side validation in ASP.NET Core
public class BasicAuthAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out var authHeader))
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        var authHeaderValue = authHeader.ToString();
        if (!authHeaderValue.StartsWith("Basic "))
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        var encodedCredentials = authHeaderValue.Substring(6);
        var decodedCredentials = Encoding.UTF8.GetString(Convert.FromBase64String(encodedCredentials));
        var credentials = decodedCredentials.Split(':', 2);
        
        // Validate credentials against your user store
        if (!IsValidUser(credentials[0], credentials[1]))
        {
            context.Result = new UnauthorizedResult();
        }
    }
    
    private bool IsValidUser(string username, string password)
    {
        // Your validation logic here
        return username == "admin" && password == "securepassword";
    }
}

Bearer Token

Explanation: Bearer tokens are opaque strings that represent authentication credentials. The client presents the token, and the server validates it without needing to understand its internal structure. C# Code Example:
// Custom bearer token validation in ASP.NET Core
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", options =>
        {
            options.Authority = "https://localhost:5001";
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = false
            };
        });
        
    // Custom token validation
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = "CustomBearer";
        options.DefaultChallengeScheme = "CustomBearer";
    })
    .AddScheme<CustomBearerAuthenticationOptions, CustomBearerAuthenticationHandler>(
        "CustomBearer", options => { });
}

public class CustomBearerAuthenticationHandler : AuthenticationHandler<CustomBearerAuthenticationOptions>
{
    public CustomBearerAuthenticationHandler(
        IOptionsMonitor<CustomBearerAuthenticationOptions> options,
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock) { }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue("Authorization", out var authorization))
        {
            return AuthenticateResult.Fail("Missing Authorization Header");
        }

        var authHeader = authorization.ToString();
        if (!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
        {
            return AuthenticateResult.Fail("Invalid Authorization Header");
        }

        var token = authHeader.Substring("Bearer ".Length).Trim();
        
        // Validate your custom token logic
        var principal = await ValidateTokenAsync(token);
        if (principal == null)
        {
            return AuthenticateResult.Fail("Invalid Token");
        }

        var ticket = new AuthenticationTicket(principal, Scheme.Name);
        return AuthenticateResult.Success(ticket);
    }
    
    private async Task<ClaimsPrincipal> ValidateTokenAsync(string token)
    {
        // Your custom token validation logic
        // This could involve database lookups, etc.
        return await Task.FromResult(new ClaimsPrincipal(new ClaimsIdentity("Custom")));
    }
}

API Key

Explanation: API keys are simple tokens that identify an application rather than a user. They’re commonly used for server-to-server communication and require secure storage and transmission. C# Code Example:
// API Key authentication in ASP.NET Core
public class ApiKeyAttribute : Attribute, IAsyncActionFilter
{
    private const string ApiKeyHeaderName = "X-API-Key";
    
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeaderName, out var potentialApiKey))
        {
            context.Result = new ContentResult()
            {
                StatusCode = 401,
                Content = "API Key missing"
            };
            return;
        }

        var configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
        var apiKey = configuration.GetValue<string>("ApiKey");

        if (!apiKey.Equals(potentialApiKey))
        {
            context.Result = new ContentResult()
            {
                StatusCode = 401,
                Content = "Invalid API Key"
            };
            return;
        }

        await next();
    }
}

// More sophisticated API key with service registration
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
    private readonly IApiKeyValidator _apiKeyValidator;
    
    public ApiKeyAuthenticationHandler(
        IOptionsMonitor<ApiKeyAuthenticationOptions> options,
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock,
        IApiKeyValidator apiKeyValidator)
        : base(options, logger, encoder, clock)
    {
        _apiKeyValidator = apiKeyValidator;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue(Options.HeaderName, out var apiKeyHeaderValues))
        {
            return AuthenticateResult.Fail("API Key header missing");
        }

        var providedApiKey = apiKeyHeaderValues.FirstOrDefault();
        if (string.IsNullOrEmpty(providedApiKey))
        {
            return AuthenticateResult.Fail("API Key is empty");
        }

        var validationResult = await _apiKeyValidator.ValidateAsync(providedApiKey);
        if (!validationResult.IsValid)
        {
            return AuthenticateResult.Fail("Invalid API Key");
        }

        var claims = new[] { new Claim(ClaimTypes.Name, validationResult.ApplicationId) };
        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);

        return AuthenticateResult.Success(ticket);
    }
}

OAuth 2.0

Explanation: OAuth 2.0 is an authorization framework that enables third-party applications to obtain limited access to HTTP services. It separates the role of the client from the resource owner. C# Code Example:
// OAuth 2.0 client implementation using HttpClient
public class OAuth2Client
{
    private readonly HttpClient _httpClient;
    private readonly OAuth2Config _config;

    public OAuth2Client(OAuth2Config config)
    {
        _httpClient = new HttpClient();
        _config = config;
    }

    public async Task<OAuth2TokenResponse> GetTokenAsync(string code)
    {
        var tokenRequest = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type", "authorization_code"),
            new KeyValuePair<string, string>("code", code),
            new KeyValuePair<string, string>("redirect_uri", _config.RedirectUri),
            new KeyValuePair<string, string>("client_id", _config.ClientId),
            new KeyValuePair<string, string>("client_secret", _config.ClientSecret)
        });

        var response = await _httpClient.PostAsync(_config.TokenEndpoint, tokenRequest);
        var content = await response.Content.ReadAsStringAsync();
        
        return JsonSerializer.Deserialize<OAuth2TokenResponse>(content);
    }

    public async Task<OAuth2TokenResponse> RefreshTokenAsync(string refreshToken)
    {
        var refreshRequest = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type", "refresh_token"),
            new KeyValuePair<string, string>("refresh_token", refreshToken),
            new KeyValuePair<string, string>("client_id", _config.ClientId),
            new KeyValuePair<string, string>("client_secret", _config.ClientSecret)
        });

        var response = await _httpClient.PostAsync(_config.TokenEndpoint, refreshRequest);
        var content = await response.Content.ReadAsStringAsync();
        
        return JsonSerializer.Deserialize<OAuth2TokenResponse>(content);
    }
}

// ASP.NET Core OAuth 2.0 authentication
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = "OAuth2";
    })
    .AddCookie()
    .AddOAuth("OAuth2", options =>
    {
        options.ClientId = "your-client-id";
        options.ClientSecret = "your-client-secret";
        options.CallbackPath = new PathString("/oauth-callback");
        
        options.AuthorizationEndpoint = "https://provider.com/oauth/authorize";
        options.TokenEndpoint = "https://provider.com/oauth/token";
        options.UserInformationEndpoint = "https://provider.com/oauth/userinfo";
        
        options.Scope.Add("profile");
        options.Scope.Add("email");
        
        options.SaveTokens = true;
        
        options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
        options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
        options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
    });
}

JWT (JSON Web Tokens)

Explanation: JWTs are compact, URL-safe tokens that contain claims and can be digitally signed or encrypted. They’re commonly used in stateless authentication systems.
C# Code Example:
// JWT Token generation and validation
public class JwtService
{
    private readonly IConfiguration _configuration;

    public JwtService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public string GenerateToken(IEnumerable<Claim> claims, TimeSpan expiration)
    {
        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_configuration["Jwt:Secret"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.Now.Add(expiration),
            signingCredentials: creds);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public ClaimsPrincipal ValidateToken(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Secret"]);

        try
        {
            var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = true,
                ValidIssuer = _configuration["Jwt:Issuer"],
                ValidateAudience = true,
                ValidAudience = _configuration["Jwt:Audience"],
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);

            return principal;
        }
        catch
        {
            return null;
        }
    }
}

// ASP.NET Core JWT configuration
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = _configuration["Jwt:Issuer"],
                ValidAudience = _configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(_configuration["Jwt:Secret"]))
            };
            
            // For SignalR/WebSockets
            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    var accessToken = context.Request.Query["access_token"];
                    var path = context.HttpContext.Request.Path;
                    if (!string.IsNullOrEmpty(accessToken) && 
                        path.StartsWithSegments("/chat"))
                    {
                        context.Token = accessToken;
                    }
                    return Task.CompletedTask;
                }
            };
        });
}

Session-Based Authentication

Explanation: Session authentication maintains state on the server-side. The client receives a session identifier (usually in a cookie) that references server-side session data. C# Code Example:
// Custom session-based authentication
public class SessionAuthMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ISessionService _sessionService;

    public SessionAuthMiddleware(RequestDelegate next, ISessionService sessionService)
    {
        _next = next;
        _sessionService = sessionService;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var sessionId = context.Request.Cookies["SessionId"];
        if (!string.IsNullOrEmpty(sessionId))
        {
            var session = await _sessionService.GetSessionAsync(sessionId);
            if (session != null && session.IsValid)
            {
                var claims = new[]
                {
                    new Claim(ClaimTypes.Name, session.Username),
                    new Claim(ClaimTypes.NameIdentifier, session.UserId.ToString()),
                    new Claim("SessionId", sessionId)
                };
                
                var identity = new ClaimsIdentity(claims, "Session");
                context.User = new ClaimsPrincipal(identity);
                
                // Refresh session expiration
                await _sessionService.RefreshSessionAsync(sessionId);
                context.Response.Cookies.Append("SessionId", sessionId, 
                    new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict });
            }
        }

        await _next(context);
    }
}

// ASP.NET Core built-in session authentication
public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedMemoryCache(); // Or Redis, SQL Server, etc.
    
    services.AddSession(options =>
    {
        options.Cookie.HttpOnly = true;
        options.Cookie.Secure = true;
        options.Cookie.SameSite = SameSiteMode.Strict;
        options.IdleTimeout = TimeSpan.FromMinutes(30);
        options.Cookie.Name = "MyApp.Session";
    });
    
    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options =>
        {
            options.LoginPath = "/Account/Login";
            options.AccessDeniedPath = "/Account/AccessDenied";
            options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
            options.SlidingExpiration = true;
        });
}

// Usage in controller
[Authorize]
public class DashboardController : Controller
{
    public IActionResult Index()
    {
        var userName = User.Identity.Name;
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        
        return View();
    }
}

OpenID Connect

Explanation: OpenID Connect (OIDC) is an authentication layer built on top of OAuth 2.0. It provides identity verification by obtaining user profile information through an ID token. C# Code Example:
// OpenID Connect configuration in ASP.NET Core
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddOpenIdConnect("oidc", options =>
    {
        options.Authority = "https://demo.identityserver.io";
        options.ClientId = "interactive.confidential";
        options.ClientSecret = "secret";
        options.ResponseType = "code";
        
        options.Scope.Clear();
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.Scope.Add("email");
        options.Scope.Add("offline_access");
        
        options.SaveTokens = true;
        options.GetClaimsFromUserInfoEndpoint = true;
        
        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name",
            RoleClaimType = "role"
        };
        
        options.Events = new OpenIdConnectEvents
        {
            OnRedirectToIdentityProvider = context =>
            {
                // Customize the redirect if needed
                return Task.CompletedTask;
            },
            OnTokenValidated = context =>
            {
                // Add custom claims or perform additional validation
                return Task.CompletedTask;
            }
        };
    });
}

// Custom OIDC claim transformation
public class OidcClaimsTransformer : IClaimsTransformation
{
    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var claimsIdentity = new ClaimsIdentity();
        
        if (principal.HasClaim(c => c.Type == "role"))
        {
            var roles = principal.FindAll("role");
            foreach (var role in roles)
            {
                claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role.Value));
            }
        }
        
        // Add custom claims based on OIDC claims
        if (principal.HasClaim(c => c.Type == "email"))
        {
            var email = principal.FindFirst("email")?.Value;
            // Add additional claims based on email
        }
        
        principal.AddIdentity(claimsIdentity);
        return principal;
    }
}

Why is Authentication Important?

  1. Principle of Least Privilege Enforcement - Authentication enables proper authorization implementation, ensuring users only access resources appropriate to their verified identity, minimizing potential damage from compromised accounts.
  2. Non-Repudiation Foundation - Proper authentication creates audit trails that prevent users from denying actions they’ve performed, which is crucial for compliance and security investigations.
  3. Scalability Through Stateless Design - Token-based authentication patterns (like JWT) support horizontal scaling by eliminating server-side session state, allowing requests to be handled by any server in a cluster.

Advanced Nuances

1. Token Revocation Strategies

For JWT-based systems, the stateless nature makes immediate token revocation challenging. Senior developers should implement strategies like:
  • Short-lived access tokens with long-lived refresh tokens
  • Token blacklisting using Redis or database lookups
  • Distributed token validation services
public class RevocableJwtBearerEvents : JwtBearerEvents
{
    private readonly ITokenRevocationService _revocationService;

    public override async Task<TokenValidatedContext> TokenValidated(TokenValidatedContext context)
    {
        var jti = context.SecurityToken.Id;
        if (await _revocationService.IsRevokedAsync(jti))
        {
            context.Fail("Token revoked");
        }
        return context;
    }
}

2. Multi-Factor Authentication Integration

Advanced systems often require MFA. This involves coordinating multiple authentication factors while maintaining usability:
public class MfaService
{
    public async Task<MfaResult> ValidateMfaAsync(string userId, string mfaCode, string mfaType)
    {
        // Validate time-based one-time password (TOTP)
        // Or push notifications, biometrics, etc.
        return await Task.FromResult(new MfaResult { IsValid = true });
    }
}

3. Cross-Cutting Concern Authentication

Senior developers should implement authentication as a cross-cutting concern using middleware, filters, and policy-based authorization:
public class CustomAuthorizationPolicyProvider : IAuthorizationPolicyProvider
{
    public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        var policy = new AuthorizationPolicyBuilder()
            .AddRequirements(new CustomAuthorizationRequirement(policyName))
            .Build();
            
        return Task.FromResult(policy);
    }
}

How this fits the Roadmap

Within the “Security and Best Practices” section of the Advanced C# Mastery roadmap, Authentication serves as the foundational gateway security layer. It’s a prerequisite for more advanced topics like:
  • Authorization Patterns - Understanding authentication is essential before implementing role-based, claim-based, or policy-based authorization
  • Secure Communication - Auth mechanisms integrate with HTTPS, certificate pinning, and secure header configurations
  • API Security - Advanced API protection patterns like rate limiting, threat detection, and API gateway security build upon authentication
  • Identity Management - Leads into more complex identity patterns including federation, single sign-on (SSO), and identity provider integration
Mastering authentication unlocks the ability to design secure microservices architectures, implement zero-trust security models, and build enterprise-grade applications that comply with security standards like OWASP and regulatory requirements like GDPR.

Build docs developers (and LLMs) love