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.
 
 
 
 
 
 

112 lines
6.7 KiB

using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security;
using OpenIddict.Client.Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Sandbox.AspNet.Server.Controllers;
public class AuthenticationController : Controller
{
// Note: this controller uses the same callback action for all providers
// but for users who prefer using a different action per provider,
// the following action can be split into separate actions.
[AcceptVerbs("GET", "POST"), Route("~/callback/login/{provider}")]
public async Task<ActionResult> LogInCallback()
{
var context = HttpContext.GetOwinContext();
// Retrieve the authorization data validated by OpenIddict as part of the callback handling.
var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType);
// Multiple strategies exist to handle OAuth 2.0/OpenID Connect callbacks, each with their pros and cons:
//
// * Directly using the tokens to perform the necessary action(s) on behalf of the user, which is suitable
// for applications that don't need a long-term access to the user's resources or don't want to store
// access/refresh tokens in a database or in an authentication cookie (which has security implications).
// It is also suitable for applications that don't need to authenticate users but only need to perform
// action(s) on their behalf by making API calls using the access token returned by the remote server.
//
// * Storing the external claims/tokens in a database (and optionally keeping the essential claims in an
// authentication cookie so that cookie size limits are not hit). For the applications that use ASP.NET
// Core Identity, the UserManager.SetAuthenticationTokenAsync() API can be used to store external tokens.
//
// Note: in this case, it's recommended to use column encryption to protect the tokens in the database.
//
// * Storing the external claims/tokens in an authentication cookie, which doesn't require having
// a user database but may be affected by the cookie size limits enforced by most browser vendors
// (e.g Safari for macOS and Safari for iOS/iPadOS enforce a per-domain 4KB limit for all cookies).
//
// Note: this is the approach used here, but the external claims are first filtered to only persist
// a few claims like the user identifier. The same approach is used to store the access/refresh tokens.
// Important: if the remote server doesn't support OpenID Connect and doesn't expose a userinfo endpoint,
// result.Principal.Identity will represent an unauthenticated identity and won't contain any user claim.
//
// Such identities cannot be used as-is to build an authentication cookie in ASP.NET (as the
// antiforgery stack requires at least a name claim to bind CSRF cookies to the user's identity) but
// the access/refresh tokens can be retrieved using result.Properties.GetTokens() to make API calls.
if (result is not { Identity.IsAuthenticated: true })
{
throw new InvalidOperationException("The external authorization data cannot be used for authentication.");
}
// Build an identity based on the external claims and that will be used to create the authentication cookie.
//
// By default, all claims extracted during the authorization dance are available. The claims collection stored
// in the cookie can be filtered out or mapped to different names depending the claim name or its issuer.
var claims = result.Identity.Claims.Where(claim => claim.Type is ClaimTypes.NameIdentifier or ClaimTypes.Name
//
// Preserve the registration details to be able to resolve them later.
//
or Claims.Private.RegistrationId or Claims.Private.ProviderName
//
// The ASP.NET 4.x antiforgery module requires preserving the "identityprovider" claim.
//
or "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider");
// Note: when using external authentication providers with ASP.NET Identity,
// the user identity MUST be added to the external authentication cookie scheme.
var identity = new ClaimsIdentity(claims,
authenticationType: DefaultAuthenticationTypes.ExternalCookie,
nameType: ClaimTypes.Name,
roleType: ClaimTypes.Role);
// Build the authentication properties based on the properties that were added when the challenge was triggered.
var properties = new AuthenticationProperties(result.Properties.Dictionary
.Where(item => item.Key is
// Preserve the return URL.
".redirect" or
// If needed, the tokens returned by the authorization server can be stored in the authentication cookie.
OpenIddictClientOwinConstants.Tokens.BackchannelAccessToken or
OpenIddictClientOwinConstants.Tokens.RefreshToken)
.ToDictionary(pair => pair.Key, pair => pair.Value))
{
// Set the creation and expiration dates of the ticket to null to decorrelate the lifetime
// of the resulting authentication cookie from the lifetime of the identity token returned by
// the authorization server (if applicable). In this case, the expiration date time will be
// automatically computed by the cookie handler using the lifetime configured in the options.
//
// Applications that prefer binding the lifetime of the ticket stored in the authentication cookie
// to the identity token returned by the identity provider can remove or comment these two lines:
IssuedUtc = null,
ExpiresUtc = null,
// Note: this flag controls whether the authentication cookie that will be returned to the
// browser will be treated as a session cookie (i.e destroyed when the browser is closed)
// or as a persistent cookie. In both cases, the lifetime of the authentication ticket is
// always stored as protected data, preventing malicious users from trying to use an
// authentication cookie beyond the lifetime of the authentication ticket itself.
IsPersistent = false
};
context.Authentication.SignIn(properties, identity);
return Redirect(properties.RedirectUri ?? "/");
}
}