/// <summary> /// Determines whether the authentication ticket contains at least one scope. /// </summary> /// <param name="ticket">The authentication ticket.</param> /// <returns><c>true</c> if the ticket contains at least one scope.</returns> public static bool HasScope([NotNull] this AuthenticationTicket ticket) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } return(ticket.HasProperty(OpenIdConnectConstants.Properties.Scopes)); }
public void HasProperty_ReturnsExpectedResult(string value, bool result) { // Arrange var ticket = new AuthenticationTicket( new ClaimsIdentity(), new AuthenticationProperties()); ticket.Properties.Dictionary["property"] = value; // Act and assert Assert.Equal(result, ticket.HasProperty("property")); Assert.Equal(result, ticket.Properties.HasProperty("property")); }
private async Task <bool> HandleSignInAsync(AuthenticationTicket ticket) { // Extract the OpenID Connect request from the ASP.NET context. // If it cannot be found or doesn't correspond to an authorization // or a token request, throw an InvalidOperationException. var request = Context.GetOpenIdConnectRequest(); if (request == null || (!request.IsAuthorizationRequest() && !request.IsTokenRequest())) { throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint."); } // Note: if an OpenID Connect response was already generated, throw an exception. var response = Context.GetOpenIdConnectResponse(); if (response != null) { throw new InvalidOperationException("An OpenID Connect response has already been sent."); } if (!ticket.Principal.HasClaim(claim => claim.Type == ClaimTypes.NameIdentifier)) { throw new InvalidOperationException("The authentication ticket was rejected because it didn't " + "contain the mandatory ClaimTypes.NameIdentifier claim."); } // Prepare a new OpenID Connect response. response = new OpenIdConnectResponse(); if (request.IsAuthorizationRequest()) { response.RedirectUri = request.RedirectUri; response.State = request.State; // Keep the code_challenge, code_challenge_method, nonce and redirect_uri parameters for later comparison. ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, request.CodeChallenge); ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, request.CodeChallengeMethod); ticket.SetProperty(OpenIdConnectConstants.Properties.Nonce, request.Nonce); ticket.SetProperty(OpenIdConnectConstants.Properties.RedirectUri, request.RedirectUri); } // Copy the confidentiality level associated with the request to the authentication ticket. if (!ticket.HasProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel)) { ticket.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel, request.GetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel)); } // Always include the "openid" scope when the developer doesn't explicitly call SetScopes. // Note: the application is allowed to specify a different "scopes": in this case, // don't replace the "scopes" property stored in the authentication ticket. if (!ticket.HasProperty(OpenIdConnectConstants.Properties.Scopes) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId)) { ticket.SetProperty(OpenIdConnectConstants.Properties.Scopes, OpenIdConnectConstants.Scopes.OpenId); } // When a "resources" property cannot be found in the ticket, infer it from the "audiences" property. if (!ticket.HasProperty(OpenIdConnectConstants.Properties.Resources)) { ticket.SetProperty(OpenIdConnectConstants.Properties.Resources, ticket.GetProperty(OpenIdConnectConstants.Properties.Audiences)); } // Only return an authorization code if the request is an authorization request and has response_type=code. if (request.IsAuthorizationRequest() && request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code)) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); // properties.IssuedUtc and properties.ExpiresUtc are always // explicitly set to null to avoid aligning the expiration date // of the authorization code with the lifetime of the other tokens. properties.IssuedUtc = properties.ExpiresUtc = null; response.Code = await SerializeAuthorizationCodeAsync(ticket.Principal, properties, request, response); } // Only return an access token if the request is a token request // or an authorization request that specifies response_type=token. if (request.IsTokenRequest() || (request.IsAuthorizationRequest() && request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token))) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); // When receiving a grant_type=refresh_token request, determine whether the client application // requests a limited set of scopes/resources and replace the corresponding properties if necessary. // Note: at this stage, request.GetResources() cannot return more items than the ones that were initially granted // by the resource owner as the "resources" parameter is always validated when receiving the token request. if (request.IsTokenRequest() && request.IsRefreshTokenGrantType()) { if (!string.IsNullOrEmpty(request.Resource)) { // Replace the resources initially granted by the resources listed by the client application in the token request. // Note: request.GetResources() automatically removes duplicate entries, so additional filtering is not necessary. properties.SetProperty(OpenIdConnectConstants.Properties.Resources, string.Join(" ", request.GetResources())); } if (!string.IsNullOrEmpty(request.Scope)) { // Replace the scopes initially granted by the scopes listed by the client application in the token request. // Note: request.GetScopes() automatically removes duplicate entries, so additional filtering is not necessary. properties.SetProperty(OpenIdConnectConstants.Properties.Scopes, string.Join(" ", request.GetScopes())); } } var resources = properties.GetProperty(OpenIdConnectConstants.Properties.Resources); if (request.IsAuthorizationCodeGrantType() || (!string.IsNullOrEmpty(resources) && !string.IsNullOrEmpty(request.Resource) && !string.Equals(request.Resource, resources, StringComparison.Ordinal))) { response.Resource = resources; } var scopes = properties.GetProperty(OpenIdConnectConstants.Properties.Scopes); if (request.IsAuthorizationCodeGrantType() || (!string.IsNullOrEmpty(scopes) && !string.IsNullOrEmpty(request.Scope) && !string.Equals(request.Scope, scopes, StringComparison.Ordinal))) { response.Scope = scopes; } response.TokenType = OpenIdConnectConstants.TokenTypes.Bearer; response.AccessToken = await SerializeAccessTokenAsync(ticket.Principal, properties, request, response); // properties.ExpiresUtc is automatically set by SerializeAccessTokenAsync but the end user // is free to set a null value directly in the SerializeAccessToken event. if (properties.ExpiresUtc.HasValue && properties.ExpiresUtc > Options.SystemClock.UtcNow) { var lifetime = properties.ExpiresUtc.Value - Options.SystemClock.UtcNow; response.ExpiresIn = (long)(lifetime.TotalSeconds + .5); } } // Only return a refresh token if the request is a token request that specifies scope=offline_access. if (request.IsTokenRequest() && ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess)) { // Note: when sliding expiration is enabled, don't return a new refresh token, // unless the token request is not a grant_type=refresh_token request. if (!request.IsRefreshTokenGrantType() || Options.UseSlidingExpiration) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.RefreshToken = await SerializeRefreshTokenAsync(ticket.Principal, properties, request, response); } } // Only return an identity token if the openid scope was requested and granted // to avoid generating and returning an unnecessary token to pure OAuth2 clients. if (ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId)) { // Note: don't return an identity token if the request is an // authorization request that doesn't use response_type=id_token. if (request.IsTokenRequest() || request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken)) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); // properties.IssuedUtc and properties.ExpiresUtc are always // explicitly set to null to avoid aligning the expiration date // of the identity token with the lifetime of the other tokens. properties.IssuedUtc = properties.ExpiresUtc = null; response.IdToken = await SerializeIdentityTokenAsync(ticket.Principal, properties, request, response); } } if (request.IsAuthorizationRequest()) { return(await SendAuthorizationResponseAsync(response, ticket)); } return(await SendTokenResponseAsync(response, ticket)); }
private async Task <bool> HandleSignInAsync(AuthenticationTicket ticket) { // Extract the OpenID Connect request from the OWIN context. // If it cannot be found or doesn't correspond to an authorization // or a token request, throw an InvalidOperationException. var request = Context.GetOpenIdConnectRequest(); if (request == null || (!request.IsAuthorizationRequest() && !request.IsTokenRequest())) { throw new InvalidOperationException("An authorization or token response cannot be returned from this endpoint."); } // Note: if a response was already generated, throw an exception. var response = Context.GetOpenIdConnectResponse(); if (response != null) { throw new InvalidOperationException("A response has already been sent."); } if (string.IsNullOrEmpty(ticket.Identity.GetClaim(OpenIdConnectConstants.Claims.Subject))) { throw new InvalidOperationException("The authentication ticket was rejected because " + "the mandatory subject claim was missing."); } Logger.LogTrace("A sign-in operation was triggered: {Claims} ; {Properties}.", ticket.Identity.Claims, ticket.Properties.Dictionary); // Prepare a new OpenID Connect response. response = new OpenIdConnectResponse(); // Copy the confidentiality level associated with the request to the authentication ticket. if (!ticket.HasProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel)) { ticket.SetConfidentialityLevel(request.GetProperty <string>(OpenIdConnectConstants.Properties.ConfidentialityLevel)); } // Always include the "openid" scope when the developer doesn't explicitly call SetScopes. // Note: the application is allowed to specify a different "scopes": in this case, // don't replace the "scopes" property stored in the authentication ticket. if (request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && !ticket.HasScope()) { ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId); } // When a "resources" property cannot be found in the ticket, // infer it from the "audiences" property. if (ticket.HasAudience() && !ticket.HasResource()) { ticket.SetResources(ticket.GetAudiences()); } // Add the validated client_id to the list of authorized presenters, // unless the presenters were explicitly set by the developer. var presenter = request.GetProperty <string>(OpenIdConnectConstants.Properties.ValidatedClientId); if (!string.IsNullOrEmpty(presenter) && !ticket.HasPresenter()) { ticket.SetPresenters(presenter); } var notification = new ProcessSigninResponseContext(Context, Options, ticket, request, response); if (request.IsAuthorizationRequest()) { // By default, return an authorization code if a response type containing code was specified. notification.IncludeAuthorizationCode = request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code); // By default, return an access token if a response type containing token was specified. notification.IncludeAccessToken = request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token); // By default, prevent a refresh token from being returned as the OAuth2 specification // explicitly disallows returning a refresh token from the authorization endpoint. // See https://tools.ietf.org/html/rfc6749#section-4.2.2 for more information. notification.IncludeRefreshToken = false; // By default, return an identity token if a response type containing code // was specified and if the openid scope was explicitly or implicitly granted. notification.IncludeIdentityToken = request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) && ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId); } else { // By default, prevent an authorization code from being returned as this type of token // cannot be issued from the token endpoint in the standard OAuth2/OpenID Connect flows. notification.IncludeAuthorizationCode = false; // By default, always return an access token. notification.IncludeAccessToken = true; // By default, only return a refresh token is the offline_access scope was granted and if // sliding expiration is disabled or if the request is not a grant_type=refresh_token request. notification.IncludeRefreshToken = ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && (Options.UseSlidingExpiration || !request.IsRefreshTokenGrantType()); // By default, only return an identity token if the openid scope was granted. notification.IncludeIdentityToken = ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId); } await Options.Provider.ProcessSigninResponse(notification); if (notification.HandledResponse) { Logger.LogDebug("The sign-in response was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default sign-in handling was skipped from user code."); return(false); } else if (notification.IsRejected) { Logger.LogError("The request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); if (request.IsAuthorizationRequest()) { return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } // Flow the changes made to the ticket. ticket = notification.Ticket; // Ensure an authentication ticket has been provided or return // an error code indicating that the request was rejected. if (ticket == null) { Logger.LogError("The request was rejected because no authentication ticket was provided."); if (request.IsAuthorizationRequest()) { return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.AccessDenied, ErrorDescription = "The authorization was denied by the resource owner." })); } return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The token request was rejected by the authorization server." })); } if (notification.IncludeAuthorizationCode) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.Code = await SerializeAuthorizationCodeAsync(ticket.Identity, properties, request, response); } if (notification.IncludeAccessToken) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); // When receiving a grant_type=refresh_token request, determine whether the client application // requests a limited set of scopes and replace the corresponding properties if necessary. if (!string.IsNullOrEmpty(request.Scope) && request.IsTokenRequest() && request.IsRefreshTokenGrantType()) { Logger.LogDebug("The access token scopes will be limited to the scopes requested " + "by the client application: {Scopes}.", request.GetScopes()); // Replace the scopes initially granted by the scopes listed by the client // application in the token request. Note: request.GetScopes() automatically // removes duplicate entries, so additional filtering is not necessary. properties.SetProperty(OpenIdConnectConstants.Properties.Scopes, new JArray(request.GetScopes()).ToString(Formatting.None)); } var scopes = ticket.GetScopes(); if ((request.IsTokenRequest() && request.IsAuthorizationCodeGrantType()) || !new HashSet <string>(scopes).SetEquals(request.GetScopes())) { response.Scope = string.Join(" ", scopes); } response.TokenType = OpenIdConnectConstants.TokenTypes.Bearer; response.AccessToken = await SerializeAccessTokenAsync(ticket.Identity, properties, request, response); // properties.ExpiresUtc is automatically set by SerializeAccessTokenAsync but the end user // is free to set a null value directly in the SerializeAccessToken event. if (properties.ExpiresUtc.HasValue && properties.ExpiresUtc > Options.SystemClock.UtcNow) { var lifetime = properties.ExpiresUtc.Value - Options.SystemClock.UtcNow; response.ExpiresIn = (long)(lifetime.TotalSeconds + .5); } } if (notification.IncludeRefreshToken) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.RefreshToken = await SerializeRefreshTokenAsync(ticket.Identity, properties, request, response); } if (notification.IncludeIdentityToken) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.IdToken = await SerializeIdentityTokenAsync(ticket.Identity, properties, request, response); } if (request.IsAuthorizationRequest()) { return(await SendAuthorizationResponseAsync(response, ticket)); } return(await SendTokenResponseAsync(response, ticket)); }
private async Task <bool> HandleSignInAsync(AuthenticationTicket ticket) { // Extract the OpenID Connect request from the ASP.NET Core context. // If it cannot be found or doesn't correspond to an authorization // or a token request, throw an InvalidOperationException. var request = Context.GetOpenIdConnectRequest(); if (request == null || (!request.IsAuthorizationRequest() && !request.IsTokenRequest())) { throw new InvalidOperationException("An authorization or token response cannot be returned from this endpoint."); } // Note: if a response was already generated, throw an exception. var response = Context.GetOpenIdConnectResponse(); if (response != null || Response.HasStarted) { throw new InvalidOperationException("A response has already been sent."); } if (string.IsNullOrEmpty(ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject))) { throw new InvalidOperationException("The authentication ticket was rejected because " + "the mandatory subject claim was missing."); } Logger.LogTrace("A sign-in operation was triggered: {Claims} ; {Properties}.", ticket.Principal.Claims, ticket.Properties.Items); // Prepare a new OpenID Connect response. response = new OpenIdConnectResponse(); // Copy the confidentiality level associated with the request to the authentication ticket. if (!ticket.HasProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel)) { ticket.SetConfidentialityLevel(request.GetProperty <string>(OpenIdConnectConstants.Properties.ConfidentialityLevel)); } // Always include the "openid" scope when the developer doesn't explicitly call SetScopes. // Note: the application is allowed to specify a different "scopes": in this case, // don't replace the "scopes" property stored in the authentication ticket. if (request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && !ticket.HasScope()) { ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId); } // When a "resources" property cannot be found in the ticket, // infer it from the "audiences" property. if (ticket.HasAudience() && !ticket.HasResource()) { ticket.SetResources(ticket.GetAudiences()); } // Add the validated client_id to the list of authorized presenters, // unless the presenters were explicitly set by the developer. var presenter = request.GetProperty <string>(OpenIdConnectConstants.Properties.ValidatedClientId); if (!string.IsNullOrEmpty(presenter) && !ticket.HasPresenter()) { ticket.SetPresenters(presenter); } // Only return an authorization code if the request is an authorization request and has response_type=code. if (request.IsAuthorizationRequest() && request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code)) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.Code = await SerializeAuthorizationCodeAsync(ticket.Principal, properties, request, response); } // Only return an access token if the request is a token request // or an authorization request that specifies response_type=token. if (request.IsTokenRequest() || (request.IsAuthorizationRequest() && request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token))) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); // When receiving a grant_type=refresh_token request, determine whether the client application // requests a limited set of scopes/resources and replace the corresponding properties if necessary. // Note: at this stage, request.GetResources() cannot return more items than the ones that were initially granted // by the resource owner as the "resources" parameter is always validated when receiving the token request. if (request.IsTokenRequest() && request.IsRefreshTokenGrantType()) { if (!string.IsNullOrEmpty(request.Resource)) { Logger.LogDebug("The access token resources will be limited to the resources requested " + "by the client application: {Resources}.", request.GetResources()); // Replace the resources initially granted by the resources listed by the client // application in the token request. Note: request.GetResources() automatically // removes duplicate entries, so additional filtering is not necessary. properties.SetProperty(OpenIdConnectConstants.Properties.Resources, new JArray(request.GetResources()).ToString(Formatting.None)); } if (!string.IsNullOrEmpty(request.Scope)) { Logger.LogDebug("The access token scopes will be limited to the scopes requested " + "by the client application: {Scopes}.", request.GetScopes()); // Replace the scopes initially granted by the scopes listed by the client // application in the token request. Note: request.GetScopes() automatically // removes duplicate entries, so additional filtering is not necessary. properties.SetProperty(OpenIdConnectConstants.Properties.Scopes, new JArray(request.GetScopes()).ToString(Formatting.None)); } } var resources = ticket.GetResources(); if (request.IsAuthorizationCodeGrantType() || !new HashSet <string>(resources).SetEquals(request.GetResources())) { response.Resource = string.Join(" ", resources); } var scopes = ticket.GetScopes(); if (request.IsAuthorizationCodeGrantType() || !new HashSet <string>(scopes).SetEquals(request.GetScopes())) { response.Scope = string.Join(" ", scopes); } response.TokenType = OpenIdConnectConstants.TokenTypes.Bearer; response.AccessToken = await SerializeAccessTokenAsync(ticket.Principal, properties, request, response); // properties.ExpiresUtc is automatically set by SerializeAccessTokenAsync but the end user // is free to set a null value directly in the SerializeAccessToken event. if (properties.ExpiresUtc.HasValue && properties.ExpiresUtc > Options.SystemClock.UtcNow) { var lifetime = properties.ExpiresUtc.Value - Options.SystemClock.UtcNow; response.ExpiresIn = (long)(lifetime.TotalSeconds + .5); } } // Only return a refresh token if the request is a token request that specifies scope=offline_access. if (request.IsTokenRequest() && ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess)) { // Note: when sliding expiration is disabled, don't return a new refresh token, // unless the token request is not a grant_type=refresh_token request. if (Options.UseSlidingExpiration || !request.IsRefreshTokenGrantType()) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.RefreshToken = await SerializeRefreshTokenAsync(ticket.Principal, properties, request, response); } } // Only return an identity token if the openid scope was requested and granted // to avoid generating and returning an unnecessary token to pure OAuth2 clients. if (ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId)) { // Note: don't return an identity token if the request is an // authorization request that doesn't use response_type=id_token. if (request.IsTokenRequest() || request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken)) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.IdToken = await SerializeIdentityTokenAsync(ticket.Principal, properties, request, response); } } if (request.IsAuthorizationRequest()) { return(await SendAuthorizationResponseAsync(response, ticket)); } return(await SendTokenResponseAsync(response, ticket)); }