dotnet new web --name AuthorizationServer
dotnet dev-certs https --trust
Use cookie authentication without ASP.NET Core Identity: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0
Login/Logout buttons are working now.
dotnet add package OpenIddict
dotnet add package OpenIddict.AspNetCore
dotnet add package OpenIddict.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.InMemory
builder.Services.AddDbContext<DbContext>(options =>
{
// Configure the context to use an in-memory store.
options.UseInMemoryDatabase(nameof(DbContext));
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();
});
builder.Services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the EF Core stores/models.
options
.UseEntityFrameworkCore()
.UseDbContext<DbContext>();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
options.AllowClientCredentialsFlow();
options.SetTokenEndpointUris("/connect/token");
// Encryption and signing of tokens (DEVELOPMENT ONLY!)
options
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey();
// Register scopes (permissions)
options.RegisterScopes("api");
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options
.UseAspNetCore()
.EnableTokenEndpointPassthrough();
});
Check implementation: https://localhost:5001/.well-known/openid-configuration
Request-Headers according https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
Get access token, using Authentication tab in Postman: https://learning.postman.com/docs/sending-requests/authorization/#oauth-20
Manually Post in Postman to get Access Token:
curl --location --request POST 'https://localhost:5001/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=postman' \
--data-urlencode 'client_secret=postman-secret'
Result:
{
"access_token": "eyJhbGciOiJSU0EtT0FF...",
"token_type": "Bearer",
"expires_in": 3599
}
Disable token encryption
options
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.DisableAccessTokenEncryption(); // disable token encryption
Check token in https://jwt.io/
options
.AllowAuthorizationCodeFlow()
.RequireProofKeyForCodeExchange();
options
...
.SetAuthorizationEndpointUris("/connect/authorize");
options
...
.EnableAuthorizationEndpointPassthrough();
[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Retrieve the user principal stored in the authentication cookie.
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
// If the user principal can't be extracted, redirect the user to the login page.
if (!result.Succeeded)
{
return Challenge(
authenticationSchemes: CookieAuthenticationDefaults.AuthenticationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
});
}
// Create a new claims principal
var claims = new List<Claim>
{
// 'subject' claim which is required
new Claim(OpenIddictConstants.Claims.Subject, result?.Principal?.Identity?.Name ?? "n/a"),
new Claim("some claim", "some value").SetDestinations(OpenIddictConstants.Destinations.AccessToken)
};
var claimsIdentity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
// Set requested scopes (this is not done automatically)
claimsPrincipal.SetScopes(request.GetScopes());
// Signing in with the OpenIddict authentiction scheme trigger OpenIddict to issue a code (which can be exchanged for an access token)
return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
[HttpPost("~/connect/token"), Produces("application/json")]
public async Task<IActionResult> Exchange()
{
...
else if (request.IsAuthorizationCodeGrantType())
{
// Retrieve the claims principal stored in the authorization code
claimsPrincipal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
}
...
}
Get Access token in Postman, using PKCE:
Error:
error:invalid_request
error_description:The specified 'redirect_uri' is not valid for this client application.
error_uri:https://documentation.openiddict.com/errors/ID2043
Add redirect URI:
RedirectUris = { new Uri("https://oauth.pstmn.io/v1/callback") },
Add OIDC claim
new Claim(OpenIddictConstants.Claims.Email, "some@email").SetDestinations(OpenIddictConstants.Destinations.IdentityToken)
Add config
options
...
.SetUserinfoEndpointUris("/connect/userinfo");
options
...
.EnableUserinfoEndpointPassthrough();
See new userinfo link in https://localhost:5001/.well-known/openid-configuration
Add Endpoint:
[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
});
}
This endpoint is authorized. Enabled Authorization:
app.UseAuthorization();
Call secure endpoint https://localhost:5001/connect/userinfo in Postman, using an Access token:
- Get Access Token (Button 'Get New Access Token')
- Login to Server (first execution only)
- redirects back to postman: see response, press button "Use Token"
- Get Userinfo (Button 'Send')
Manually:
curl --location --request GET 'https://localhost:5001/connect/userinfo' --header 'Authorization: Bearer eyJhbGciOiJSU...
Get Access and Refersh Tokens:
Use Refresh Token: