Dotnet

OpenIddict - Authorization server with Client Credentials [EP.1]

OpenIddict is an open source project for building an authorization server. It focuses only on authorization, so whatever you use to authenticate your users can work alongside OpenIddict.
Tech Blogs

Let's get to know OpenIddict

Many of you have probably heard about Authentication & Authorization servers, and some of you may have built one yourself, maybe with C# .NET or with some other language. In this blog I'm going to talk about a library that is one of the options for building an Authorization Server on .NET.

OpenIddict is an open source project and a framework for building an authorization server based on the OAuth 2.0 / OpenID Connect (OIDC) standards for ASP.NET and ASP.NET Core. OpenIddict focuses only on checking permission to access the server's resources, in other words Authorization. Verifying who the user is, or Authentication, has nothing to do with OpenIddict, so whatever kind of authentication you use can work together with it, whether that's username and password, an access token or even windows authentication.

In this blog I'll use .NET 7 with the ASP.NET Core project template as the example.

Required Dependencies:

Note: I chose Sqlite for the example because it's easy for anyone who wants to check out the code and run it themselves, without having to set up some other database.


Create and configure project

Create the DbContext
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
}

Add the ConnectionString to appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "DataSource=Database\\app.db"
  }
}

Use EntityFramework in Program.cs

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
    // Configure Entity Framework Core to use Microsoft Sqlite.
    options.UseSqlite(connectionString);

    // Register the entity sets needed by OpenIddict.
    options.UseOpenIddict();
});

Run the EF Migrations commands to create the database and tables
dotnet ef migrations add init
dotnet ef database update

Once the migrations are done we get the Sqlite database and tables shown in the picture. I use the SQLite Viewer Extension for VS Code to look at the data in a Sqlite database.

SqliteViewer.png


Use OpenIddict in Program.cs

In this blog we'll only talk about authorizing with client credentials. Client credentials means using a client_id and a client_secret to request a token, which is then used for authorization when calling resources such as an API.

builder.Services
        .AddOpenIddict()
        .AddCore(options =>
        {
            options.UseEntityFrameworkCore()
                   .UseDbContext<ApplicationDbContext>();
        })
        .AddServer(options =>
        {
            options.SetTokenEndpointUris("connect/token");

            options.AllowClientCredentialsFlow()
                   .AllowRefreshTokenFlow();

            options.AddDevelopmentEncryptionCertificate()
                   .AddDevelopmentSigningCertificate();

            options.RegisterScopes("api");

            options.UseAspNetCore()
                   .EnableTokenEndpointPassthrough();
        })
        .AddValidation(options =>
        {
            options.UseLocalServer();
            options.UseAspNetCore();
        });

Configure Authorization Policy

Since the access_token is created by authenticating with OpenIddict, we need to specify the authentication scheme with OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme so the access_token can be authorized through the policy. We can also define the scope of the policy from the token's claims.


builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy(
        "ClientCredentialsPolicy",
        policy => policy
            .RequireAuthenticatedUser()
            .RequireClaim(Claims.Scope, "api")
            .AddAuthenticationSchemes(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme));
});


Add the Authorization Endpoint

In this blog I'll handle authorization with a Minimal API instead of a Controller. We don't render any View or UI here, so a Minimal API is enough.

The url path has to match the one we declared in the OpenIddict service, so the client can call it to request a token for authorizing against the api. The client_id and client_secret are validated automatically by the OpenIddict service, so we don't have to implement that ourselves.


app.MapPost("/connect/token", async (HttpContext context, IOpenIddictApplicationManager applicationManager) =>
{
    var request = context.GetOpenIddictServerRequest();
    if (!request.IsClientCredentialsGrantType())
    {
        throw new NotImplementedException("The specified grant is not implemented.");
    }

    var application = await applicationManager.FindByClientIdAsync(request.ClientId) ??
        throw new InvalidOperationException("The application cannot be found.");

    var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType, Claims.Name, Claims.Role);

    identity.AddClaim(Claims.Subject, await applicationManager.GetClientIdAsync(application));
    identity.AddClaim(Claims.Name, await applicationManager.GetDisplayNameAsync(application));
    identity.AddClaim(Claims.Scope, "api");
    identity.SetDestinations(GetDestinations);

    return Results.SignIn(
        principal: new ClaimsPrincipal(identity),
        properties: null,
        authenticationScheme: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
});

static IEnumerable<string> GetDestinations(Claim claim) => claim.Type switch
{
    Claims.Name or Claims.Email or Claims.Role => new[] { Destinations.AccessToken, Destinations.IdentityToken },
    _ => new[] { Destinations.AccessToken }
};


Create ClientCredentials for OpenIddict

OpenIddict needs to know whether the client asking for a token has valid credentials, so I'll put the client's credentials data straight into the application pipeline in program.cs.


await SeedClientCredentials(app.Services);

static async Task SeedClientCredentials(IServiceProvider serviceProvider)
{
    using var scope = serviceProvider.CreateScope();

    var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
    await context.Database.EnsureCreatedAsync();

    var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();

    if (await manager.FindByClientIdAsync("aixasz-client") is null)
    {
        await manager.CreateAsync(new OpenIddictApplicationDescriptor
        {
            ClientId = "aixasz-client",
            ClientSecret = "f128d22c-412d-469e-94c0-e1eb366e7f1b",
            DisplayName = "Aixasz Sample OpenIddict ClientCredentials",
            Permissions =
            {
                Permissions.Endpoints.Token,
                Permissions.GrantTypes.ClientCredentials
            }
        });
    }
}


Generate an access_token with ClientCredentials

I'll use the Thunder Client Extension in VS Code to request the access_token. Just put the client_id, client_secret and grant_type in a Form-Encode body and POST it to the token endpoint we configured. That's all it takes to get an access_token for authorization.

client_credentials_openiddict.jpg

Authorize an API endpoint with the access_token from OpenIddict

First, we need an API endpoint

Still using a Minimal API, we add the extension method that requires authorization on the request and point it at the policy we defined.


app.MapGet("/version", () => "v1")
   .RequireAuthorization("ClientCredentialsPolicy");


Test the request without an access_token

without-token.png

As you can see, the server responds with Status: 401 Unauthorized.


Test the request with an access_token authenticated through OpenIddict

with-token.png

And just like that, we're authorized.

What's next

OpenIddict can also do authorization with other flows, such as the Password Credentials Flow, the Authorization Code Flow and more. We'll talk about those in the next EP.

I've pushed the source code to a git repo, you can take a look here -> Github Repository

Found this useful? You can support the blog with a coffee.

Buy me a coffee