Informatique

4 méthodes d'autorisation pour sécuriser votre API .NET

L'authentification et l'autorisation sont des éléments essentiels de toutes les applications web. Comprendre la terminologie et les méthodes dans l'écosystème .NET peut être particulièrement déroutant. Traditionnellement, ces domaines ont été parmi les points de douleur les plus importants dans .NET, et de nombreux développeurs trouvent que la mise en œuvre de mécanismes d'authentification appropriés est inutilement difficile par rapport à d'autres technologies.

Cependant, comprendre quelques principes et méthodes de base peut rendre l'approche beaucoup plus facile. Dans cet article, nous allons examiner quatre des approches d'autorisation les plus courantes et vous guider pour comprendre et choisir la méthode la plus appropriée à vos besoins.

Différence entre autorisation et authentification

Avant d'explorer les différentes méthodes de gestion de l'authentification et de l'autorisation, il est important de clarifier la distinction entre ces deux concepts. Bien qu'ils soient souvent utilisés de manière interchangeable, l'authentification et l'autorisation servent des objectifs distincts dans le domaine du contrôle d'accès.

L'authentification consiste à identifier qui vous êtes. Lorsque nous nous authentifions ou nous connectons à un système, nous faisons essentiellement une revendication auprès de l'entité authentifiante que nous sommes bien qui nous prétendons être. Cela se fait généralement à l'aide d'un nom d'utilisateur/email et d'un mot de passe. Une fois que le système a validé que cet individu ou entité est bien qui il prétend être, l'authentification est terminée, et l'accès est accordé.

L'autorisation se réfère à ce que vous, en tant qu'entité authentifiée, êtes autorisé à faire. Après que le système ait confirmé votre identité, il détermine ensuite si vous avez les permissions appropriées pour accéder à une ressource particulière.

Cet article se concentrera principalement sur l'autorisation et les mécanismes qui la facilitent. L'authentification implique généralement de stocker des données utilisateur et de valider des mots de passe avec des techniques telles que le hachage et le salage. C'est un sujet à part entière, cependant, dans cet article, nous nous concentrons principalement sur les aspects de l'autorisation.


1. Jetons Web JSON (JWT)

Qu'est-ce qu'un Jeton Web JSON ?

Pour comprendre l'utilisation des JWT, nous devons couvrir quelques principes de base.

Le JWT, une norme ouverte (RFC 7519), est conçu pour transférer en toute sécurité des données sous forme d'objets JSON entre deux parties. Ces données sont signées numériquement avec une clé, ce qui permet à d'autres parties ayant la clé de les vérifier et de leur faire confiance. Cela en fait une technologie idéale pour faciliter l'autorisation. Une fois que le client a été authentifié, il se verra délivrer un JWT. Chaque demande ultérieure au serveur contiendra le jeton, que le serveur vérifiera. En fonction des informations contenues dans la charge utile, le serveur permettra ensuite au client d'accéder à certaines routes et ressources.

Les JWT sont une méthode extrêmement populaire pour gérer l'autorisation et constituent un excellent outil à avoir dans votre arsenal.

Un JWT se compose de trois parties, séparées par un point (.).

xxxxx.yyyyy.zzzzz

Ces parties sont appelées l'en-tête, la charge utile et la signature.

1. En-tête

L'en-tête contient des informations sur le type de jeton et l'algorithme de signature :

{
  "alg": "HS256",
  "typ": "JWT"
}

2. Charge utile

La charge utile contient les données réelles ou les revendications concernant une entité donnée que nous souhaitons échanger entre les deux parties communicantes. La charge utile contiendra généralement des données utilisateur :

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

3. Signature

Enfin, la signature est utilisée pour vérifier que le JWT n'a pas été altéré ou manipulé. Elle le fait en signant l'en-tête encodé, la charge utile et le secret en utilisant l'algorithme spécifié dans l'en-tête. Si les données contenues dans le JWT sont modifiées, cela sera détecté par la vérification de la signature par le serveur et rejeté.

Flux d'autorisation

La nature inviolable des JWT les rend idéaux pour échanger des informations en toute sécurité entre deux parties. Parce que les jetons sont signés, nous pouvons vérifier l'identité des expéditeurs et détecter si les données incluses dans le jeton ont été altérées.

