authentication in web api
authentication in web api
Authentication in a web API is the process of verifying the identity of the user or client making a request to protect sensitive data and control access
Common Authentication Methods
Bearer Token Authentication: Clients include a token (e.g., JWT or OAuth token) in the request headers, which the server validates.
Basic Authentication: Clients send a username and password with each request, typically encoded in Base64 format.
API Key Authentication: Clients include an API key in the request, validated on the server.
OAuth 2.0: OAuth 2.0 is a framework for securing APIs. It allows clients to obtain access tokens used to authenticate requests. Custom Authentication: You can implement custom authentication logic based on your requirements.
Windows Authentication: If you are running your application in a Windows environment, you can use Windows Authentication to authenticate users based on their Windows credentials.
Best Practices
Use Established Frameworks: Avoid writing custom authentication code from scratch. Rely on well-tested, established protocols like OAuth 2.0, OpenID Connect, and JWT.
Always Use HTTPS: Sensitive credentials and tokens should never be transmitted over an unencrypted connection.*
Separate Authentication and Authorization: Clearly define which parts of your system handle identity verification (authentication) versus access control (authorization).
Monitor API Access: Implement logging to monitor access and detect suspicious activities, such as a spike in failed login attempts.
Authorization in Web API:
Authorization is deciding whether the authenticated user can perform an action on a specific resource (Web API Resource).
For example, James (an authenticated user) has permission to get a resource but does not have permission to create a resource. So, once a user is authenticated, you can implement authorization by defining Roles and Policies or using attributes in your controllers and actions.
Role-Based Authorization: You can use role-based authorization to restrict access to specific resources or actions based on the user’s role. Users are assigned roles (e.g., “Admin,” “User”) that determine their access rights. You can decorate controllers or actions with the [Authorize(Roles = “RoleName”)] attribute.
Policy-Based Authorization: You can define and apply custom policies to controllers or actions. Policies can be based on a combination of roles, claims, or custom requirements. Policies are defined with specific access requirements, and endpoints are protected using the [Authorize(Policy = “PolicyName”)] attribute.
Claim-Based Authorization: Resource access is based on the user’s claims in claims-based authorization. You can use the [Authorize] attribute with specific claims as parameters.
Custom Authorization: For more complex scenarios, you can implement custom authorization logic by extending the authorization framework or implementing custom middleware.
Implementation of Authentication & Autherization
Basic Authentication
mplementing Basic Authentication in an ASP.NET Core Web API requires creating a custom authentication handler. This handler intercepts every request, decodes the credentials from the Authorization header, and validates them against your user store.
Step 1: Create an Authentication Handler
Create a class that inherits from AuthenticationHandler<AuthenticationSchemeOptions>. This class handles the core logic of extracting and verifying credentials.
This handler will override the HandleAuthenticateAsync method to:
- Check for the
Authorizationheader in the incoming request.
- Check for the
- Parse and decode the Base64-encoded
"Basic"credentials to extract the username and password.
- Parse and decode the Base64-encoded
- Validate the credentials (this example uses hardcoded values, but you should integrate with your user data store).
- If valid, create a
ClaimsPrincipaland anAuthenticationTicketto represent the authenticated user.
- If valid, create a
- Return an
AuthenticateResultindicating success or failure.
- Return an
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions>
options, ILoggerFactory logger,
UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//1. Check for the Authorization header in the incoming request
if (!Request.Headers.ContainsKey("Authorization"))
{
return AuthenticateResult.Fail("Unauthorized!!");
}
string authenticationHeader = Request.Headers["Authorization"];
if (string.IsNullOrEmpty(authenticationHeader))
{
return AuthenticateResult.Fail("Unauthorized!!");
}
if(!authenticationHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
{
return AuthenticateResult.Fail("Unauthorized!!");
}
//2. Parse and decode the Base64-encoded or FromBase64String "Basic" credentials to extract the username and password
var token = authenticationHeader.Substring(6);
var credentialString = Encoding.UTF8.GetString(Convert.FromBase64String(token));
var crendentials = credentialString.Split(":");
if(crendentials.Length != 2)
{
return AuthenticateResult.Fail("Unauthorized!!");
}
var username= crendentials[0];
var password= crendentials[1];
// 3. Validate the credentials (this example uses hardcoded values, but you should integrate with your user data store).
if (username != "like" || password != "qqqq")
{
return AuthenticateResult.Fail("Unauthorized!!");
}
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, username),
new Claim(ClaimTypes.Role, "admin")
};
// 4. If valid, create a ClaimsPrincipal and an AuthenticationTicket to represent the authenticated user
var idintity = new ClaimsIdentity(claims, "Basic");
var claimPrinciple = new ClaimsPrincipal(idintity);
// 5. Return an AuthenticateResult indicating success or failure.
return AuthenticateResult.Success(new AuthenticationTicket(claimPrinciple, Scheme.Name));
}
}
Step 2: Register the Service
In your Program.cs file, you need to register the custom authentication scheme with the application's services. This tells the application to use your BasicAuthenticationHandler for the "BasicAuthentication" scheme. Additionally, you need to add authorization services and ensure the authentication and authorization middleware are enabled in the request pipeline.
// Program.cs
// Register Authentication service
builder.Services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
app.UseAuthorization();
Step 3: Secure Your Endpoints
To protect specific controllers or action methods, apply the [Authorize] attribute. This attribute will trigger the authentication process using the configured scheme when a request is made to the decorated endpoint.
// in controler use
[Authorize]
// or for role base use below
[Authorize(Roles ="admin")]
// to retreve username from claims
var username = HttpContext.User.Claim
.FirstOrDefault(c=> c.Type == ClaimTypes.NameIdentifier)?.Value;
Step 4: Testing with Postman To test your implemented basic authentication using Postman, follow these steps:
- Create a new HTTP request (e.g., GET) targeting your secured API endpoint.
- Navigate to the Authorization tab.
- Select "Basic Auth" from the "Type" dropdown.
- Enter the valid username and password for testing.
- Click Send. Postman will automatically add the correctly formatted Authorization header to your request.
API KEY Authentication
Implementing API key authentication involves three main phases: generating and storing a secure key, validating that key on every request, and configuring your API to enforce this check.
1. Store the API Key You can store your keys in a simple configuration file for development or a secure vault for production.
Development: Add a key-value pair to your
appsettings.jsonorenvironment variables.Production: Use a dedicated secret manager like
Azure Key VaultorAWS Secrets Managerto prevent accidental exposure in source code.
2. Implementation Strategies
There are three common ways to implement the validation logic in ASP.NET Core:
- Custom Middleware (Global):
Best for securing the entire API. Create a class that intercepts every request, checks for a specific header (e.g., X-API-KEY), and returns a 401 Unauthorized if the key is missing or invalid.
- Custom Attributes (Granular):
Best for securing specific controllers or actions. Decorate individual methods with an attribute (e.g., [ApiKey]) that triggers an authorization filter during execution.
- Endpoint Filters (Minimal APIs):
Designed for Minimal APIs, these filters allow you to apply authentication logic directly to specific routes using .AddEndpointFilter<ApiKeyFilter>().
3. Key Transmission Methods
Choose how clients will send the key to your server:
Request Headers (Recommended): Use a custom header like
X-API-KEY. This is the most secure method as headers are generally not logged by servers.Query Parameters: Passing the key in the URL (e.g., ?
apiKey=xyz) is easy to test but insecure because it appears in browser history and server logs.Basic Auth: Pass the API key as the username with an empty password in the
Authorizationheader.
4. Enable Swagger Support To test your secured endpoints via Swagger UI, configure AddSwaggerGen in your Program.cs to include a Security Definition. This adds an "Authorize" button where you can enter your key. 5. Security Best Practices
- Use HTTPS: Always transmit keys over
HTTPSto encrypt them in transit. - Hash Stored Keys: Do not store plain-text keys in your database;
hash themjust like passwords to protect against data breaches. - Rotation: Implement a mechanism to
rotate or regenerate keysif they are compromised
Implemetation of APIKey using Custom Middleware (Global)
Implementing API key authentication in an ASP.NET Core Web API is most efficiently done using Custom Middleware for global security or Attributes for granular control.
Step 1: Store the API Key Add your expected API key to the appsettings.json file.
{
"ApiKey": "My-secrate-api-key"
}
Step 2: Create a Validation middleware class Create a middleware class to intercept every request and check for the X-API-KEY header.
namespace WebApplication1.Auth
{
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
public const string APIKEY_HEADER = "X-API-KEY";
public ApiKeyMiddleware( RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IConfiguration configuration)
{
if(!context.Request.Headers.TryGetValue(APIKEY_HEADER, out var extractedApikey))
{
context.Response.StatusCode = 401; // Unauthorized
await context.Response.WriteAsync("API KEY IS MISSING!!!");
return;
}
var apiKey = configuration.GetValue<string>("ApiKey");
if (!apiKey.Equals(extractedApikey))
{
context.Response.StatusCode = 401; // Unauthorized
await context.Response.WriteAsync("API KEY INVALID!!!");
return;
}
await _next(context);
}
}
}
Step 3: Register Middleware
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Register the middleware (place before HttpsRedirection or Routing)
app.UseMiddleware<ApiKeyMiddleware>();
app.MapControllers();
app.Run();
Token based Authentication (JWT)
Implementing token authentication in a Web API involves establishing a flow where a client exchanges credentials for a digitally signed token, typically a JSON Web Token (JWT), and then includes that token in the header of subsequent requests.
1. Token Authentication Workflow
1. User Login: The client sends a POST request with credentials (username/password) to an authentication endpoint.
2. Validation & Generation: The server verifies the credentials against its database and, if valid, generates a signed token (JWT).
3. Token Issuance: The server sends the token back to the client.
4. Authenticated Requests: For all protected resources, the client attaches the token to the Authorization HTTP header using the Bearer scheme: Authorization: Bearer <token>.
5. Server Verification: The server’s middleware intercepts the request, validates the token’s signature and expiry, and extracts user claims.
2. Implementation Steps (ASP.NET Core Example)
Step 1: Install Required Packages
Install the JWT Bearer authentication package via the NuGet Package Manager or CLI:
Microsoft.AspNetCore.Authentication.JwtBearerSystem.IdentityModel.Tokens.Jwt(for token generation)
Step 2: Configure Authentication in Middleware
In your Program.cs or Startup.cs file, register the authentication services and define validation parameters (Issuer, Audience, and Secret Key).
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "yourdomain.com",
ValidAudience = "yourdomain.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("YourSecretKeyHere"))
};
});
- Key Detail: Use
app.UseAuthentication()beforeapp.UseAuthorization()in the request pipeline.
Step 3: Create the Token Generation Endpoint
Develop a controller action that validates user credentials and returns the JWT using JwtSecurityTokenHandler.
public string CreateToken(Userlogin user){
var claims = new[] { new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Role, user.Role) };
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("YourSecretKeyHere"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "yourdomain.com",
audience: "yourdomain.com",
claims: claims,
expires: DateTime.Now.AddHours(1),
signingCredentials: creds
);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}
Step 4: Secure Protected Endpoints
Decorate your API controllers or specific actions with the [Authorize] attribute to enforce token verification.
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase {
[HttpGet]
public IActionResult GetProtectedData() => Ok("Secure data accessed.");
}
Q) how to implement Token Authentication authentication in web api
Implementing token authentication in a web API involves a secure, multi-step process using an authentication server to issue tokens and the API to validate them. The most common and recommended approach is to use JSON Web Tokens (JWTs) over HTTPS.
Core Concepts
Authentication Server: Verifies user credentials and generates a token (typically a JWT).
Access Token: A short-lived, self-contained string containing user information (claims) and a signature, used to access protected resources.
Refresh Token (Optional): A longer-lived token used to obtain a new access token without forcing the user to log in again.
HTTPS: All communication must occur over HTTPS to prevent tokens from being intercepted.
Implementation Steps
The process can be divided into client-side and server-side operations:
1. User Authentication (Client-Side)
The client sends a request to the authentication server with user credentials.
The user provides their username and password via a login form.
The client application sends these credentials to the API's authentication endpoint (e.g.,
/api/auth/login) using an HTTPPOSTrequest.
2. Token Generation (Server-Side) The server validates the credentials and, if correct, generates a token.
The server verifies the provided credentials against the user database.
Upon successful verification, the server generates a JWT containing claims (user ID, roles, expiration time) and a cryptographic signature.
The server sends the token back to the client in the HTTP response.
3. Token Storage and Transmission (Client-Side)
The client stores the token securely and includes it in subsequent requests.
The client stores the token (e.g., in secure memory or an HTTP-only cookie, not browser local storage for better security).
For all subsequent requests to protected API endpoints, the client includes the token in the
Authorizationheader using theBearerscheme (e.g.,Authorization: Bearer <access_token>).
4. Token Validation and Authorization (Server-Side) The API validates the token for every request to protected resources.
The API reads the token from the
Authorizationheader.It then validates the token's authenticity, integrity, and expiration time by checking its signature and claims. Libraries are available for this purpose (e.g.,
System.IdentityModel.Tokens.Jwtfor .NET).If the token is valid, the server processes the request. If invalid or expired, it returns a 401 Unauthorized or 403 Forbidden error.
Key Best Practices
Use HTTPS: Never send tokens over non-HTTPS connections.
Short Expiration Times: Set access tokens to expire quickly (e.g., 5-10 minutes) to minimize the impact of a compromised token.
Secure Storage: Store tokens on the server side using the Backend for Frontend (BFF) pattern or in secure, HTTP-only cookies to prevent cross-site scripting (XSS) attacks.
Use Asymmetric Keys: Use strong algorithms like RS256 for signing tokens and regularly rotate cryptographic keys.
Avoid Sensitive Data: Do not put sensitive user data in the token payload as it is only encoded, not encrypted, and can be easily decoded (though not tampered with).
Implement Login
Implementing the login step for token authentication involves creating a secure endpoint that validates credentials and returns a signed JSON Web Token (JWT).
Step 1: Create Login Models
Define a simple model to capture the user's credentials sent from the client.
public class UserLoginDTO
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
Step 2: Create the Authentication Endpoint
In your API controller (e.g., AuthController), create a POST method. This method will check the database for the user and, if valid, generate the token.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WebApplication1.Models;
using WebApplication1.DATA;
using WebApplication1.Service.Interfaces;
using WebApplication1.Models.DTO;
namespace WebApplication1.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
private readonly ITokenService tokenService;
public LoginController(ITokenService tokenService)
{
this.tokenService = tokenService;
}
[HttpPost]
public IActionResult Login([FromBody] UserLoginDTO userlogin)
{
// 1. Validate the user from Ugerstore/ or database
var user = UserTable.UserList.FirstOrDefault(u => (u.Username == userlogin.Username && u.Password == userlogin.Password));
if (user == null)
{
return Unauthorized( new {message= "Invalid username or password"});
}
// 2. Generate token
var token = tokenService.CreateToken(user);
// Return the token to the client
return Ok(new {Token = token});
}
}
}
Step 3: create the JWT Token
On the server side, use a library like System.IdentityModel.Tokens.Jwt to create the token using a secret key.
1. create ITokenService interface
using WebApplication1.Models;
using WebApplication1.Models.DTO;
namespace WebApplication1.Service.Interfaces
{
public interface ITokenService
{
string CreateToken(UserLoginDTO user);
}
}
2 . Create TokenService to create JWT Token
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using WebApplication1.Models.DTO;
using WebApplication1.Service.Interfaces;
namespace WebApplication1.Service
{
public class TokenService : ITokenService
{
private readonly SymmetricSecurityKey _key;
public TokenService(IConfiguration config)
{
_key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"]));
}
public string CreateToken(UserLoginDTO user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Username)
};
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha256);
var tokenDescriptor = new JwtSecurityToken(
claims: claims,
expires: DateTime.Now.AddMinutes(4),
signingCredentials: creds
) ;
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.WriteToken(tokenDescriptor) ;
return token;
}
}
}
3. Register TokenService in program.cs
// in Program.cs
builder.Services.AddScoped<ITokenService, TokenService>();
Step 4: Configure Authentication JWT in program.cs
1. create Service Extention class
// in Program.cs
// extention of server for Jwt registration
builder.Services.AddIdentityService(builder.Configuration);
2. Configure JWT Authentication in IdentityServiceExtention.cs class
make static class and method to use
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace WebApplication1.Extentions
{
public static class IdentityServiceExtention
{
public static IServiceCollection AddIdentityService (this IServiceCollection services,
IConfiguration config)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(
options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])),
ValidateIssuer =false,
ValidateAudience= false
};
});
return services;
}
}
}
Step 5: Token key in configuration file
"TokenKey": "AKeyThatIsAtLeast32CharsLong1234"
Implement Registration
Step: 1. Create the Registration Model (DTO)
using System.ComponentModel.DataAnnotations;
namespace WebApplication1.Models.DTO
{
public class UserRegistrationDTO
{
[Required]
public string Username { get; set; }
[Required, MinLength(3)]
public string Password { get; set; }
}
}
Step: 2. Implement the Registration Logic
[HttpPost("Register")]
public async Task<IActionResult> Register([FromBody] UserRegistrationDTO userRegistration)
{
// check if user is already exist
var user = UserTable.UserList.FirstOrDefault(u => (u.Username == userRegistration.Username));
if (user!=null)
{
return BadRequest("User already exist!!");
}
var userRegitor = new UserLoginDTO
{
Username = userRegistration.Username,
// Password Hashing: Never store plain-text passwords. Use a secure library like BCrypt.Net-Next to hash and salt the password.
Password = userRegistration.Password,
};
UserTable.UserList.Add(userRegitor);
// return JWT token
var token = tokenService.CreateToken(userRegitor);
return Ok(new {Token = token});
}
Enhanced Security Options
ASP.NET Core Identity: For production-grade applications, use the Microsoft Identity framework, which provides built-in methods for UserManager.CreateAsync and automatic password hashing/validation.
Password in webapi
Password Hashing: Never store or compare passwords in plain text; use a library like BCrypt to hash them. or use hasing and salting.
Implementing password security in a .NET Web API involves using a slow hashing algorithm like BCrypt or the built-in ASP.NET Core Identity PasswordHasher. Below is a step-by-step implementation guide using the BCrypt.Net-Next library, which is widely considered a modern best practice for its adaptive complexity.
Step 1: Install Required Package
Install the hashing library via the .NET CLI or NuGet Package Manager:
dotnet add package BCrypt.Net-Next
Step 2: Update the User Model
Ensure your user database entity includes a field for the hashed password, typically a string.
public class User
{
public int Id { get; set; }
public string Username { get; set; }
// Never store the password itself! Store only the hash.
public string PasswordHash { get; set; }
}
Step 3: Implement Registration Logic
During registration, use BCrypt.HashPassword to securely hash the user's input before saving it to the database.
[HttpPost("register")]
public async Task<IActionResult> Register(UserRegisterDto request)
{
// 1. Hash the password with a work factor (e.g., 12 or 13)
// BCrypt automatically handles the salt and includes it in the resulting string.
string hashedPassword = BCrypt.Net.BCrypt.HashPassword(request.Password, workFactor: 13);
// 2. Create the user object and save to DB
var user = new User {
Username = request.Username,
PasswordHash = hashedPassword
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
return Ok("User registered successfully");
}
Step 4: Implement Login (Authentication) Logic
When a user attempts to log in, retrieve their stored hash and use BCrypt.Verify to compare it against the provided password.
[HttpPost("login")]
public async Task<IActionResult> Login(UserLoginDto request)
{
// 1. Find user in the database
var user = await _context.Users.SingleOrDefaultAsync(u => u.Username == request.Username);
if (user == null) return Unauthorized("Invalid credentials");
// 2. Verify the incoming password against the stored hash
bool isPasswordValid = BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash);
if (!isPasswordValid) return Unauthorized("Invalid credentials");
// 3. DO NOT return the password. Issue a JWT for future requests.
string token = CreateJwtToken(user);
return Ok(new { Token = token });
}
Step 5: Critical Best Practices
Work Factor: Use a "Work Factor" of at least 11–13 to ensure hashing is slow enough to resist brute-force attacks but fast enough for a good user experience.
Enforce HTTPS: Always use HTTPS Redirection in Program.cs to prevent passwords from being intercepted in transit.
Generic Error Messages: Always return generic "Invalid credentials" messages rather than specific errors like "Username not found" to prevent attackers from mapping valid usernames.
Input Validation: Use Data Annotations (e.g., [Required], [MinLength(8)]) on your DTOs to enforce strong password policies before the request even reaches your logic.
Register Services in Program.cs
ReplyDeleteAdd the authentication and authorization services to the dependency injection container.
This involves configuring the JwtBearerDefaults.AuthenticationScheme and specifying the TokenValidationParameters, including validating the issuer, audience, lifetime, and issuer signing key. The signing key is created using the secret key from the appsettings.json file. Finally, add authorization services and ensure app.UseAuthentication() is called before app.UseAuthorization()