DEV Community

Cover image for Setting up an Authorization Server with OpenIddict - Part V - OpenID Connect
Robin van der Knaap
Robin van der Knaap

Posted on • Edited on

Setting up an Authorization Server with OpenIddict - Part V - OpenID Connect

This article is part of a series called Setting up an Authorization Server with OpenIddict. The articles in this series will guide you through the process of setting up an OAuth2 + OpenID Connect authorization server on the the ASPNET Core platform using OpenIddict.

GitHub logo robinvanderknaap / authorization-server-openiddict

Authorization Server implemented with OpenIddict.


OpenIddict implements the OpenID Connect protocol, which is an identity layer on top of the OAuth2 protocol.
The implementation of the OpenID Connect protocol issues an extra token to the client application, called the identity token. This token contains user profile information which can be used by client applications to identify the end-user.

It's wise to keep your tokens small. Therefore, the OpenID Connect protocol offers the possibility to expose an userinfo endpoint from which clients can retrieve extra information about the end-user which is not stored in the identity token.

In this part we will see how to leverage the OpenID Connect protocol and how to add an endpoint for querying extra user information.

Enable OpenID Connect

As said, OpenIddict has implemented the OpenID Connect protocol. You can test this by requesting an extra scope called openid with Postman:

Alt Text

In the response you will find an identity token together with the access token.

To store extra profile information in the identity token, you can add claims in the Authorize method in the AuthorizationController when using the authorization code flow or add claims in the token endpoint if you are using the client credentials flow. Make sure to set the destination to IdentityToken. Here's an example using a default claim called email:

public async Task<IActionResult> Authorize()
{
    ...

    var claims = new List<Claim>
    {
        // 'subject' claim which is required
        new Claim(OpenIddictConstants.Claims.Subject, result.Principal.Identity.Name),
        new Claim("some claim", "some value").SetDestinations(OpenIddictConstants.Destinations.AccessToken),
        new Claim(OpenIddictConstants.Claims.Email, "some@email").SetDestinations(OpenIddictConstants.Destinations.IdentityToken)
    };

    ...
}
Enter fullscreen mode Exit fullscreen mode

It is also possible for a claim to have multiple destinations, so we can add a claim to the access token and the identity token if we want.

You can view the content of the identity token on jwt.io:

{
  "sub": "Robin van der Knaap",
  "email": "some@email",
  "oi_au_id": "e5e12b78-ddea-42df-8350-5b7bda36b07b",
  "azp": "postman",
  "at_hash": "iuzdujyas1p_1XjUuxtw9A",
  "oi_tkn_id": "7f0ae652-c997-4425-bda5-6d024ea3acb3",
  "aud": "postman",
  "exp": 1607684956,
  "iss": "https://localhost:5001/",
  "iat": 1607683756
}
Enter fullscreen mode Exit fullscreen mode

Userinfo endpoint

Besides the identity token, the OpenID Connect protocols also allows for an url to get information about the user. To leverage the userinfo endpoint, we need to enable the endpoint and set the url in Startup.cs first:

options
    .SetAuthorizationEndpointUris("/connect/authorize")
    .SetTokenEndpointUris("/connect/token")
    .SetUserinfoEndpointUris("/connect/userinfo");

options
    .UseAspNetCore()
    .EnableTokenEndpointPassthrough()
    .EnableAuthorizationEndpointPassthrough()
    .EnableUserinfoEndpointPassthrough();
Enter fullscreen mode Exit fullscreen mode

Now if you restart the authorization server and navigate to https://localhost:5001/.well-known/openid-configuration. You will see that the userinfo endpoint is added to public configuration file:

Alt Text

Next step is to implement the userinfo endpoint. We can decide for ourselves which user info we want to share with clients. We will add the method to the AuthorizationController:

[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
[HttpGet("~/connect/userinfo")]
public async Task<IActionResult> Userinfo()
{
    var claimsPrincipal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;

    return Ok(new
    {
        Name = claimsPrincipal.GetClaim(OpenIddictConstants.Claims.Subject),
        Occupation = "Developer",
        Age = 43
    });
}
Enter fullscreen mode Exit fullscreen mode

The user info endpoint can only be accessed when the client has a valid access token. This is why we added the Authorize attribute. For this to work, we have to enable authorization in Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseAuthentication();

    app.UseAuthorization();

    ...
}
Enter fullscreen mode Exit fullscreen mode