Un flux d'autorisation typique utilisant un JWT ressemble à ceci :

  1. Connexion : Le client envoie une demande avec des identifiants tels qu'un nom d'utilisateur et un mot de passe au serveur.
  2. Génération de jeton : Le serveur vérifie les identifiants. Cela implique généralement de vérifier le mot de passe par rapport au hachage du mot de passe dans la base de données. Si valide, le serveur génère un JWT contenant des informations utilisateur et le renvoie au client.
  3. Stockage du jeton : Le client stocke le JWT, généralement dans un cookie.
  4. Requêtes ultérieures : Pour toute future demande, le client envoie le JWT dans l'en-tête HTTP Authorization.
  5. Vérification du jeton : Le serveur vérifie la signature du JWT et extrait les informations utilisateur de la charge utile du jeton pour authentifier la requête.

Mise en place de l'autorisation JWT dans une API .NET

La mise en place de l'authentification via Jeton Web JSON peut être effectuée assez simplement. Voyons un exemple.

  1. Installation des packages nécessaires :
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package Microsoft.IdentityModel.Tokens
Install-Package Microsoft.IdentityModel.JsonWebTokens

2. Configurer les services d'authentification JWT dans Program.cs :

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

var key = "this is my custom Secret key for authentication"; // This should be stored securely, e.g., in environment variables

builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
        };
    });

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Note : Assurez-vous que la clé secrète est stockée de manière sécurisée. Pour des raisons de démonstration, elle est définie en texte clair ici, mais dans une application de production, elle doit être stockée et accessible de manière sécurisée, par exemple via des variables d'environnement ou un service de gestion des secrets.

Nous configurons ensuite les services d'authentification et spécifions que nous souhaitons utiliser les Jetons Web JSON. Nous définissons ValidateIssuerSigningKey à true pour vérifier que le jeton a été signé avec la clé correcte. Cela garantit que le jeton n'a pas été altéré et qu'il provient d'une source fiable. Enfin, nous spécifions la clé de signature en utilisant la clé secrète que nous avons définie précédemment.

  1. Créer un contrôleur pour le point de terminaison de connexion :
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;

namespace JsonWebTokens.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private readonly string _key;

        public AuthController(IConfiguration config)
        {
            _key = "this is my custom Secret key for authentication";
        }

        [HttpPost("login")]
        public IActionResult Login([FromBody] UserLogin userLogin)
        {
            if (userLogin.Username == "test" && userLogin.Password == "password")
            {
                var token = GenerateJwtToken(userLogin.Username);
                return Ok(new { token });
            }

            return Unauthorized();
        }

        private string GenerateJwtToken(string username)
        {
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, username),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_key));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                issuer: null,
                audience: null,
                claims: claims,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);

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

    public class UserLogin
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
}
 Dans cet exemple, nous avons créé un contrôleur simple avec une action de connexion. Pour l'authentification, nous vérifions si les informations d'identification du client correspondent à "test" et "password". Si elles correspondent, nous générons un jeton et le renvoyons dans la réponse.
  1. Utiliser l'API sécurisée :

Maintenant que tout est configuré, chaque requête à l'API nécessitera un JWT valide dans l'en-tête Authorization pour accéder aux ressources.

[Route("api/[controller]")]
[ApiController]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    [Authorize]
    public IActionResult Get()
    {
        return Ok(new { message = "This is a protected endpoint" });
    }
}

We decorate the controllers we wish to protect with the [Authorize] attribute to instruct the controller to use the authentication configuration we defined earlier. If no token is provided, the user will be rejected with a 401 status code.

JSON Web Tokens are appropriate for situations where you need more granular control over your authorization mechanism. Thanks to the token payload, we can extract information about the user and restrict access to certain resources based on this information, making it an ideal and highly popular technology for authorization.

2. OAuth 2.0

What is OAuth 2.0?

OAuth 2.0 is an open standard designed to allow an application secure access to services or resources hosted on third-party services, on behalf of a user. It is widely used and can be considered the de facto industry standard for online authorization. OAuth 2.0 provides several flows or “recipes” for how authorization should be handled for different scenarios. These flows are also known as grant types and dictate how authorization should be handled for various situations.

OAuth 2.0 is an authorization protocol only and does not deal with user authentication. It is designed to provide access to certain resources and employs access tokens to do so. Most commonly, a JWT will be used for this purpose. As such, JWT and OAuth 2.0 are not two distinct, mutually exclusive technologies. Rather, JWT is a format that can be integrated and used within an OAuth 2.0 flow.

