diff --git a/Directory.Packages.props b/Directory.Packages.props index b0f7781e1..ce5dbe46b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,8 +23,8 @@ - - + + diff --git a/samples/Fornax/Fornax.Server/Connect/Authorize.aspx.cs b/samples/Fornax/Fornax.Server/Connect/Authorize.aspx.cs index 89da2ca40..a42b219de 100644 --- a/samples/Fornax/Fornax.Server/Connect/Authorize.aspx.cs +++ b/samples/Fornax/Fornax.Server/Connect/Authorize.aspx.cs @@ -25,226 +25,225 @@ public partial class Authorize : Page public IEnumerable> Parameters { get; private set; } - protected void Page_Load(object sender, EventArgs e) + protected void Page_Load(object sender, EventArgs e) => RegisterAsyncTask(new PageAsyncTask(async () => { - RegisterAsyncTask(new PageAsyncTask(async () => - { - var context = Context.GetOwinContext(); - var request = context.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - - // Retrieve the user principal stored in the authentication cookie. - // If a max_age parameter was provided, ensure that the cookie is not too old. - // If the user principal can't be extracted or the cookie is too old, redirect the user to the login page. - var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); - if (result == null || result.Identity == null || (request.MaxAge != null && result.Properties?.IssuedUtc != null && - DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) - { - context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie); - Visible = false; - return; - } - - // Retrieve the profile of the logged in user. - var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetUserId()) ?? - throw new InvalidOperationException("The user details cannot be retrieved."); - - // Retrieve the application details from the database. - var application = await ApplicationManager.FindByClientIdAsync(request.ClientId) ?? - throw new InvalidOperationException("Details concerning the calling client application cannot be found."); - - // Retrieve the permanent authorizations associated with the user and the calling client application. - var authorizations = await AuthorizationManager.FindAsync( - subject: user.Id, - client : await ApplicationManager.GetIdAsync(application), - status : Statuses.Valid, - type : AuthorizationTypes.Permanent, - scopes : request.GetScopes()).ToListAsync(); - - switch (await ApplicationManager.GetConsentTypeAsync(application)) - { - // If the consent is external (e.g when authorizations are granted by a sysadmin), - // immediately return an error if no authorization can be found in the database. - case ConsentTypes.External when !authorizations.Any(): - context.Authentication.Challenge( - authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, - [OpenIddictServerOwinConstants.Properties.ErrorDescription] = - "The logged in user is not allowed to access this client application." - })); - Visible = false; - return; - - // If the consent is implicit or if an authorization was found, - // return an authorization response without displaying the consent form. - case ConsentTypes.Implicit: - case ConsentTypes.External when authorizations.Any(): - case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent): - // Create the claims-based identity that will be used by OpenIddict to generate tokens. - var identity = new ClaimsIdentity( - authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, - nameType: Claims.Name, - roleType: Claims.Role); - - // Add the claims that will be persisted in the tokens. - identity.SetClaim(Claims.Subject, user.Id) - .SetClaim(Claims.Email, user.Email) - .SetClaim(Claims.Name, user.UserName) - .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); - - // Note: in this sample, the granted scopes match the requested scope - // but you may want to allow the user to uncheck specific scopes. - // For that, simply restrict the list of scopes before calling SetScopes. - identity.SetScopes(request.GetScopes()); - identity.SetResources(await ScopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); - - // Automatically create a permanent authorization to avoid requiring explicit consent - // for future authorization or token requests containing the same scopes. - var authorization = authorizations.LastOrDefault(); - authorization ??= await AuthorizationManager.CreateAsync( - identity: identity, - subject : user.Id, - client : await ApplicationManager.GetIdAsync(application), - type : AuthorizationTypes.Permanent, - scopes : identity.GetScopes()); - - identity.SetAuthorizationId(await AuthorizationManager.GetIdAsync(authorization)); - identity.SetDestinations(GetDestinations); - - context.Authentication.SignIn(identity); - Visible = false; - return; - - // At this point, no authorization was found in the database and an error must be returned - // if the client application specified prompt=none in the authorization request. - case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): - case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): - context.Authentication.Challenge( - authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, - [OpenIddictServerOwinConstants.Properties.ErrorDescription] = - "Interactive user consent is required." - })); - Visible = false; - return; - - // In every other case, render the consent form. - default: - ApplicationName.Text = await ApplicationManager.GetLocalizedDisplayNameAsync(application); - Scope.Text = request.Scope; - - // Flow the request parameters so they can be received by the Accept/Reject actions. - Parameters = string.Equals(Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase) ? - from name in Request.Form.AllKeys - from value in Request.Form.GetValues(name) - select new KeyValuePair(name, value) : - from name in Request.QueryString.AllKeys - from value in Request.QueryString.GetValues(name) - select new KeyValuePair(name, value); - return; - } - })); - } - - // Important: this endpoint MUST be protected CSRF and session fixation attacks. - // In applications generated using a Visual Studio 2012 or higher, antiforgery - // is implemented at the master page level, in the Site.Master.cs file. - protected void Accept(object sender, EventArgs e) - { - if (!IsPostBack) + var context = Context.GetOwinContext(); + var request = context.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + + // Retrieve the user principal stored in the authentication cookie. + // If a max_age parameter was provided, ensure that the cookie is not too old. + // If the user principal can't be extracted or the cookie is too old, redirect the user to the login page. + var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); + if (result == null || result.Identity == null || (request.MaxAge != null && result.Properties?.IssuedUtc != null && + DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) { + context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie); + Visible = false; return; } - RegisterAsyncTask(new PageAsyncTask(async () => + // Retrieve the profile of the logged in user. + var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetUserId()) ?? + throw new InvalidOperationException("The user details cannot be retrieved."); + + // Retrieve the application details from the database. + var application = await ApplicationManager.FindByClientIdAsync(request.ClientId) ?? + throw new InvalidOperationException("Details concerning the calling client application cannot be found."); + + // Retrieve the permanent authorizations associated with the user and the calling client application. + var authorizations = await AuthorizationManager.FindAsync( + subject: user.Id, + client : await ApplicationManager.GetIdAsync(application), + status : Statuses.Valid, + type : AuthorizationTypes.Permanent, + scopes : request.GetScopes()).ToListAsync(); + + switch (await ApplicationManager.GetConsentTypeAsync(application)) { - var context = Context.GetOwinContext(); - var request = context.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - - // Retrieve the user principal stored in the authentication cookie. - var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); - if (result == null || result.Identity == null) - { - context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie); + // If the consent is external (e.g when authorizations are granted by a sysadmin), + // immediately return an error if no authorization can be found in the database. + case ConsentTypes.External when !authorizations.Any(): + context.Authentication.Challenge( + authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, + [OpenIddictServerOwinConstants.Properties.ErrorDescription] = + "The logged in user is not allowed to access this client application." + })); + Visible = false; + return; + + // If the consent is implicit or if an authorization was found, + // return an authorization response without displaying the consent form. + case ConsentTypes.Implicit: + case ConsentTypes.External when authorizations.Any(): + case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent): + // Create the claims-based identity that will be used by OpenIddict to generate tokens. + var identity = new ClaimsIdentity( + authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, + nameType: Claims.Name, + roleType: Claims.Role); + + // Add the claims that will be persisted in the tokens. + identity.SetClaim(Claims.Subject, user.Id) + .SetClaim(Claims.Email, user.Email) + .SetClaim(Claims.Name, user.UserName) + .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); + + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + identity.SetScopes(request.GetScopes()); + identity.SetResources(await ScopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); + + // Automatically create a permanent authorization to avoid requiring explicit consent + // for future authorization or token requests containing the same scopes. + var authorization = authorizations.LastOrDefault(); + authorization ??= await AuthorizationManager.CreateAsync( + identity: identity, + subject : user.Id, + client : await ApplicationManager.GetIdAsync(application), + type : AuthorizationTypes.Permanent, + scopes : identity.GetScopes()); + + identity.SetAuthorizationId(await AuthorizationManager.GetIdAsync(authorization)); + identity.SetDestinations(GetDestinations); + + context.Authentication.SignIn(identity); Visible = false; return; - } - - // Retrieve the profile of the logged in user. - var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetUserId()) ?? - throw new InvalidOperationException("The user details cannot be retrieved."); - - // Retrieve the application details from the database. - var application = await ApplicationManager.FindByClientIdAsync(request.ClientId) ?? - throw new InvalidOperationException("Details concerning the calling client application cannot be found."); - - // Retrieve the permanent authorizations associated with the user and the calling client application. - var authorizations = await AuthorizationManager.FindAsync( - subject: user.Id, - client : await ApplicationManager.GetIdAsync(application), - status : Statuses.Valid, - type : AuthorizationTypes.Permanent, - scopes : request.GetScopes()).ToListAsync(); - - // Note: the same check is already made in the other action but is repeated - // here to ensure a malicious user can't abuse this POST-only endpoint and - // force it to return a valid response without the external authorization. - if (!authorizations.Any() && await ApplicationManager.HasConsentTypeAsync(application, ConsentTypes.External)) - { + + // At this point, no authorization was found in the database and an error must be returned + // if the client application specified prompt=none in the authorization request. + case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): + case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): context.Authentication.Challenge( authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, properties: new AuthenticationProperties(new Dictionary { [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, [OpenIddictServerOwinConstants.Properties.ErrorDescription] = - "The logged in user is not allowed to access this client application." + "Interactive user consent is required." })); Visible = false; return; - } - - // Create the claims-based identity that will be used by OpenIddict to generate tokens. - var identity = new ClaimsIdentity( - authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, - nameType: Claims.Name, - roleType: Claims.Role); - - // Add the claims that will be persisted in the tokens. - identity.SetClaim(Claims.Subject, user.Id) - .SetClaim(Claims.Email, user.Email) - .SetClaim(Claims.Name, user.UserName) - .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); - - // Note: in this sample, the granted scopes match the requested scope - // but you may want to allow the user to uncheck specific scopes. - // For that, simply restrict the list of scopes before calling SetScopes. - identity.SetScopes(request.GetScopes()); - identity.SetResources(await ScopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); - - // Automatically create a permanent authorization to avoid requiring explicit consent - // for future authorization or token requests containing the same scopes. - var authorization = authorizations.LastOrDefault(); - authorization ??= await AuthorizationManager.CreateAsync( - identity: identity, - subject : user.Id, - client : await ApplicationManager.GetIdAsync(application), - type : AuthorizationTypes.Permanent, - scopes : identity.GetScopes()); - - identity.SetAuthorizationId(await AuthorizationManager.GetIdAsync(authorization)); - identity.SetDestinations(GetDestinations); - - context.Authentication.SignIn(identity); + + // In every other case, render the consent form. + default: + ApplicationName.Text = await ApplicationManager.GetLocalizedDisplayNameAsync(application); + Scope.Text = request.Scope; + + // Flow the request parameters so they can be received by the Accept/Reject actions. + Parameters = string.Equals(Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase) ? + from name in Request.Form.AllKeys + from value in Request.Form.GetValues(name) + select new KeyValuePair(name, value) : + from name in Request.QueryString.AllKeys + from value in Request.QueryString.GetValues(name) + select new KeyValuePair(name, value); + return; + } + })); + + // Important: this endpoint MUST be protected against CSRF and session fixation attacks. + // + // In applications generated using a Visual Studio 2012 or higher, antiforgery + // is implemented at the master page level, in the Site.Master.cs file. + protected void Accept(object sender, EventArgs e) => RegisterAsyncTask(new PageAsyncTask(async () => + { + if (!IsPostBack) + { + return; + } + + var context = Context.GetOwinContext(); + var request = context.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + + // Retrieve the user principal stored in the authentication cookie. + var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); + if (result == null || result.Identity == null) + { + context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie); Visible = false; return; - })); - } + } + // Retrieve the profile of the logged in user. + var user = await context.GetUserManager().FindByIdAsync(result.Identity.GetUserId()) ?? + throw new InvalidOperationException("The user details cannot be retrieved."); + + // Retrieve the application details from the database. + var application = await ApplicationManager.FindByClientIdAsync(request.ClientId) ?? + throw new InvalidOperationException("Details concerning the calling client application cannot be found."); + + // Retrieve the permanent authorizations associated with the user and the calling client application. + var authorizations = await AuthorizationManager.FindAsync( + subject: user.Id, + client : await ApplicationManager.GetIdAsync(application), + status : Statuses.Valid, + type : AuthorizationTypes.Permanent, + scopes : request.GetScopes()).ToListAsync(); + + // Note: the same check is already made in the other action but is repeated + // here to ensure a malicious user can't abuse this POST-only endpoint and + // force it to return a valid response without the external authorization. + if (!authorizations.Any() && await ApplicationManager.HasConsentTypeAsync(application, ConsentTypes.External)) + { + context.Authentication.Challenge( + authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerOwinConstants.Properties.Error] = Errors.ConsentRequired, + [OpenIddictServerOwinConstants.Properties.ErrorDescription] = + "The logged in user is not allowed to access this client application." + })); + Visible = false; + return; + } + + // Create the claims-based identity that will be used by OpenIddict to generate tokens. + var identity = new ClaimsIdentity( + authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, + nameType: Claims.Name, + roleType: Claims.Role); + + // Add the claims that will be persisted in the tokens. + identity.SetClaim(Claims.Subject, user.Id) + .SetClaim(Claims.Email, user.Email) + .SetClaim(Claims.Name, user.UserName) + .SetClaims(Claims.Role, (await context.Get().GetRolesAsync(user.Id)).ToImmutableArray()); + + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + identity.SetScopes(request.GetScopes()); + identity.SetResources(await ScopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); + + // Automatically create a permanent authorization to avoid requiring explicit consent + // for future authorization or token requests containing the same scopes. + var authorization = authorizations.LastOrDefault(); + authorization ??= await AuthorizationManager.CreateAsync( + identity: identity, + subject : user.Id, + client : await ApplicationManager.GetIdAsync(application), + type : AuthorizationTypes.Permanent, + scopes : identity.GetScopes()); + + identity.SetAuthorizationId(await AuthorizationManager.GetIdAsync(authorization)); + identity.SetDestinations(GetDestinations); + + context.Authentication.SignIn(identity); + Visible = false; + return; + })); + + // Important: this endpoint MUST be protected against CSRF and session fixation attacks. + // + // In applications generated using a Visual Studio 2012 or higher, antiforgery + // is implemented at the master page level, in the Site.Master.cs file. protected void Deny(object sender, EventArgs e) { if (!IsPostBack)