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.
- Part I: Introduction
- Part II: Create ASPNET project
- Part III: Client Credentials Flow
- Part IV: Authorization Code Flow
- Part V: OpenID Connect
- Part VI: Refresh tokens
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:
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)
};
...
}
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
}
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();
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:
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
});
}
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();
...
}
Now if we do a GET request to /connect/userinfo
and use a valid access token, the response looks something like this:
Next
Next up, we will enable the usage of refresh tokens.
Top comments (9)
Part VI - Integrating External Authorization Providers ... google/azure/facebook/twitter.... etc ? :)
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)
2) Use the validation authentication scheme to authorize instead of the server's scheme
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.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.
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.
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..."
}.
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:
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.
For me it is working without any change.
Same here. Worked without any change. Realy great article.