Roles

Roles within OAuth 2.0 refer to the different components of the OAuth system. These are important to understand.

  • Resource Owner: The user or system who authorizes a client to access resources that they own.
  • Client: The application that wants to access a certain resource on behalf of the resource owner.
  • Authorization Server: The server that authenticates the resource owner and issues access tokens to the client.
  • Resource Server: The server hosting the protected resources (often APIs) that the client wants to access.

Authorization using the client credentials grant

As described above, OAuth 2.0 offers different grant types or flows for handling various authorization situations. One such grant type is the Client Credentials Grant, which we will focus on in this article. This flow is used for machine-to-machine (M2M) communication, where no user is involved. It is an extremely common approach that you will likely encounter in one way or another. In the client credentials grant scenario, the client acts on its own behalf, meaning the client itself needs access to a certain resource. For example, this could be a backend application (client) that needs to access an external API (resource server) to fetch data for internal processing.

The client credentials grant is very common and perhaps the simplest of the grant types. A typical flow looks like this:

  1. Client authentication: The backend application (client) has its client ID and client secret, which it uses to authenticate with the authorization server.
  2. Access token request: The backend application sends a POST request to the token endpoint of the authorization server with its client ID, client secret, and the grant type.
  3. Access token response: The authorization server validates the client’s credentials and responds with an access token.
  4. Resource request: The backend application uses the access token to request data from the external API (resource server).
  5. Resource response: The resource server validates the access token and responds with the requested data.

Example

In this small .NET example, we will create a single application acting as both the authorization server and the resource server. Often, these two entities are separated, but for smaller and more integrated applications, a combined approach can be effective and will reduce complexity and latency. Consider the trade-offs between the combined and separated approaches for your particular situation.

  1. Like before, we start by installing the necessary packages.
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package Microsoft.IdentityModel.Tokens
Install-Package Microsoft.IdentityModel.JsonWebTokens

2. Once again we create the necessary JWT configurations in Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

var key = "this is my custom Secret key for authentication"; // This should be stored securely, e.g., in environment variables

builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "https://localhost:7232",
            ValidAudience = "https://localhost:7232",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
        };
    });

builder.Services.AddAuthorization();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
In this scenario, we enable the ValidateIssuer and ValidateAudience options. This adds an extra layer of security to our tokens by ensuring that our application encodes both the issuing and receiving entities into the token. By validating the issuer and audience, we make it harder for a potential attacker to misuse a stolen token. This is particularly important in scenarios where you know the specific application that will be authorized to interact with your application beforehand.

3. We create the token endpoint.

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;

namespace OAuth2._0.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TokenController : ControllerBase
    {
        private const string ClientId = "1";
        private const string ClientSecret = "secret";
        private const string Issuer = "https://localhost:7232";
        private const string Audience = "https://localhost:7232";
        private const string Key = "this is my custom Secret key for authentication";

        [HttpPost("token")]
        public IActionResult Token([FromForm] string client_id, [FromForm] string client_secret)
        {
            if (client_id == ClientId && client_secret == ClientSecret)
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(Key);
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(new[] { new Claim("sub", client_id) }),
                    Expires = DateTime.UtcNow.AddHours(1),
                    Issuer = Issuer,
                    Audience = Audience,
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
                        SecurityAlgorithms.HmacSha256Signature)
                };
                var token = tokenHandler.CreateToken(tokenDescriptor);
                var tokenString = tokenHandler.WriteToken(token);

                return Ok(new
                {
                    access_token = tokenString,
                    token_type = "Bearer",
                    expires_in = 3600, // 1 hour in seconds
                });
            }

            return BadRequest("Invalid client credentials");
        }
    }
}

In a production application, there will usually be an underlying system storing the Client ID and Client Secret. These two values are then issued to trusted systems (Clients) that wish to gain access to resources. In this small example, we simply hardcode these two values, but understand that this should be handled more elegantly in a production application.

We accept client_id and client_secret in a multipart/form-data request and check if these values match what is expected. If all is good, we proceed to create a token and add the necessary claims. Note that we add Issuer and Audience in this situation.

