private async Task <string> SerializeAuthorizationCodeAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Note: claims in authorization codes are never filtered as they are supposed to be opaque: // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring // that subsequent access and identity tokens are correctly filtered. // Create a new ticket containing the updated properties. var ticket = new AuthenticationTicket(identity, properties); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + (ticket.GetAuthorizationCodeLifetime() ?? Options.AuthorizationCodeLifetime); ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode); // Associate a random identifier with the authorization code. ticket.SetTicketId(Guid.NewGuid().ToString()); // Store the code_challenge, code_challenge_method and nonce parameters for later comparison. ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, request.CodeChallenge) .SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, request.CodeChallengeMethod) .SetProperty(OpenIdConnectConstants.Properties.Nonce, request.Nonce); // Store the original redirect_uri sent by the client application for later comparison. ticket.SetProperty(OpenIdConnectConstants.Properties.RedirectUri, request.GetProperty <string>(OpenIdConnectConstants.Properties.OriginalRedirectUri)); // Remove the unwanted properties from the authentication ticket. ticket.RemoveProperty(OpenIdConnectConstants.Properties.AuthorizationCodeLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.ClientId); var notification = new SerializeAuthorizationCodeContext(Context, Options, request, response, ticket) { DataFormat = Options.AuthorizationCodeFormat }; await Options.Provider.SerializeAuthorizationCode(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.AuthorizationCode)) { return(notification.AuthorizationCode); } else if (notification.Skipped) { return(null); } if (notification.DataFormat == null) { return(null); } return(notification.DataFormat.Protect(ticket)); }
private async Task <string> SerializeAuthorizationCodeAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectRequest request, OpenIdConnectResponse response) { // Note: claims in authorization codes are never filtered as they are supposed to be opaque: // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring // that subsequent access and identity tokens are correctly filtered. // Create a new ticket containing the updated properties. var ticket = new AuthenticationTicket(identity, properties); ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow; ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc; ticket.Properties.ExpiresUtc += ticket.GetAuthorizationCodeLifetime() ?? Options.AuthorizationCodeLifetime; // Associate a random identifier with the authorization code. ticket.SetTokenId(Guid.NewGuid().ToString()); // Store the code_challenge, code_challenge_method and nonce parameters for later comparison. ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, request.CodeChallenge) .SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, request.CodeChallengeMethod) .SetProperty(OpenIdConnectConstants.Properties.Nonce, request.Nonce); // Store the original redirect_uri sent by the client application for later comparison. ticket.SetProperty(OpenIdConnectConstants.Properties.OriginalRedirectUri, request.GetProperty <string>(OpenIdConnectConstants.Properties.OriginalRedirectUri)); // Remove the unwanted properties from the authentication ticket. ticket.RemoveProperty(OpenIdConnectConstants.Properties.AuthorizationCodeLifetime) .RemoveProperty(OpenIdConnectConstants.Properties.TokenUsage); var notification = new SerializeAuthorizationCodeContext(Context, Options, request, response, ticket) { DataFormat = Options.AuthorizationCodeFormat }; await Options.Provider.SerializeAuthorizationCode(notification); if (notification.IsHandled || !string.IsNullOrEmpty(notification.AuthorizationCode)) { return(notification.AuthorizationCode); } if (notification.DataFormat == null) { throw new InvalidOperationException("A data formatter must be provided."); } var result = notification.DataFormat.Protect(ticket); Logger.LogTrace("A new authorization code was successfully generated using " + "the specified data format: {Code} ; {Claims} ; {Properties}.", result, ticket.Identity.Claims, ticket.Properties.Dictionary); return(result); }
public void Copy_ReturnsDifferentTicketInstance() { // Arrange var identity = new ClaimsIdentity(); identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Name, "Bob le Bricoleur")); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), nameof(AuthenticationTicket)); ticket.SetProperty("property", "value"); // Act var copy = ticket.Copy(); copy.Principal.Identities.First().AddClaim(new Claim("clone_claim", "value")); copy.SetProperty("clone_property", "value"); // Assert Assert.NotSame(ticket, copy); Assert.Null(ticket.Principal.FindFirst("clone_claim")); Assert.NotEqual(ticket.Properties.Items, copy.Properties.Items); }
public async Task HandleRevocationRequest_RequestIsRejectedWhenTokenIsAnAccessTokenIfReferenceTokensAreDisabled() { // Arrange var ticket = new AuthenticationTicket( new ClaimsPrincipal(), new AuthenticationProperties(), OpenIddictServerDefaults.AuthenticationScheme); ticket.SetProperty(OpenIddictConstants.Properties.InternalTokenId, "3E228451-1555-46F7-A471-951EFBA23A56"); ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AccessToken); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("SlAV32hkKG")) .Returns(ticket); var server = CreateAuthorizationServer(builder => { builder.Configure(options => options.AccessTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest { Token = "SlAV32hkKG" }); // Assert Assert.Equal(OpenIdConnectConstants.Errors.UnsupportedTokenType, response.Error); Assert.Equal("The specified token cannot be revoked.", response.ErrorDescription); format.Verify(mock => mock.Unprotect("SlAV32hkKG"), Times.Once()); }
public async Task ValidateToken_ReturnsValidResultForValidAuthorization() { // Arrange var authorization = new OpenIddictAuthorization(); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("valid-token")) .Returns(delegate { var identity = new ClaimsIdentity(OpenIddictValidationDefaults.AuthenticationScheme); identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Subject, "Fabrikam")); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), OpenIddictValidationDefaults.AuthenticationScheme); ticket.SetProperty(OpenIddictConstants.Properties.InternalAuthorizationId, "5230CBAD-89F9-4C3F-B48C-9253B6FB8620"); return(ticket); }); var manager = CreateAuthorizationManager(instance => { instance.Setup(mock => mock.FindByIdAsync("5230CBAD-89F9-4C3F-B48C-9253B6FB8620", It.IsAny <CancellationToken>())) .ReturnsAsync(authorization); instance.Setup(mock => mock.IsValidAsync(authorization, It.IsAny <CancellationToken>())) .ReturnsAsync(true); }); var server = CreateResourceServer(builder => { builder.Services.AddSingleton(manager); builder.EnableAuthorizationValidation(); builder.Configure(options => { options.AccessTokenFormat = format.Object; options.UseReferenceTokens = false; }); }); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Get, "/"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); // Act var response = await client.SendAsync(request); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("5230CBAD-89F9-4C3F-B48C-9253B6FB8620", It.IsAny <CancellationToken>()), Times.Once()); Mock.Get(manager).Verify(mock => mock.IsValidAsync(authorization, It.IsAny <CancellationToken>()), Times.Once()); }
private async Task CreateAuthorizationAsync( [NotNull] AuthenticationTicket ticket, [NotNull] OpenIddictOptions options, [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request) { var descriptor = new OpenIddictAuthorizationDescriptor { Principal = ticket.Principal, Status = OpenIddictConstants.Statuses.Valid, Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject), Type = OpenIddictConstants.AuthorizationTypes.AdHoc }; foreach (var property in ticket.Properties.Items) { descriptor.Properties.Add(property); } foreach (var scope in ticket.GetScopes()) { descriptor.Scopes.Add(scope); } // If the client application is known, bind it to the authorization. if (!string.IsNullOrEmpty(request.ClientId)) { var application = await Applications.FindByClientIdAsync(request.ClientId, context.RequestAborted); if (application == null) { throw new InvalidOperationException("The client application cannot be retrieved from the database."); } descriptor.ApplicationId = await Applications.GetIdAsync(application, context.RequestAborted); } var authorization = await Authorizations.CreateAsync(descriptor, context.RequestAborted); if (authorization != null) { var identifier = await Authorizations.GetIdAsync(authorization, context.RequestAborted); if (string.IsNullOrEmpty(request.ClientId)) { Logger.LogInformation("An ad hoc authorization was automatically created and " + "associated with an unknown application: {Identifier}.", identifier); } else { Logger.LogInformation("An ad hoc authorization was automatically created and " + "associated with the '{ClientId}' application: {Identifier}.", request.ClientId, identifier); } // Attach the unique identifier of the ad hoc authorization to the authentication ticket // so that it is attached to all the derived tokens, allowing batched revocations support. ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, identifier); } }
/// <summary> /// Sets the unique identifier associated with the authentication ticket. /// </summary> /// <param name="ticket">The authentication ticket.</param> /// <param name="identifier">The unique identifier to store.</param> /// <returns>The authentication ticket.</returns> public static AuthenticationTicket SetTicketId([NotNull] this AuthenticationTicket ticket, string identifier) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } return(ticket.SetProperty(OpenIdConnectConstants.Properties.TicketId, identifier)); }
/// <summary> /// Sets the usage of the token in the authentication ticket. /// </summary> /// <param name="ticket">The authentication ticket.</param> /// <param name="usage">The usage of the token.</param> /// <returns>The authentication ticket.</returns> public static AuthenticationTicket SetUsage([NotNull] this AuthenticationTicket ticket, string usage) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } return(ticket.SetProperty(OpenIdConnectConstants.Properties.Usage, usage)); }
/// <summary> /// Sets the internal token identifier associated with the authentication ticket. /// Note: the identifier MUST correspond to a valid token entry in the database. /// </summary> /// <param name="ticket">The authentication ticket.</param> /// <param name="identifier">The internal token identifier.</param> /// <returns>The authentication ticket.</returns> public static AuthenticationTicket SetInternalTokenId( [NotNull] this AuthenticationTicket ticket, [CanBeNull] string identifier) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } return(ticket.SetProperty(KeystoneConstants.Properties.InternalTokenId, identifier)); }
private async Task CreateAuthorizationAsync([NotNull] AuthenticationTicket ticket, [NotNull] OpenIddictServerOptions options, [NotNull] OpenIdConnectRequest request) { var descriptor = new OpenIddictAuthorizationDescriptor { Principal = ticket.Principal, Status = OpenIddictConstants.Statuses.Valid, Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject), Type = OpenIddictConstants.AuthorizationTypes.AdHoc }; foreach (var property in ticket.Properties.Items) { descriptor.Properties.Add(property); } foreach (var scope in ticket.GetScopes()) { descriptor.Scopes.Add(scope); } // If the client application is known, bind it to the authorization. if (!string.IsNullOrEmpty(request.ClientId)) { var application = request.GetProperty($"{OpenIddictConstants.Properties.Application}:{request.ClientId}"); Debug.Assert(application != null, "The client application shouldn't be null."); descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); } var authorization = await _authorizationManager.CreateAsync(descriptor); if (authorization != null) { var identifier = await _authorizationManager.GetIdAsync(authorization); if (string.IsNullOrEmpty(request.ClientId)) { _logger.LogInformation("An ad hoc authorization was automatically created and " + "associated with an unknown application: {Identifier}.", identifier); } else { _logger.LogInformation("An ad hoc authorization was automatically created and " + "associated with the '{ClientId}' application: {Identifier}.", request.ClientId, identifier); } // Attach the unique identifier of the ad hoc authorization to the authentication ticket // so that it is attached to all the derived tokens, allowing batched revocations support. ticket.SetProperty(OpenIddictConstants.Properties.InternalAuthorizationId, identifier); } }
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenClientIsNotAValidAudience() { // Arrange var identity = new ClaimsIdentity(OpenIddictServerDefaults.AuthenticationScheme); identity.AddClaim(OpenIddictConstants.Claims.Subject, "Bob le Bricoleur"); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), OpenIddictServerDefaults.AuthenticationScheme); ticket.SetAudiences("Contoso"); ticket.SetProperty(OpenIddictConstants.Properties.InternalTokenId, "3E228451-1555-46F7-A471-951EFBA23A56"); ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AccessToken); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) .Returns(ticket); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .Returns(new ValueTask <string>(OpenIddictConstants.ClientTypes.Confidential)); instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny <CancellationToken>())) .ReturnsAsync(true); })); builder.Configure(options => options.AccessTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Single(response.GetParameters()); Assert.False((bool)response[OpenIddictConstants.Claims.Active]); }
/// <summary> /// Sets the refresh token lifetime associated with the authentication ticket. /// </summary> /// <param name="ticket">The authentication ticket.</param> /// <param name="lifetime">The refresh token lifetime to store.</param> /// <returns>The authentication ticket.</returns> public static AuthenticationTicket SetRefreshTokenLifetime([NotNull] this AuthenticationTicket ticket, TimeSpan?lifetime) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } var value = lifetime?.ToString("c", CultureInfo.InvariantCulture); return(ticket.SetProperty(OpenIdConnectConstants.Properties.RefreshTokenLifetime, value)); }
public void SetProperty_AddsExpectedProperty() { // Arrange var ticket = new AuthenticationTicket( new ClaimsIdentity(), new AuthenticationProperties()); // Act ticket.SetProperty("property", "value"); // Assert Assert.Equal("value", ticket.GetProperty("property")); }
public void SetProperty_IgnoreEmptyEnumerations() { // Arrange var ticket = new AuthenticationTicket( new ClaimsIdentity(), new AuthenticationProperties()); // Act ticket.SetProperty("property", new string[0]); // Assert Assert.Null(ticket.GetProperty("property")); }
public void SetProperty_SupportsMultipleStrings() { // Arrange var ticket = new AuthenticationTicket( new ClaimsIdentity(), new AuthenticationProperties()); // Act ticket.SetProperty("property", new[] { "value-1", "value-2" }); // Assert Assert.Equal(@"[""value-1"",""value-2""]", ticket.GetProperty("property")); }
public void SetProperty_IsCaseSensitive() { // Arrange var ticket = new AuthenticationTicket( new ClaimsIdentity(), new AuthenticationProperties()); // Act ticket.SetProperty("PROPERTY", "value"); // Assert Assert.Null(ticket.GetProperty("property")); }
public void SetProperty_RemovesEmptyProperty() { // Arrange var ticket = new AuthenticationTicket( new ClaimsIdentity(), new AuthenticationProperties()); // Act ticket.SetProperty("property", string.Empty); // Assert Assert.Null(ticket.GetProperty("property")); }
public async Task HandleRevocationRequest_TokenIsSuccessfullyRevoked() { // Arrange var ticket = new AuthenticationTicket( new ClaimsPrincipal(), new AuthenticationProperties(), OpenIddictServerDefaults.AuthenticationScheme); ticket.SetProperty(OpenIddictConstants.Properties.InternalTokenId, "3E228451-1555-46F7-A471-951EFBA23A56"); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("SlAV32hkKG")) .Returns(ticket); var token = new OpenIddictToken(); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny <CancellationToken>())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny <CancellationToken>())) .Returns(new ValueTask <string>("3E228451-1555-46F7-A471-951EFBA23A56")); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(manager); builder.Configure(options => options.RefreshTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest { Token = "SlAV32hkKG" }); // Assert Assert.Empty(response.GetParameters()); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny <CancellationToken>()), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny <CancellationToken>()), Times.Once()); }
public void Copy_ReturnsIdenticalTicket() { // Arrange var identity = new ClaimsIdentity(); identity.AddClaim(new Claim(ClaimTypes.Name, "Bob le Bricoleur")); var ticket = new AuthenticationTicket(identity, new AuthenticationProperties()); ticket.SetProperty("property", "value"); // Act var copy = ticket.Copy(); // Assert Assert.Equal("Bob le Bricoleur", copy.Identity.FindFirst(ClaimTypes.Name).Value); Assert.Equal(ticket.Properties.Dictionary, copy.Properties.Dictionary); }
public void Copy_ReturnsIdenticalTicket() { // Arrange var identity = new ClaimsIdentity(); identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Name, "Bob le Bricoleur")); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), nameof(AuthenticationTicket)); ticket.SetProperty("property", "value"); // Act var copy = ticket.Copy(); // Assert Assert.Equal(ticket.AuthenticationScheme, copy.AuthenticationScheme); Assert.Equal("Bob le Bricoleur", copy.Principal.FindFirst(OpenIdConnectConstants.Claims.Name).Value); Assert.Equal(ticket.Properties.Items, copy.Properties.Items); }
public void Copy_ReturnsDifferentTicketInstance() { // Arrange var identity = new ClaimsIdentity(); identity.AddClaim(new Claim(ClaimTypes.Name, "Bob le Bricoleur")); var ticket = new AuthenticationTicket(identity, new AuthenticationProperties()); ticket.SetProperty("property", "value"); // Act var copy = ticket.Copy(); copy.Identity.AddClaim(new Claim("clone_claim", "value")); copy.SetProperty("clone_property", "value"); // Assert Assert.NotSame(ticket, copy); Assert.Null(ticket.Identity.FindFirst("clone_claim")); Assert.NotEqual(ticket.Properties.Dictionary, copy.Properties.Dictionary); }
private async Task <bool> InvokeTokenEndpointAsync() { if (!string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { Options.Logger.LogError("The token request was rejected because an invalid " + "HTTP method was received: {Method}.", Request.Method); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed token request has been received: make sure to use POST." })); } // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrEmpty(Request.ContentType)) { Options.Logger.LogError("The token request was rejected because the " + "mandatory 'Content-Type' header was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed token request has been received: " + "the mandatory 'Content-Type' header was missing from the POST request." })); } // May have media/type; charset=utf-8, allow partial match. if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { Options.Logger.LogError("The token request was rejected because an invalid 'Content-Type' " + "header was received: {ContentType}.", Request.ContentType); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed token request has been received: " + "the 'Content-Type' header contained an unexcepted value. " + "Make sure to use 'application/x-www-form-urlencoded'." })); } var request = new OpenIdConnectRequest(await Request.ReadFormAsync()); // Note: set the message type before invoking the ExtractTokenRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.Token); // Store the token request in the OWIN context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractTokenRequestContext(Context, Options, request); await Options.Provider.ExtractTokenRequest(@event); if (@event.HandledResponse) { return(true); } else if (@event.Skipped) { return(false); } else if (@event.IsRejected) { Options.Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } // Reject token requests missing the mandatory grant_type parameter. if (string.IsNullOrEmpty(request.GrantType)) { Options.Logger.LogError("The token request was rejected because the grant type was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'grant_type' parameter was missing.", })); } // Reject grant_type=authorization_code requests if the authorization endpoint is disabled. else if (request.IsAuthorizationCodeGrantType() && !Options.AuthorizationEndpointPath.HasValue) { Options.Logger.LogError("The token request was rejected because the authorization code grant was disabled."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The authorization code grant is not allowed by this authorization server." })); } // Reject grant_type=authorization_code requests missing the authorization code. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 else if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.Code)) { Options.Logger.LogError("The token request was rejected because the authorization code was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'code' parameter was missing." })); } // Reject grant_type=refresh_token requests missing the refresh token. // See https://tools.ietf.org/html/rfc6749#section-6 else if (request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(request.RefreshToken)) { Options.Logger.LogError("The token request was rejected because the refresh token was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'refresh_token' parameter was missing." })); } // Reject grant_type=password requests missing username or password. // See https://tools.ietf.org/html/rfc6749#section-4.3.2 else if (request.IsPasswordGrantType() && (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password))) { Options.Logger.LogError("The token request was rejected because the resource owner credentials were missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'username' and/or 'password' parameters " + "was/were missing from the request message." })); } // When client_id and client_secret are both null, try to extract them from the Authorization header. // See http://tools.ietf.org/html/rfc6749#section-2.3.1 and // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication if (string.IsNullOrEmpty(request.ClientId) && string.IsNullOrEmpty(request.ClientSecret)) { var header = Request.Headers.Get("Authorization"); if (!string.IsNullOrEmpty(header) && header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase)) { try { var value = header.Substring("Basic ".Length).Trim(); var data = Encoding.UTF8.GetString(Convert.FromBase64String(value)); var index = data.IndexOf(':'); if (index >= 0) { request.ClientId = data.Substring(0, index); request.ClientSecret = data.Substring(index + 1); } } catch (FormatException) { } catch (ArgumentException) { } } } var context = new ValidateTokenRequestContext(Context, Options, request); await Options.Provider.ValidateTokenRequest(context); // If the validation context was set as fully validated, // mark the OpenID Connect request as confidential. if (context.IsValidated) { request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel, OpenIdConnectConstants.ConfidentialityLevels.Private); } if (context.HandledResponse) { return(true); } else if (context.Skipped) { return(false); } else if (context.IsRejected) { Options.Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } // Reject grant_type=client_credentials requests if validation was skipped. else if (context.IsSkipped && request.IsClientCredentialsGrantType()) { Options.Logger.LogError("The token request must be fully validated to use the client_credentials grant type."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Client authentication is required when using client_credentials." })); } // Ensure that the client_id has been set from the ValidateTokenRequest event. else if (context.IsValidated && string.IsNullOrEmpty(request.ClientId)) { Options.Logger.LogError("The token request was validated but the client_id was not set."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal server error occurred." })); } // At this stage, client_id cannot be null for grant_type=authorization_code requests, // as it must either be set in the ValidateTokenRequest notification // by the developer or manually flowed by non-confidential client applications. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.ClientId)) { Options.Logger.LogError("The token request was rejected because the mandatory 'client_id' was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "client_id was missing from the token request" })); } AuthenticationTicket ticket = null; // See http://tools.ietf.org/html/rfc6749#section-4.1 // and http://tools.ietf.org/html/rfc6749#section-4.1.3 (authorization code grant). // See http://tools.ietf.org/html/rfc6749#section-6 (refresh token grant). if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { ticket = request.IsAuthorizationCodeGrantType() ? await DeserializeAuthorizationCodeAsync(request.Code, request) : await DeserializeRefreshTokenAsync(request.RefreshToken, request); if (ticket == null) { Options.Logger.LogError("The token request was rejected because the " + "authorization code or the refresh token was invalid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Invalid ticket" })); } // If the client was fully authenticated when retrieving its refresh token, // the current request must be rejected if client authentication was not enforced. if (request.IsRefreshTokenGrantType() && !context.IsValidated && ticket.IsConfidential()) { Options.Logger.LogError("The token request was rejected because client authentication " + "was required to use the confidential refresh token."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Client authentication is required to use this ticket" })); } if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Options.Logger.LogError("The token request was rejected because the " + "authorization code or the refresh token was expired."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Expired ticket" })); } // Note: presenters may be empty during a grant_type=refresh_token request if the refresh token // was issued to a public client but cannot be null for an authorization code grant request. var presenters = ticket.GetPresenters(); if (request.IsAuthorizationCodeGrantType() && !presenters.Any()) { Options.Logger.LogError("The token request was rejected because the authorization " + "code didn't contain any valid presenter."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal server error occurred." })); } // Ensure the authorization code/refresh token was issued to the client application making the token request. // Note: when using the refresh token grant, client_id is optional but must validated if present. // As a consequence, this check doesn't depend on the actual status of client authentication. // See https://tools.ietf.org/html/rfc6749#section-6 // and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken if (!string.IsNullOrEmpty(request.ClientId) && presenters.Any() && !presenters.Contains(request.ClientId, StringComparer.Ordinal)) { Options.Logger.LogError("The token request was rejected because the authorization " + "code was issued to a different client application."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Ticket does not contain matching client_id" })); } // Validate the redirect_uri flowed by the client application during this token request. // Note: for pure OAuth2 requests, redirect_uri is only mandatory if the authorization request // contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri // but the specifications allow proceeding the token request without returning an error // if the authorization request didn't contain an explicit redirect_uri. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 // and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation var address = ticket.GetProperty(OpenIdConnectConstants.Properties.RedirectUri); if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(address)) { ticket.SetProperty(OpenIdConnectConstants.Properties.RedirectUri, null); if (string.IsNullOrEmpty(request.RedirectUri)) { Options.Logger.LogError("The token request was rejected because the mandatory 'redirect_uri' " + "parameter was missing from the grant_type=authorization_code request."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "redirect_uri was missing from the token request" })); } else if (!string.Equals(address, request.RedirectUri, StringComparison.Ordinal)) { Options.Logger.LogError("The token request was rejected because the 'redirect_uri' " + "parameter didn't correspond to the expected value."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Authorization code does not contain matching redirect_uri" })); } } // If a code challenge was initially sent in the authorization request and associated with the // code, validate the code verifier to ensure the token request is sent by a legit caller. var challenge = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallenge); if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(challenge)) { ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, null); // Get the code verifier from the token request. // If it cannot be found, return an invalid_grant error. var verifier = request.CodeVerifier; if (string.IsNullOrEmpty(verifier)) { Options.Logger.LogError("The token request was rejected because the required 'code_verifier' " + "parameter was missing from the grant_type=authorization_code request."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The required 'code_verifier' was missing from the token request." })); } // Note: the code challenge method is always validated when receiving the authorization request. var method = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod); ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, null); Debug.Assert(string.IsNullOrEmpty(method) || string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Plain, StringComparison.Ordinal) || string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal), "The specified code challenge method should be supported."); // If the S256 challenge method was used, compute the hash corresponding to the code verifier. if (string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal)) { using (var algorithm = SHA256.Create()) { // Compute the SHA-256 hash of the code verifier and encode it using base64-url. // See https://tools.ietf.org/html/rfc7636#section-4.6 for more information. var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(request.CodeVerifier)); verifier = Base64UrlEncoder.Encode(hash); } } // Compare the verifier and the code challenge: if the two don't match, return an error. // Note: to prevent timing attacks, a time-constant comparer is always used. if (!OpenIdConnectServerHelpers.AreEqual(verifier, challenge)) { Options.Logger.LogError("The token request was rejected because the 'code_verifier' was invalid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified 'code_verifier' was invalid." })); } } if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Resource)) { // When an explicit resource parameter has been included in the token request // but was missing from the initial request, the request MUST be rejected. var resources = ticket.GetResources(); if (!resources.Any()) { Options.Logger.LogError("The token request was rejected because the 'resource' parameter was not allowed."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Token request cannot contain a resource parameter " + "if the authorization request didn't contain one" })); } // When an explicit resource parameter has been included in the token request, // the authorization server MUST ensure that it doesn't contain resources // that were not allowed during the initial authorization/token request. else if (!new HashSet <string>(resources).IsSupersetOf(request.GetResources())) { Options.Logger.LogError("The token request was rejected because the 'resource' parameter was not valid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Token request doesn't contain a valid resource parameter" })); } } if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Scope)) { // When an explicit scope parameter has been included in the token request // but was missing from the initial request, the request MUST be rejected. // See http://tools.ietf.org/html/rfc6749#section-6 var scopes = ticket.GetScopes(); if (!scopes.Any()) { Options.Logger.LogError("The token request was rejected because the 'scope' parameter was not allowed."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Token request cannot contain a scope parameter " + "if the authorization request didn't contain one" })); } // When an explicit scope parameter has been included in the token request, // the authorization server MUST ensure that it doesn't contain scopes // that were not allowed during the initial authorization/token request. // See https://tools.ietf.org/html/rfc6749#section-6 else if (!new HashSet <string>(scopes).IsSupersetOf(request.GetScopes())) { Options.Logger.LogError("The token request was rejected because the 'scope' parameter was not valid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Token request doesn't contain a valid scope parameter" })); } } } var notification = new HandleTokenRequestContext(Context, Options, request, ticket); await Options.Provider.HandleTokenRequest(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } else if (notification.IsRejected) { Options.Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant, /* Description: */ notification.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant, 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 grant type is not supported. if (ticket == null) { Options.Logger.LogError("The token request was rejected because no authentication " + "ticket was returned by application code."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant_type parameter is not supported." })); } return(await HandleSignInAsync(ticket)); }
private async Task <string> CreateTokenAsync( [NotNull] string type, [NotNull] AuthenticationTicket ticket, [NotNull] OpenIddictOptions options, [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request, [NotNull] ISecureDataFormat <AuthenticationTicket> format) { Debug.Assert(!(options.DisableTokenRevocation && options.UseReferenceTokens), "Token revocation cannot be disabled when using reference tokens."); Debug.Assert(type == OpenIdConnectConstants.TokenUsages.AccessToken || type == OpenIdConnectConstants.TokenUsages.AuthorizationCode || type == OpenIdConnectConstants.TokenUsages.RefreshToken, "Only authorization codes, access and refresh tokens should be created using this method."); // When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed // and must exactly match the expiration date of the refresh token used in the token request. if (request.IsTokenRequest() && request.IsRefreshTokenGrantType() && !options.UseSlidingExpiration && type == OpenIdConnectConstants.TokenUsages.RefreshToken) { var properties = request.GetProperty <AuthenticationTicket>( OpenIddictConstants.Properties.AuthenticationTicket)?.Properties; Debug.Assert(properties != null, "The authentication properties shouldn't be null."); ticket.Properties.ExpiresUtc = properties.ExpiresUtc; } if (options.DisableTokenRevocation) { return(null); } var descriptor = new OpenIddictTokenDescriptor { AuthorizationId = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId), CreationDate = ticket.Properties.IssuedUtc, ExpirationDate = ticket.Properties.ExpiresUtc, Principal = ticket.Principal, Status = OpenIddictConstants.Statuses.Valid, Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject), Type = type }; foreach (var property in ticket.Properties.Items) { descriptor.Properties.Add(property); } string result = null; // When reference tokens are enabled or when the token is an authorization code or a // refresh token, remove the unnecessary properties from the authentication ticket. if (options.UseReferenceTokens || (type == OpenIdConnectConstants.TokenUsages.AuthorizationCode || type == OpenIdConnectConstants.TokenUsages.RefreshToken)) { ticket.Properties.IssuedUtc = ticket.Properties.ExpiresUtc = null; ticket.RemoveProperty(OpenIddictConstants.Properties.AuthorizationId) .RemoveProperty(OpenIdConnectConstants.Properties.TokenId); } // If reference tokens are enabled, create a new entry for // authorization codes, refresh tokens and access tokens. if (options.UseReferenceTokens) { // Note: the data format is automatically replaced at startup time to ensure // that encrypted tokens stored in the database cannot be considered as // valid tokens if the developer decides to disable reference tokens support. descriptor.Ciphertext = format.Protect(ticket); // Generate a new crypto-secure random identifier that will be // substituted to the ciphertext returned by the data format. var bytes = new byte[256 / 8]; options.RandomNumberGenerator.GetBytes(bytes); result = Base64UrlEncoder.Encode(bytes); // Compute the digest of the generated identifier and use // it as the hashed identifier of the reference token. // Doing that prevents token identifiers stolen from // the database from being used as valid reference tokens. using (var algorithm = SHA256.Create()) { descriptor.Hash = Convert.ToBase64String(algorithm.ComputeHash(bytes)); } } // Otherwise, only create a token metadata entry for authorization codes and refresh tokens. else if (type != OpenIdConnectConstants.TokenUsages.AuthorizationCode && type != OpenIdConnectConstants.TokenUsages.RefreshToken) { return(null); } // If the client application is known, associate it with the token. if (!string.IsNullOrEmpty(request.ClientId)) { var application = await Applications.FindByClientIdAsync(request.ClientId, context.RequestAborted); if (application == null) { throw new InvalidOperationException("The client application cannot be retrieved from the database."); } descriptor.ApplicationId = await Applications.GetIdAsync(application, context.RequestAborted); } // If a null value was returned by CreateAsync(), return immediately. var token = await Tokens.CreateAsync(descriptor, context.RequestAborted); if (token == null) { return(null); } // Throw an exception if the token identifier can't be resolved. var identifier = await Tokens.GetIdAsync(token, context.RequestAborted); if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty."); } // Restore the token identifier using the unique // identifier attached with the database entry. ticket.SetTokenId(identifier); // Dynamically set the creation and expiration dates. ticket.Properties.IssuedUtc = descriptor.CreationDate; ticket.Properties.ExpiresUtc = descriptor.ExpirationDate; // Restore the authorization identifier using the identifier attached with the database entry. ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, descriptor.AuthorizationId); if (!string.IsNullOrEmpty(result)) { Logger.LogTrace("A new reference token was successfully generated and persisted " + "in the database: {Token} ; {Claims} ; {Properties}.", result, ticket.Principal.Claims, ticket.Properties.Items); } return(result); }
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationIsInvalid() { // Arrange var identity = new ClaimsIdentity(OpenIddictServerDefaults.AuthenticationScheme); identity.AddClaim(OpenIddictConstants.Claims.Subject, "Bob le Bricoleur"); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), OpenIddictServerDefaults.AuthenticationScheme); ticket.SetAudiences("Fabrikam"); ticket.SetProperty(OpenIddictConstants.Properties.InternalTokenId, "3E228451-1555-46F7-A471-951EFBA23A56"); ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AccessToken); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) .Returns(ticket); var authorization = new OpenIddictAuthorization(); var manager = CreateAuthorizationManager(instance => { instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny <CancellationToken>())) .ReturnsAsync(authorization); instance.Setup(mock => mock.IsValidAsync(authorization, It.IsAny <CancellationToken>())) .ReturnsAsync(false); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .Returns(new ValueTask <string>(OpenIddictConstants.ClientTypes.Confidential)); instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny <CancellationToken>())) .ReturnsAsync(true); })); builder.Services.AddSingleton(CreateTokenManager(instance => { var token = new OpenIddictToken(); instance.Setup(mock => mock.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny <CancellationToken>())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny <CancellationToken>())) .Returns(new ValueTask <string>("3E228451-1555-46F7-A471-951EFBA23A56")); instance.Setup(mock => mock.GetPayloadAsync(token, It.IsAny <CancellationToken>())) .Returns(new ValueTask <string>("2YotnFZFEjr1zCsicMWpAA")); instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny <CancellationToken>())) .ReturnsAsync(token); instance.Setup(mock => mock.IsValidAsync(token, It.IsAny <CancellationToken>())) .ReturnsAsync(false); instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny <CancellationToken>())) .Returns(new ValueTask <string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")); })); builder.Services.AddSingleton(manager); builder.Configure(options => options.AccessTokenFormat = format.Object); builder.UseReferenceTokens(); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" }); // Assert Assert.Single(response.GetParameters()); Assert.False((bool)response[OpenIddictConstants.Claims.Active]); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny <CancellationToken>()), Times.Once()); Mock.Get(manager).Verify(mock => mock.IsValidAsync(authorization, It.IsAny <CancellationToken>()), Times.Once()); }
private async Task <AuthenticationTicket> DeserializeIdentityTokenAsync(string token, OpenIdConnectRequest request) { var notification = new DeserializeIdentityTokenContext(Context, Options, request, token) { SecurityTokenHandler = Options.IdentityTokenHandler }; // Note: ValidateAudience and ValidateLifetime are always set to false: // if necessary, the audience and the expiration can be validated // in InvokeIntrospectionEndpointAsync or InvokeTokenEndpointAsync. notification.TokenValidationParameters = new TokenValidationParameters { IssuerSigningKeys = Options.SigningCredentials.Select(credentials => credentials.Key), NameClaimType = OpenIdConnectConstants.Claims.Name, RoleClaimType = OpenIdConnectConstants.Claims.Role, ValidIssuer = Context.GetIssuer(Options), ValidateAudience = false, ValidateLifetime = false }; await Options.Provider.DeserializeIdentityToken(notification); if (notification.HandledResponse || notification.Ticket != null) { notification.Ticket.SetUsage(OpenIdConnectConstants.Usages.IdentityToken); return(notification.Ticket); } else if (notification.Skipped) { return(null); } if (notification.SecurityTokenHandler == null) { return(null); } SecurityToken securityToken; ClaimsPrincipal principal; try { if (!notification.SecurityTokenHandler.CanReadToken(token)) { Logger.LogDebug("The identity token handler refused to read the token: {Token}", token); return(null); } principal = notification.SecurityTokenHandler.ValidateToken(token, notification.TokenValidationParameters, out securityToken); } catch (Exception exception) { Logger.LogDebug("An exception occured when deserializing an identity token: {Message}.", exception.Message); return(null); } // Parameters stored in AuthenticationProperties are lost // when the identity token is serialized using a security token handler. // To mitigate that, they are inferred from the claims or the security token. var properties = new AuthenticationProperties { ExpiresUtc = securityToken.ValidTo, IssuedUtc = securityToken.ValidFrom }; var ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme); var audiences = principal.FindAll(OpenIdConnectConstants.Claims.Audience); if (audiences.Any()) { ticket.SetAudiences(audiences.Select(claim => claim.Value)); } var presenters = principal.FindAll(OpenIdConnectConstants.Claims.AuthorizedParty); if (presenters.Any()) { ticket.SetPresenters(presenters.Select(claim => claim.Value)); } var identifier = principal.FindFirst(OpenIdConnectConstants.Claims.JwtId); if (identifier != null) { ticket.SetTicketId(identifier.Value); } var usage = principal.FindFirst(OpenIdConnectConstants.Claims.Usage); if (usage != null) { ticket.SetUsage(usage.Value); } var confidentiality = principal.FindFirst(OpenIdConnectConstants.Claims.ConfidentialityLevel); if (confidentiality != null) { ticket.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel, confidentiality.Value); } // Ensure the received ticket is an identity token. if (!ticket.IsIdentityToken()) { Logger.LogDebug("The received token was not an identity token: {Token}.", token); return(null); } return(ticket); }
private static TestServer CreateAuthorizationServer(Action <OpenIddictBuilder> configuration = null) { var builder = new WebHostBuilder(); builder.UseEnvironment("Testing"); builder.ConfigureLogging(options => options.AddDebug()); builder.ConfigureServices(services => { services.AddOptions(); services.AddDistributedMemoryCache(); // Note: the following client_id/client_secret are fake and are only // used to test the metadata returned by the discovery endpoint. services.AddAuthentication() .AddFacebook(options => { options.ClientId = "16018790-E88E-4553-8036-BB342579FF19"; options.ClientSecret = "3D6499AF-5607-489B-815A-F3ACF1617296"; options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddGoogle(options => { options.ClientId = "BAF437A5-87FA-4D06-8EFD-F9BA96CCEDC4"; options.ClientSecret = "27DF07D3-6B03-4EE0-95CD-3AC16782216B"; options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }); // Replace the default OpenIddict managers. services.AddSingleton(CreateApplicationManager()); services.AddSingleton(CreateAuthorizationManager()); services.AddSingleton(CreateTokenManager()); services.AddOpenIddict(options => { // Disable the transport security requirement during testing. options.DisableHttpsRequirement(); // Enable the tested endpoints. options.EnableAuthorizationEndpoint(AuthorizationEndpoint) .EnableIntrospectionEndpoint(IntrospectionEndpoint) .EnableLogoutEndpoint(LogoutEndpoint) .EnableRevocationEndpoint(RevocationEndpoint) .EnableTokenEndpoint(TokenEndpoint) .EnableUserinfoEndpoint(UserinfoEndpoint); // Enable the tested flows. options.AllowAuthorizationCodeFlow() .AllowClientCredentialsFlow() .AllowImplicitFlow() .AllowPasswordFlow() .AllowRefreshTokenFlow(); // Register the X.509 certificate used to sign the identity tokens. options.AddSigningCertificate( assembly: typeof(OpenIddictProviderTests).GetTypeInfo().Assembly, resource: "OpenIddict.Tests.Certificate.pfx", password: "******"); // Note: overriding the default data protection provider is not necessary for the tests to pass, // but is useful to ensure unnecessary keys are not persisted in testing environments, which also // helps make the unit tests run faster, as no registry or disk access is required in this case. options.UseDataProtectionProvider(new EphemeralDataProtectionProvider(new LoggerFactory())); // Run the configuration delegate // registered by the unit tests. configuration?.Invoke(options); }); }); builder.Configure(app => { app.UseStatusCodePages(context => { context.HttpContext.Response.Headers[HeaderNames.ContentType] = "application/json"; return(context.HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(new { error_custom = OpenIdConnectConstants.Errors.InvalidRequest }))); }); app.Use(next => context => { if (context.Request.Path != "/authorize-status-code-middleware" && context.Request.Path != "/logout-status-code-middleware") { var feature = context.Features.Get <IStatusCodePagesFeature>(); feature.Enabled = false; } return(next(context)); }); app.UseAuthentication(); app.Run(context => { var request = context.GetOpenIdConnectRequest(); if (request.IsAuthorizationRequest() || request.IsTokenRequest()) { var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Magnifique"); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetScopes(request.GetScopes()); ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70"); return(context.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, ticket.Properties)); } else if (request.IsLogoutRequest()) { return(context.SignOutAsync(OpenIdConnectServerDefaults.AuthenticationScheme)); } else if (request.IsUserinfoRequest()) { context.Response.Headers[HeaderNames.ContentType] = "application/json"; return(context.Response.WriteAsync(JsonConvert.SerializeObject(new { access_token = request.AccessToken, sub = "Bob le Bricoleur" }))); } return(Task.FromResult(0)); }); }); return(new TestServer(builder)); }
private async Task <AuthenticationTicket> CreateTicketAsync( IUser user, object application, object authorization, OpenIdConnectRequest request, AuthenticationProperties properties = null) { Debug.Assert(request.IsAuthorizationRequest() || request.IsTokenRequest(), "The request should be an authorization or token request."); // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); var identity = (ClaimsIdentity)principal.Identity; // Note: while ASP.NET Core Identity uses the legacy WS-Federation claims (exposed by the ClaimTypes class), // OpenIddict uses the newer JWT claims defined by the OpenID Connect specification. To ensure the mandatory // subject claim is correctly populated (and avoid an InvalidOperationException), it's manually added here. if (string.IsNullOrEmpty(principal.FindFirstValue(OpenIddictConstants.Claims.Subject))) { identity.AddClaim(new Claim(OpenIddictConstants.Claims.Subject, await _userManager.GetUserIdAsync(user))); } // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIddictServerDefaults.AuthenticationScheme); if (request.IsAuthorizationRequest() || (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(request.GetScopes()); ticket.SetResources(await GetResourcesAsync(request.GetScopes())); // If the request is an authorization request, automatically create // a permanent authorization to avoid requiring explicit consent for // future authorization or token requests containing the same scopes. if (authorization == null && request.IsAuthorizationRequest()) { authorization = await _authorizationManager.CreateAsync( principal : ticket.Principal, subject : await _userManager.GetUserIdAsync(user), client : await _applicationManager.GetIdAsync(application), type : OpenIddictConstants.AuthorizationTypes.Permanent, scopes : ImmutableArray.CreateRange(ticket.GetScopes()), properties : ImmutableDictionary.CreateRange(ticket.Properties.Items)); } if (authorization != null) { // Attach the authorization identifier to the authentication ticket. ticket.SetProperty(OpenIddictConstants.Properties.InternalAuthorizationId, await _authorizationManager.GetIdAsync(authorization)); } } // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in ticket.Principal.Claims) { // Never include the security stamp in the access and identity tokens, as it's a secret value. if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } var destinations = new List <string> { OpenIddictConstants.Destinations.AccessToken }; // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. // The other claims will only be added to the access_token, which is encrypted when using the default format. if ((claim.Type == OpenIddictConstants.Claims.Name && ticket.HasScope(OpenIddictConstants.Scopes.Profile)) || (claim.Type == OpenIddictConstants.Claims.Email && ticket.HasScope(OpenIddictConstants.Scopes.Email)) || (claim.Type == OpenIddictConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles))) { destinations.Add(OpenIddictConstants.Destinations.IdentityToken); } claim.SetDestinations(destinations); } return(ticket); }
public async Task ProcessSigninResponse_PreviousTokensAreNotRevokedWhenRollingTokensAreDisabled() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur"); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103"); ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken); ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess); ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Unprotect("8xLOxBtZp8")) .Returns(ticket); var tokens = ImmutableArray.Create( new OpenIddictToken(), new OpenIddictToken(), new OpenIddictToken()); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny <CancellationToken>())) .ReturnsAsync(tokens[0]); instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny <CancellationToken>())) .ReturnsAsync(false); instance.Setup(mock => mock.IsValidAsync(tokens[0], It.IsAny <CancellationToken>())) .ReturnsAsync(true); instance.Setup(mock => mock.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny <CancellationToken>())) .ReturnsAsync(tokens); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(manager); builder.Configure(options => options.RefreshTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest { GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken, RefreshToken = "8xLOxBtZp8" }); // Assert Assert.Null(response.RefreshToken); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny <CancellationToken>()), Times.Exactly(2)); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny <CancellationToken>()), Times.Never()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny <CancellationToken>()), Times.Never()); }
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)); }
public async Task ProcessSigninResponse_AuthenticationPropertiesAreAutomaticallyRestored() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur"); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103"); ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken); ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess); ticket.SetProperty("custom_property_in_original_ticket", "original_value"); var format = new Mock <ISecureDataFormat <AuthenticationTicket> >(); format.Setup(mock => mock.Protect(It.IsAny <AuthenticationTicket>())) .Returns("8xLOxBtZp8"); format.Setup(mock => mock.Unprotect("8xLOxBtZp8")) .Returns(ticket); var token = new OpenIddictToken(); var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny <CancellationToken>())) .ReturnsAsync(token); instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny <CancellationToken>())) .ReturnsAsync(false); instance.Setup(mock => mock.IsValidAsync(token, It.IsAny <CancellationToken>())) .ReturnsAsync(true); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(manager); builder.UseRollingTokens(); builder.Configure(options => options.RefreshTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest { GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken, RefreshToken = "8xLOxBtZp8", ["do-not-flow-original-properties"] = true }); // Assert Assert.NotNull(response.IdToken); Assert.NotNull(response.RefreshToken); format.Verify(mock => mock.Protect( It.Is <AuthenticationTicket>(value => value.Properties.Items["custom_property_in_original_ticket"] == "original_value" && value.Properties.Items["custom_property_in_new_ticket"] == "new_value"))); }