Versatile OpenID Connect stack for ASP.NET Core and Microsoft.Owin (compatible with ASP.NET 4.6.1)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

244 lines
11 KiB

using System.Security.Claims;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using OpenIddict.Sandbox.AspNetCore.CimdServer.Models;
using OpenIddict.Server.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Sandbox.AspNetCore.CimdServer;
public class AuthorizationController : Controller
{
private readonly IOpenIddictApplicationManager _applicationManager;
private readonly IOpenIddictAuthorizationManager _authorizationManager;
private readonly IOpenIddictScopeManager _scopeManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
public AuthorizationController(
IOpenIddictApplicationManager applicationManager,
IOpenIddictAuthorizationManager authorizationManager,
IOpenIddictScopeManager scopeManager,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager)
{
_applicationManager = applicationManager;
_authorizationManager = authorizationManager;
_scopeManager = scopeManager;
_signInManager = signInManager;
_userManager = userManager;
}
[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.");
// Try to retrieve the user principal stored in the authentication cookie.
// If the cookie is stale (e.g. database was recreated), sign out and re-authenticate.
var result = await HttpContext.AuthenticateAsync();
if (result is { Succeeded: true })
{
var existing = await _userManager.GetUserAsync(result.Principal!);
if (existing is null)
{
// The cookie references a user that no longer exists. Sign out and retry.
await _signInManager.SignOutAsync();
result = AuthenticateResult.NoResult();
}
}
if (result is not { Succeeded: true })
{
// For this demo, auto-sign in the test user to simplify testing.
var user = await _userManager.FindByNameAsync("testuser");
if (user is not null)
{
await _signInManager.SignInAsync(user, isPersistent: false);
// Redirect back to the same URL so the cookie is sent on the next request.
return Redirect(HttpContext.Request.PathBase + HttpContext.Request.Path + HttpContext.Request.QueryString);
}
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in."
}));
}
var userEntity = await _userManager.GetUserAsync(result.Principal!) ??
throw new InvalidOperationException("The user details cannot be retrieved.");
// Auto-approve consent for this demonstrator.
var identity = new ClaimsIdentity(
authenticationType: TokenValidationParameters.DefaultAuthenticationType,
nameType: Claims.Name,
roleType: Claims.Role);
identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(userEntity))
.SetClaim(Claims.Email, await _userManager.GetEmailAsync(userEntity))
.SetClaim(Claims.Name, await _userManager.GetUserNameAsync(userEntity));
identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
// Look up the application (for CIMD clients, this returns a virtual application
// synthesized from the metadata document by the CIMD application manager).
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!);
var applicationId = application is not null ? await _applicationManager.GetIdAsync(application) : null;
// Only create an authorization entry if the application has a database identity
// (CIMD virtual applications return null for GetIdAsync).
if (!string.IsNullOrEmpty(applicationId))
{
var authorization = await _authorizationManager.CreateAsync(
identity: identity,
subject: await _userManager.GetUserIdAsync(userEntity),
client: applicationId,
type: AuthorizationTypes.Permanent,
scopes: identity.GetScopes());
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
}
identity.SetDestinations(GetDestinations);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
[HttpPost("~/connect/token")]
[IgnoreAntiforgeryToken]
[Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username!);
if (user is null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid."
}));
}
var passwordResult = await _signInManager.CheckPasswordSignInAsync(user, request.Password!, lockoutOnFailure: false);
if (!passwordResult.Succeeded)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid."
}));
}
var identity = new ClaimsIdentity(
authenticationType: TokenValidationParameters.DefaultAuthenticationType,
nameType: Claims.Name,
roleType: Claims.Role);
identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
.SetClaim(Claims.Email, await _userManager.GetEmailAsync(user))
.SetClaim(Claims.Name, await _userManager.GetUserNameAsync(user));
identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
identity.SetDestinations(GetDestinations);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
{
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
var user = await _userManager.FindByIdAsync(result.Principal!.GetClaim(Claims.Subject)!);
if (user is null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
}
if (!await _signInManager.CanSignInAsync(user))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
}
var identity = new ClaimsIdentity(result.Principal!.Claims,
authenticationType: TokenValidationParameters.DefaultAuthenticationType,
nameType: Claims.Name,
roleType: Claims.Role);
identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
.SetClaim(Claims.Email, await _userManager.GetEmailAsync(user))
.SetClaim(Claims.Name, await _userManager.GetUserNameAsync(user));
// Preserve the scopes originally granted so refresh tokens continue to be issued.
identity.SetScopes(result.Principal!.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
identity.SetDestinations(GetDestinations);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}
private static IEnumerable<string> GetDestinations(Claim claim)
{
switch (claim.Type)
{
case Claims.Name:
yield return Destinations.AccessToken;
if (claim.Subject!.HasScope(Scopes.Profile))
yield return Destinations.IdentityToken;
yield break;
case Claims.Email:
yield return Destinations.AccessToken;
if (claim.Subject!.HasScope(Scopes.Email))
yield return Destinations.IdentityToken;
yield break;
case "AspNet.Identity.SecurityStamp": yield break;
default:
yield return Destinations.AccessToken;
yield break;
}
}
}