A response is returned to the client in this format:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwibmJmIjoxNzIzMDE4MDM5LCJleHAiOjE3MjMwMjE2MzksImlhdCI6MTcyMzAxODAzOSwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NzIzMiIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjcyMzIifQ.0HpnhsU-Ud2zO8owCHeK5xcSIDJzU4OTLmi0LsifMx0",
    "token_type": "Bearer",
    "expires_in": 3600
}

Here we can see that the authorization server has issued us (the client) a Bearer token that expires in one hour.

4. We can now use the token access protected resources.

    [Route("api/[controller]")]
    [ApiController]
    public class ResourceController : ControllerBase
    {
        [HttpGet]
        [Authorize]
        public IActionResult Get()
        {
            return Ok("Protected resource data");
        }
    }

Usage

OAuth 2.0 is an industry-standard framework for handling authorization across various scenarios. In the simple example above, we’ve demonstrated how to handle authorization in a typical machine-to-machine scenario, but this is just one of many use cases. OAuth 2.0 can be used in multiple environments, including web applications, mobile apps, and IoT devices, providing a unified approach to authorization regardless of platform. It is well-documented and widely adopted, making it much easier for developers to implement. Additionally, there are many tools and libraries available for different programming languages that simplify the OAuth 2.0 integration process.

3. Basic authentication

What is basic auth?

In the first two chapters, we’ve looked at some more complex and scalable authorization mechanisms. For smaller applications that don’t have the same scalability and flexibility requirements, basic authentication offers a much simpler and straightforward, albeit less secure and scalable, method for enforcing access control to web resources.

Basic authentication is built into the HTTP specification and lets a client authenticate itself with a server by sending a username and password encoded in a specific format.

How Basic Authentication Works

A typical flow using Basic Authentication looks like this.

  1. Client Response: The client then sends a request with an Authorization header containing the credentials (username and password) encoded as base64.
  2. Server Validation: The server decodes the base64 string to retrieve the username and password and then validates these credentials.
  3. Access Granted/Denied: If the credentials are valid, the server grants access to the requested resource. If not, the server responds with 401 unauthorized.

The idea is that the username and password are sent this way for every request to the resource server. The server will then decode and validate these credentials to determine if the client should be allowed access.

This is an extremely simple solution but should be used with caution, as it presents some serious security risks. Base64 is just simple encoding and can be easily reversed, which means we’re essentially sending credentials in plain text.

If you do decide to go with this approach, always ensure that you use HTTPS to encrypt the credentials over the network.

Example

1. We start by creating an authentication handler

using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;

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()
    {
        if (!Request.Headers.ContainsKey("Authorization"))
        {
            return AuthenticateResult.Fail("Missing Authorization Header");
        }

        try
        {
            var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
            var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
            var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
            var username = credentials[0];
            var password = credentials[1];

            // Validate the username and password (this is just an example, you should validate against a user store)
            if (username == "admin" && password == "password")
            {
                var claims = new[] {
                    new Claim(ClaimTypes.Name, username)
                };
                var identity = new ClaimsIdentity(claims, Scheme.Name);
                var principal = new ClaimsPrincipal(identity);
                var ticket = new AuthenticationTicket(principal, Scheme.Name);

                return AuthenticateResult.Success(ticket);
            }
            else
            {
                return AuthenticateResult.Fail("Invalid Username or Password");
            }
        }
        catch
        {
            return AuthenticateResult.Fail("Invalid Authorization Header");
        }
    }
}
 

This is fairly straightfoward. This handler will attempt to extract the Authorization header. If no authorization header is set, we deny access. We then attempt to grab the base64 string from the header, and extract the username and password. Once again we simply validate the credentials against hardcoded values for illustration purposes. Normally you would check against some user store.

If all is good we create a simple claim and authenticate succesfully.

2. Register the handler in Program.cs

 
using BasicAuth.Handlers;
using Microsoft.AspNetCore.Authentication;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddAuthentication("BasicAuthentication")
    .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Here we configure our application to use the Basic Authentication scheme, and specify our handler.

3. We can now secure our endpoints

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    [Authorize]
    public IActionResult Get()
    {
        return Ok("Protected resource data");
    }
}

Usage

As described above, basic authentication offers an extremely simple method for authenticating requests to your application. However, this approach should never be used to facilitate actual access control in a production environment due to its obvious drawbacks.