Now if we do a GET request to /connect/userinfo and use a valid access token, the response looks something like this:

Alt Text

Next

Next up, we will enable the usage of refresh tokens.

Top comments (9)

Collapse
 
keithn profile image
Keith Nicholas

Part VI - Integrating External Authorization Providers ... google/azure/facebook/twitter.... etc ? :)

Collapse
 
rohans profile image
Rohan Singh

I could not get authorization to work without:

1) Enable validation (note: this snippet only works for authorizing requests within the same endpoint as the authorization server, such as the userinfo API)

services.AddOpenIddict()
    .AddServer(() =>
    {
        // ...
    })
    .AddValidation(options =>
    {
        // Import the configuration from the local OpenIddict server instance.
        options.UseLocalServer();

        // Register the ASP.NET Core host.
        options.UseAspNetCore();
    });
Enter fullscreen mode Exit fullscreen mode

2) Use the validation authentication scheme to authorize instead of the server's scheme

// Replace this:
//[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
// With this:
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
Enter fullscreen mode Exit fullscreen mode
Collapse
 
joepb profile image
Joep Beusenberg

Awesome catch. I think I'd never figured this out if you hadn't added this.
Also in the line below, you will need to use OpenIddictValidationAspNetCoreDefaults. Then it works flawlessly.

Collapse
 
hdsoftware profile image
HD Software

Really nice article :D

Can anyone please give an example on how to implement OpenIddict with the following situation:

3 apps, where app1 is the ID server, app2 as the API server and app3 as a client application.

Meaning no Postman, no WEB browser involvement.

I have tried to understand the "Zirku" example shipped with OpenIddict, but - I have no idea how to read the Zirku.Client

What I nead is plain and simple:
Client app asking the IdServer for a token, based on Username&password
Client app asking the ApiServer for something, passing along the token.

As simple as absolutly possible.

I tried to implement something like the "Hollastin" example, but didnt manage to get it running correctly.

So, an example in plain C# would be extreemly nice to see.

Collapse
 
nonsenseless profile image
nonsenseless

This is something I've been looking for and I think the confusion is in expecting open id to work with username/password. Hopefully @robinvanderknaap can clarify, but the oauth flows are built around supporting communication between servers or between clients and the server via third-party login. Logging in with Username/Password is basically what you set up in part 2 and can use the more normal AspNetCore individual authentication.

I'd definitely be interested in more tips on how to combine OpenIddict and the standard motely array of AspNetUsers, AspNetRoles, etc.

Collapse
 
zizounetgit profile image
zizounetgit

I still got this even if i Use the validation authentication scheme
The userinfo request was rejected because the mandatory 'access_token' parameter was missing.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
The response was successfully returned as a challenge response: {
"error": "missing_token",
"error_description": "The mandatory 'access_token' parameter is missing.",
"error_uri": "documentation.openiddict.com/error..."
}.

Collapse
 
luisapplivity profile image
LuisApplivity

For anyone, again, wondering how to use Postman to test your newly-added userinfo endpoint, click on the "Authorization" tab, and select "API Key" from the dropdown box for the field "Type". You will have 3 fields given to you: "Key", "Value" and "Add to".

For "key", write "Authorization".

For "Value", write "Bearer ACCESS_TOKEN_HERE", whereas you replace "ACCESS_TOKEN_HERE" with a copy+pasted access_token value that you just generated via the usual POST method calls you were doing up until now in the tutorial via Postman. Also! Make sure you copy+paste the ACCESS token, and not the id_token by accident. If necessary, remove the trailing paragraph character at the end of your token pasting.

For "Add to", select "Header" from the dropdown box.

To illustrate ALL of the above:

Image description

For the record, I did not need to change nor adapt any of the tutorial code in order for this to work, unlike some of the people in the comments. I am using OpenIddict 5.5.0, which is currently the latest version mid 2024.

Once again, this is a great tutorial! Thank you for writing it. I would consider adding a Postman screenshot similar to mine into the end of the tutorial, though, which would help quite a lot of people.

Collapse
 
svenni profile image
svenniuwe

For me it is working without any change.

Collapse
 
xargon180 profile image
xargon180

Same here. Worked without any change. Realy great article.