diff --git a/Directory.Packages.props b/Directory.Packages.props index d68aa1480..3daf2daaf 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,8 +46,8 @@ - - + + @@ -80,14 +80,14 @@ - - - - - - - - + + + + + + + + diff --git a/samples/Balosar/Balosar.Server/Controllers/AuthorizationController.cs b/samples/Balosar/Balosar.Server/Controllers/AuthorizationController.cs index fbbb9e17e..51444502b 100644 --- a/samples/Balosar/Balosar.Server/Controllers/AuthorizationController.cs +++ b/samples/Balosar/Balosar.Server/Controllers/AuthorizationController.cs @@ -57,7 +57,10 @@ public async Task Authorize() // - If the user principal can't be extracted or the cookie is too old. // - If prompt=login was specified by the client application. // - If a max_age parameter was provided and the authentication cookie is not considered "fresh" enough. - var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); + // + // For scenarios where the default authentication handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + var result = await HttpContext.AuthenticateAsync(); if (result == null || !result.Succeeded || request.HasPrompt(Prompts.Login) || (request.MaxAge != null && result.Properties?.IssuedUtc != null && DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) @@ -85,12 +88,12 @@ public async Task Authorize() parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt))); - return Challenge( - authenticationSchemes: IdentityConstants.ApplicationScheme, - properties: new AuthenticationProperties - { - RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) - }); + // For scenarios where the default challenge handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + return Challenge(new AuthenticationProperties + { + RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) + }); } // Retrieve the profile of the logged in user. diff --git a/samples/Dantooine/Dantooine.Server/Controllers/AuthorizationController.cs b/samples/Dantooine/Dantooine.Server/Controllers/AuthorizationController.cs index 268e899c6..7bf7ca648 100644 --- a/samples/Dantooine/Dantooine.Server/Controllers/AuthorizationController.cs +++ b/samples/Dantooine/Dantooine.Server/Controllers/AuthorizationController.cs @@ -57,7 +57,10 @@ public async Task Authorize() // - If the user principal can't be extracted or the cookie is too old. // - If prompt=login was specified by the client application. // - If a max_age parameter was provided and the authentication cookie is not considered "fresh" enough. - var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); + // + // For scenarios where the default authentication handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + var result = await HttpContext.AuthenticateAsync(); if (result == null || !result.Succeeded || request.HasPrompt(Prompts.Login) || (request.MaxAge != null && result.Properties?.IssuedUtc != null && DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) @@ -85,12 +88,12 @@ public async Task Authorize() parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt))); - return Challenge( - authenticationSchemes: IdentityConstants.ApplicationScheme, - properties: new AuthenticationProperties - { - RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) - }); + // For scenarios where the default challenge handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + return Challenge(new AuthenticationProperties + { + RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) + }); } // Retrieve the profile of the logged in user. diff --git a/samples/Dantooine/Dantooine.WebAssembly.Server/Controllers/AuthenticationController.cs b/samples/Dantooine/Dantooine.WebAssembly.Server/Controllers/AuthenticationController.cs index 5bc7c13ee..ade3f6f0d 100644 --- a/samples/Dantooine/Dantooine.WebAssembly.Server/Controllers/AuthenticationController.cs +++ b/samples/Dantooine/Dantooine.WebAssembly.Server/Controllers/AuthenticationController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Mvc; +using OpenIddict.Abstractions; using OpenIddict.Client.AspNetCore; using static OpenIddict.Abstractions.OpenIddictConstants; @@ -31,7 +32,10 @@ public async Task LogOut(string returnUrl) { // Retrieve the identity stored in the local authentication cookie. If it's not available, // this indicate that the user is already logged out locally (or has not logged in yet). - var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + // + // For scenarios where the default authentication handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + var result = await HttpContext.AuthenticateAsync(); if (result is not { Succeeded: true }) { // Only allow local return URLs to prevent open redirect attacks. @@ -39,7 +43,10 @@ public async Task LogOut(string returnUrl) } // Remove the local authentication cookie before triggering a redirection to the remote server. - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + // + // For scenarios where the default sign-out handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + await HttpContext.SignOutAsync(); var properties = new AuthenticationProperties(new Dictionary { @@ -93,47 +100,32 @@ public async Task LogInCallback() // Such identities cannot be used as-is to build an authentication cookie in ASP.NET Core (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.Principal.Identity is not ClaimsIdentity { IsAuthenticated: true }) + if (result.Principal is not ClaimsPrincipal { 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 = new List(result.Principal.Claims - .Select(claim => claim switch - { - // Map the standard "sub" and custom "id" claims to ClaimTypes.NameIdentifier, which is - // the default claim type used by .NET and is required by the antiforgery components. - { Type: Claims.Subject } - => new Claim(ClaimTypes.NameIdentifier, claim.Value, claim.ValueType, claim.Issuer), - - // Map the standard "name" claim to ClaimTypes.Name. - { Type: Claims.Name } - => new Claim(ClaimTypes.Name, claim.Value, claim.ValueType, claim.Issuer), - - _ => claim - }) - .Where(claim => claim switch - { - // Preserve the basic claims that are necessary for the application to work correctly. - { Type: ClaimTypes.NameIdentifier or ClaimTypes.Name } => true, + var identity = new ClaimsIdentity(authenticationType: "ExternalLogin"); - // Don't preserve the other claims. - _ => false - })); + // By default, OpenIddict will automatically try to map the email/name and name identifier claims from + // their standard OpenID Connect or provider-specific equivalent, if available. If needed, additional + // claims can be resolved from the external identity and copied to the final authentication cookie. + identity.SetClaim(ClaimTypes.Email, result.Principal.GetClaim(ClaimTypes.Email)) + .SetClaim(ClaimTypes.Name, result.Principal.GetClaim(ClaimTypes.Name)) + .SetClaim(ClaimTypes.NameIdentifier, result.Principal.GetClaim(ClaimTypes.NameIdentifier)); - var identity = new ClaimsIdentity(claims, - authenticationType: CookieAuthenticationDefaults.AuthenticationScheme, - nameType: ClaimTypes.Name, - roleType: ClaimTypes.Role); + // Preserve the registration identifier to be able to resolve it later. + identity.SetClaim(Claims.Private.RegistrationId, result.Principal.GetClaim(Claims.Private.RegistrationId)); // Build the authentication properties based on the properties that were added when the challenge was triggered. - var properties = new AuthenticationProperties(result.Properties.Items); + var properties = new AuthenticationProperties(result.Properties.Items) + { + RedirectUri = result.Properties.RedirectUri ?? "/" + }; // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. + // // To make cookies less heavy, tokens that are not used are filtered out before creating the cookie. properties.StoreTokens(result.Properties.GetTokens().Where(token => token switch { @@ -148,9 +140,12 @@ OpenIddictClientAspNetCoreConstants.Tokens.BackchannelIdentityToken or _ => false })); - // Ask the cookie authentication handler to return a new cookie and redirect - // the user agent to the return URL stored in the authentication properties. - return SignIn(new ClaimsPrincipal(identity), properties, CookieAuthenticationDefaults.AuthenticationScheme); + // Ask the default sign-in handler to return a new cookie and redirect the + // user agent to the return URL stored in the authentication properties. + // + // For scenarios where the default sign-in handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + return SignIn(new ClaimsPrincipal(identity), properties); } // Note: this controller uses the same callback action for all providers diff --git a/samples/Mimban/Mimban.Server/Program.cs b/samples/Mimban/Mimban.Server/Program.cs index 0c35be639..dc83fed68 100644 --- a/samples/Mimban/Mimban.Server/Program.cs +++ b/samples/Mimban/Mimban.Server/Program.cs @@ -157,7 +157,7 @@ await manager.CreateAsync(new OpenIddictApplicationDescriptor // Resolve the claims extracted by OpenIddict from the userinfo response returned by GitHub. var result = await context.AuthenticateAsync(OpenIddictClientAspNetCoreDefaults.AuthenticationScheme); - var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); + var identity = new ClaimsIdentity(authenticationType: "ExternalLogin"); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, result.Principal!.FindFirst("id")!.Value)); var properties = new AuthenticationProperties @@ -165,14 +165,19 @@ await manager.CreateAsync(new OpenIddictApplicationDescriptor RedirectUri = result.Properties!.RedirectUri }; - return Results.SignIn(new ClaimsPrincipal(identity), properties, CookieAuthenticationDefaults.AuthenticationScheme); + // For scenarios where the default sign-in handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + return Results.SignIn(new ClaimsPrincipal(identity), properties); }); app.MapGet("/authorize", async (HttpContext context) => { // Resolve the claims stored in the cookie created after the GitHub authentication dance. // If the principal cannot be found, trigger a new challenge to redirect the user to GitHub. - var principal = (await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme))?.Principal; + // + // For scenarios where the default authentication handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + var principal = (await context.AuthenticateAsync())?.Principal; if (principal is null) { var properties = new AuthenticationProperties diff --git a/samples/Velusia/Velusia.Client/Controllers/AuthenticationController.cs b/samples/Velusia/Velusia.Client/Controllers/AuthenticationController.cs index 028b9b902..4ea89a7b2 100644 --- a/samples/Velusia/Velusia.Client/Controllers/AuthenticationController.cs +++ b/samples/Velusia/Velusia.Client/Controllers/AuthenticationController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Mvc; +using OpenIddict.Abstractions; using OpenIddict.Client.AspNetCore; using static OpenIddict.Abstractions.OpenIddictConstants; @@ -31,7 +32,10 @@ public async Task LogOut(string returnUrl) { // Retrieve the identity stored in the local authentication cookie. If it's not available, // this indicate that the user is already logged out locally (or has not logged in yet). - var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + // + // For scenarios where the default authentication handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + var result = await HttpContext.AuthenticateAsync(); if (result is not { Succeeded: true }) { // Only allow local return URLs to prevent open redirect attacks. @@ -39,7 +43,10 @@ public async Task LogOut(string returnUrl) } // Remove the local authentication cookie before triggering a redirection to the remote server. - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + // + // For scenarios where the default sign-out handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + await HttpContext.SignOutAsync(); var properties = new AuthenticationProperties(new Dictionary { @@ -93,47 +100,32 @@ public async Task LogInCallback() // Such identities cannot be used as-is to build an authentication cookie in ASP.NET Core (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.Principal.Identity is not ClaimsIdentity { IsAuthenticated: true }) + if (result.Principal is not ClaimsPrincipal { 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 = new List(result.Principal.Claims - .Select(claim => claim switch - { - // Map the standard "sub" and custom "id" claims to ClaimTypes.NameIdentifier, which is - // the default claim type used by .NET and is required by the antiforgery components. - { Type: Claims.Subject } - => new Claim(ClaimTypes.NameIdentifier, claim.Value, claim.ValueType, claim.Issuer), - - // Map the standard "name" claim to ClaimTypes.Name. - { Type: Claims.Name } - => new Claim(ClaimTypes.Name, claim.Value, claim.ValueType, claim.Issuer), - - _ => claim - }) - .Where(claim => claim switch - { - // Preserve the basic claims that are necessary for the application to work correctly. - { Type: ClaimTypes.NameIdentifier or ClaimTypes.Name } => true, + var identity = new ClaimsIdentity(authenticationType: "ExternalLogin"); - // Don't preserve the other claims. - _ => false - })); + // By default, OpenIddict will automatically try to map the email/name and name identifier claims from + // their standard OpenID Connect or provider-specific equivalent, if available. If needed, additional + // claims can be resolved from the external identity and copied to the final authentication cookie. + identity.SetClaim(ClaimTypes.Email, result.Principal.GetClaim(ClaimTypes.Email)) + .SetClaim(ClaimTypes.Name, result.Principal.GetClaim(ClaimTypes.Name)) + .SetClaim(ClaimTypes.NameIdentifier, result.Principal.GetClaim(ClaimTypes.NameIdentifier)); - var identity = new ClaimsIdentity(claims, - authenticationType: CookieAuthenticationDefaults.AuthenticationScheme, - nameType: ClaimTypes.Name, - roleType: ClaimTypes.Role); + // Preserve the registration identifier to be able to resolve it later. + identity.SetClaim(Claims.Private.RegistrationId, result.Principal.GetClaim(Claims.Private.RegistrationId)); // Build the authentication properties based on the properties that were added when the challenge was triggered. - var properties = new AuthenticationProperties(result.Properties.Items); + var properties = new AuthenticationProperties(result.Properties.Items) + { + RedirectUri = result.Properties.RedirectUri ?? "/" + }; // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. + // // To make cookies less heavy, tokens that are not used are filtered out before creating the cookie. properties.StoreTokens(result.Properties.GetTokens().Where(token => token switch { @@ -148,9 +140,12 @@ OpenIddictClientAspNetCoreConstants.Tokens.BackchannelIdentityToken or _ => false })); - // Ask the cookie authentication handler to return a new cookie and redirect - // the user agent to the return URL stored in the authentication properties. - return SignIn(new ClaimsPrincipal(identity), properties, CookieAuthenticationDefaults.AuthenticationScheme); + // Ask the default sign-in handler to return a new cookie and redirect the + // user agent to the return URL stored in the authentication properties. + // + // For scenarios where the default sign-in handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + return SignIn(new ClaimsPrincipal(identity), properties); } // Note: this controller uses the same callback action for all providers diff --git a/samples/Velusia/Velusia.Client/Controllers/HomeController.cs b/samples/Velusia/Velusia.Client/Controllers/HomeController.cs index c37d3023e..f1669887a 100644 --- a/samples/Velusia/Velusia.Client/Controllers/HomeController.cs +++ b/samples/Velusia/Velusia.Client/Controllers/HomeController.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using OpenIddict.Client.AspNetCore; @@ -23,8 +22,9 @@ public HomeController(IHttpClientFactory httpClientFactory) [Authorize, HttpPost("~/")] public async Task Index(CancellationToken cancellationToken) { - var token = await HttpContext.GetTokenAsync(CookieAuthenticationDefaults.AuthenticationScheme, - OpenIddictClientAspNetCoreConstants.Tokens.BackchannelAccessToken); + // For scenarios where the default authentication handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + var token = await HttpContext.GetTokenAsync(OpenIddictClientAspNetCoreConstants.Tokens.BackchannelAccessToken); using var client = _httpClientFactory.CreateClient(); diff --git a/samples/Velusia/Velusia.Server/Controllers/AuthenticationController.cs b/samples/Velusia/Velusia.Server/Controllers/AuthenticationController.cs new file mode 100644 index 000000000..7f84a88a9 --- /dev/null +++ b/samples/Velusia/Velusia.Server/Controllers/AuthenticationController.cs @@ -0,0 +1,96 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; +using OpenIddict.Abstractions; +using OpenIddict.Client.AspNetCore; +using static OpenIddict.Abstractions.OpenIddictConstants; + +namespace Velusia.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. + [HttpGet("~/callback/login/{provider}"), HttpPost("~/callback/login/{provider}"), IgnoreAntiforgeryToken] + public async Task LogInCallback() + { + // Retrieve the authorization data validated by OpenIddict as part of the callback handling. + var result = await HttpContext.AuthenticateAsync(OpenIddictClientAspNetCoreDefaults.AuthenticationScheme); + + // 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 claim. + // + // Such identities cannot be used as-is to build an authentication cookie in ASP.NET Core (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.Principal is not ClaimsPrincipal { 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. + var identity = new ClaimsIdentity(authenticationType: "ExternalLogin"); + + // By default, OpenIddict will automatically try to map the email/name and name identifier claims from + // their standard OpenID Connect or provider-specific equivalent, if available. If needed, additional + // claims can be resolved from the external identity and copied to the final authentication cookie. + identity.SetClaim(ClaimTypes.Email, result.Principal.GetClaim(ClaimTypes.Email)) + .SetClaim(ClaimTypes.Name, result.Principal.GetClaim(ClaimTypes.Name)) + .SetClaim(ClaimTypes.NameIdentifier, result.Principal.GetClaim(ClaimTypes.NameIdentifier)); + + // Preserve the registration identifier to be able to resolve it later. + identity.SetClaim(Claims.Private.RegistrationId, result.Principal.GetClaim(Claims.Private.RegistrationId)); + + // Build the authentication properties based on the properties that were added when the challenge was triggered. + var properties = new AuthenticationProperties(result.Properties.Items) + { + RedirectUri = result.Properties.RedirectUri ?? "/" + }; + + // If needed, the tokens returned by the authorization server can be stored in the authentication cookie. + // To make cookies less heavy, tokens that are not used are filtered out before creating the cookie. + properties.StoreTokens(result.Properties.GetTokens().Where(token => token switch + { + // Preserve the access and refresh tokens returned in the token response, if available. + { + Name: OpenIddictClientAspNetCoreConstants.Tokens.BackchannelAccessToken or + OpenIddictClientAspNetCoreConstants.Tokens.RefreshToken + } => true, + + // Ignore the other tokens. + _ => false + })); + + // Ask the default sign-in handler to return a new cookie and redirect the + // user agent to the return URL stored in the authentication properties. + // + // For scenarios where the default sign-in handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + return SignIn(new ClaimsPrincipal(identity), properties); + } +} diff --git a/samples/Velusia/Velusia.Server/Controllers/AuthorizationController.cs b/samples/Velusia/Velusia.Server/Controllers/AuthorizationController.cs index 277986030..61391c397 100644 --- a/samples/Velusia/Velusia.Server/Controllers/AuthorizationController.cs +++ b/samples/Velusia/Velusia.Server/Controllers/AuthorizationController.cs @@ -57,7 +57,10 @@ public async Task Authorize() // - If the user principal can't be extracted or the cookie is too old. // - If prompt=login was specified by the client application. // - If a max_age parameter was provided and the authentication cookie is not considered "fresh" enough. - var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); + // + // For scenarios where the default authentication handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + var result = await HttpContext.AuthenticateAsync(); if (result == null || !result.Succeeded || request.HasPrompt(Prompts.Login) || (request.MaxAge != null && result.Properties?.IssuedUtc != null && DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) @@ -85,12 +88,12 @@ public async Task Authorize() parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt))); - return Challenge( - authenticationSchemes: IdentityConstants.ApplicationScheme, - properties: new AuthenticationProperties - { - RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) - }); + // For scenarios where the default challenge handler configured in the ASP.NET Core + // authentication options shouldn't be used, a specific scheme can be specified here. + return Challenge(new AuthenticationProperties + { + RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) + }); } // Retrieve the profile of the logged in user. diff --git a/samples/Velusia/Velusia.Server/Startup.cs b/samples/Velusia/Velusia.Server/Startup.cs index 5ced58f69..022bbe7c1 100644 --- a/samples/Velusia/Velusia.Server/Startup.cs +++ b/samples/Velusia/Velusia.Server/Startup.cs @@ -69,6 +69,43 @@ public void ConfigureServices(IServiceCollection services) options.UseQuartz(); }) + // Register the OpenIddict client components. + .AddClient(options => + { + // Note: this sample uses the code flow, but you can enable the other flows if necessary. + options.AllowAuthorizationCodeFlow(); + + // Register the signing and encryption credentials used to protect + // sensitive data like the state tokens produced by OpenIddict. + options.AddDevelopmentEncryptionCertificate() + .AddDevelopmentSigningCertificate(); + + // Register the ASP.NET Core host and configure the ASP.NET Core-specific options. + options.UseAspNetCore() + .EnableStatusCodePagesIntegration() + .EnableRedirectionEndpointPassthrough(); + + // Register the System.Net.Http integration and use the identity of the current + // assembly as a more specific user agent, which can be useful when dealing with + // providers that use the user agent as a way to throttle requests (e.g Reddit). + options.UseSystemNetHttp() + .SetProductInformation(typeof(Startup).Assembly); + + // Register the Web providers integrations. + // + // Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint + // URI per provider, unless all the registered providers support returning a special "iss" + // parameter containing their URL as part of authorization responses. For more information, + // see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4. + options.UseWebProviders() + .AddGitHub(options => + { + options.SetClientId("c4ade52327b01ddacff3") + .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122") + .SetRedirectUri("callback/login/github"); + }); + }) + // Register the OpenIddict server components. .AddServer(options => {