In the past, I have used basic authentication to provide access to test environments in simple scenarios where security and scalability are not a priority. Basic auth is quick and simple but should be used with caution and only in appropriate situations. Consider if other approaches are more suitable for your particular situation.

4. API Key authorization

What is API Key authorization?

API Key authorization is a straightforward and widely used method for controlling access to APIs. This method is ideal for scenarios where you need to issue access to one or more endpoints but do not require granular control over the user’s actual identity. This approach is often employed when you need to access a restricted API, typically after signing up for it. Upon signing up, an API key is usually issued to the client, which can then be stored and used to access specific resources. The issuing entity retains the ability to revoke and invalidate the API key as needed.

In essence, an API key is a unique identifier assigned to a client. The client includes this key in their API requests to gain access to the service. The API key is usually sent to the server in the x-api-key header, where it is extracted and validated using custom logic.

The beauty of API keys lies in the flexibility they offer developers in implementing authorization logic. Typically, client information is stored in a database along with their respective API key and any other relevant information. For instance, you might store the client’s IP address and validate that the request comes from the expected client. Alternatively, you might invalidate the API key after a certain period.

The point is that all the business logic surrounding the authorization can be freely configured by the developer and can be as complex or simple as needed. This makes API key authorization a good choice for many applications where simplicity and flexibility are desired over more complex authorization methods.

How API key authorization works

A typical API key authorization flow looks like this:

  • API key generation: The server generates a unique key for each client. This key is usually a long string of alphanumeric characters.
  • Client usage: The client includes this key in the request header.
  • Server verification: The server receives the request and checks the key against a stored list of valid keys.
  • Authorization: If the key is valid, the server processes the request. Otherwise, access is denied.

As mentioned above the server verification step can be implemented as the developer chooses.

Example

The API key sent by the client will usually be extracted in a piece of middleware, that will intercept the API call and execute the authorization logic.

  1. We start by creating the authorization middleware.
public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private const string API_KEY_HEADER_NAME = "X-Api-Key";

    public ApiKeyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue(API_KEY_HEADER_NAME, out var extractedApiKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("API Key is missing.");
            return;
        }

        var apiKey = "SuperSecret"; // Will normally come from a store/configuration/database

        if (!apiKey.Equals(extractedApiKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Unauthorized client.");
            return;
        }

        await _next(context);
    }
}

Here we start by injecting the RequestDelegate that we use to either send the request onwards in the HTTP request pipeline if all goes well or terminate the request if the authorization fails.

We then attempt to extract the API key from the appropriate header and check it against the expected API key. Once again, the API keys should normally be handled in some form of storage, but for this simple illustration we simply hardcode it.

If the key does not match we return 401. If all is well, we call _next to send the request to the next middleware in the request pipeline.

2. Register the middleware in Program.cs

using APIKey.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UseMiddleware<ApiKeyMiddleware>();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseRouting();
app.MapControllers();

app.Run();

That is all. For every request the API receives, the middleware will extract the API key and ensure it’s valid before allowing the request to continue down the request pipeline.

Usage

As discussed above, API key authorization is a good approach in situations where we need to issue a key to a certain client without requiring very granular identity management. This is suitable for machine-to-machine communication and can be extended to accommodate specific use cases. In the above example, we looked at the simplest possible solution, but the authorization mechanism can be configured to your needs. Unlike basic auth, we do not need to store credentials in the actual token, making it a more secure approach to handle authorization.

Summary

In this article, we’ve looked at four popular approaches to handle authorization in a .NET API, each with its own pros and cons. We discussed JWT (JSON Web Tokens), OAuth, Basic Authentication, and API Key Authorization. Understanding these methods is vital and can help you select the right one for your specific application needs.

Authorization can be challenging to grasp, but hopefully, this article provides a simple and straightforward starting point for you to build upon.

Related Articles

Information

Ce site est construit autour de Joomla 5, en utilisant le template Helix Ultimate et les composants : SP Builder Pro pour la construction de certaines pages, Smart Slider 3 Free pour les slideshows, Komento Free pour les commentaires sur les articles et Ignite Gallery pour les galeries de photos.

Mentions légales

Le site est édité par Christian Sammut
130, Avenue du Général Leclerc
45800 Saint Jean de Braye
Tél.: 06.01.74.90.33
E-mail: contact@sammut.fr

Ce site est hébergé par AMEN SASU
12-14, Rond Point des Champs Elysées
75008 Paris