/// <summary> /// Initializes base class used for certain event contexts /// </summary> protected BaseValidatingClientContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request) : base(context, options) { Request = request; }
/// <summary> /// Creates an instance of this context /// </summary> internal LogoutEndpointContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request) : base(context) { Options = options; Request = request; }
/// <summary> /// Initializes a new instance of the <see cref="ValidateAuthorizationRequestContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="request"></param> internal ValidateAuthorizationRequestContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request) : base(context, options) { Request = request; Validated(); }
public RemoteSignOutContext( HttpContext context, OpenIdConnectOptions options, OpenIdConnectMessage message) : base(context, options) { ProtocolMessage = message; }
/// <summary> /// Initializes a new instance of the <see cref="ProfileEndpointResponseContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="request"></param> /// <param name="payload"></param> internal ProfileEndpointResponseContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request, JObject payload) : base(context) { Options = options; Request = request; Payload = payload; }
/// <summary> /// Initializes a new instance of the <see cref="TokenEndpointContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="request"></param> /// <param name="ticket"></param> internal TokenEndpointContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request, AuthenticationTicket ticket) : base(context) { Options = options; Request = request; Ticket = ticket; }
/// <summary> /// Initializes a new instance of the <see cref="DeserializeAuthorizationCodeContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="request"></param> /// <param name="code"></param> internal DeserializeAuthorizationCodeContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request, string code) : base(context) { Options = options; Request = request; AuthorizationCode = code; }
/// <summary> /// Initializes a new instance of the <see cref="DeserializeIdentityTokenContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="request"></param> /// <param name="token"></param> internal DeserializeIdentityTokenContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request, string token) : base(context) { Options = options; Request = request; IdentityToken = token; }
protected override async Task<AuthenticationTicket> GetUserInformationAsync(AuthenticationProperties properties, OpenIdConnectMessage message, AuthenticationTicket ticket) { var claimsIdentity = (ClaimsIdentity)ticket.Principal.Identity; if (claimsIdentity == null) { claimsIdentity = new ClaimsIdentity(); } claimsIdentity.AddClaim(new Claim("test claim", "test value")); return new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), ticket.Properties, ticket.AuthenticationScheme); }
public OpenIdConnectTokenEndpointResponse(JObject jsonResponse) { JsonResponse = jsonResponse; Message = new OpenIdConnectMessage() { AccessToken = JsonResponse.Value<string>(OpenIdConnectParameterNames.AccessToken), IdToken = JsonResponse.Value<string>(OpenIdConnectParameterNames.IdToken), TokenType = JsonResponse.Value<string>(OpenIdConnectParameterNames.TokenType), ExpiresIn = JsonResponse.Value<string>(OpenIdConnectParameterNames.ExpiresIn) }; }
/// <summary> /// Initializes a new instance of the <see cref="SerializeAccessTokenContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="request"></param> /// <param name="response"></param> /// <param name="ticket"></param> internal SerializeAccessTokenContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request, OpenIdConnectMessage response, AuthenticationTicket ticket) : base(context) { Options = options; Request = request; Response = response; AuthenticationTicket = ticket; }
/// <summary> /// Initializes a new instance of the <see cref="TokenEndpointResponseContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="ticket"></param> /// <param name="request"></param> /// <param name="payload"></param> internal TokenEndpointResponseContext( HttpContext context, OpenIdConnectServerOptions options, AuthenticationTicket ticket, OpenIdConnectMessage request, JObject payload) : base(context) { Options = options; AuthenticationTicket = ticket; Request = request; Payload = payload; }
/// <summary> /// Initializes a new instance of the <see cref="AuthorizationEndpointResponseContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="ticket"></param> /// <param name="request"></param> /// <param name="response"></param> internal AuthorizationEndpointResponseContext( HttpContext context, OpenIdConnectServerOptions options, AuthenticationTicket ticket, OpenIdConnectMessage request, OpenIdConnectMessage response) : base(context) { Options = options; AuthenticationTicket = ticket; Request = request; Response = response; }
/// <summary> /// Tells the middleware to skip the code redemption process. The developer may have redeemed the code themselves, or /// decided that the redemption was not required. If tokens were retrieved that are needed for further processing then /// call one of the overloads that allows providing tokens. An IdToken is required if one had not been previously received /// in the authorization response. An access token can optionally be provided for the middleware to contact the /// user-info endpoint. Calling this is the same as setting TokenEndpointResponse. /// </summary> public void HandleCodeRedemption() { TokenEndpointResponse = new OpenIdConnectMessage(); }
private async Task <TokenResponseReceivedContext> RunTokenResponseReceivedEventAsync(OpenIdConnectMessage message, OpenIdConnectMessage tokenEndpointResponse, AuthenticationProperties properties) { Logger.TokenResponseReceived(); var eventContext = new TokenResponseReceivedContext(Context, Options, properties) { ProtocolMessage = message, TokenEndpointResponse = tokenEndpointResponse }; await Options.Events.TokenResponseReceived(eventContext); if (eventContext.HandledResponse) { Logger.TokenResponseReceivedHandledResponse(); } else if (eventContext.Skipped) { Logger.TokenResponseReceivedSkipped(); } return(eventContext); }
/// <summary> /// Inserts the ambient <see cref="OpenIdConnectMessage"/> response in the ASP.NET context. /// </summary> /// <param name="context">The ASP.NET context.</param> /// <param name="response">The ambient <see cref="OpenIdConnectMessage"/>.</param> public static void SetOpenIdConnectResponse([NotNull] this HttpContext context, OpenIdConnectMessage response) { var feature = context.Features.Get<IOpenIdConnectServerFeature>(); if (feature == null) { feature = new OpenIdConnectServerFeature(); context.Features.Set(feature); } feature.Response = response; }
/// <summary> /// Initializes a new instance of the <see cref="ValidateClientLogoutRedirectUriContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="request"></param> internal ValidateClientLogoutRedirectUriContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request) : base(context, options, request) { }
private async Task <bool> InvokeIntrospectionEndpointAsync() { OpenIdConnectMessage request; // See https://tools.ietf.org/html/rfc7662#section-2.1 // and https://tools.ietf.org/html/rfc7662#section-4 if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { request = new OpenIdConnectMessage(Request.Query) { RequestType = OpenIdConnectRequestType.AuthenticationRequest }; } else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrEmpty(Request.ContentType)) { Options.Logger.LogError("The introspection request was rejected because " + "the mandatory 'Content-Type' header was missing."); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed introspection 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 introspection request was rejected because an invalid 'Content-Type' " + "header was received: {ContentType}.", Request.ContentType); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed introspection request has been received: " + "the 'Content-Type' header contained an unexcepted value. " + "Make sure to use 'application/x-www-form-urlencoded'." })); } request = new OpenIdConnectMessage(await Request.ReadFormAsync()) { RequestType = OpenIdConnectRequestType.AuthenticationRequest }; } else { Options.Logger.LogError("The introspection request was rejected because an invalid " + "HTTP method was received: {Method}.", Request.Method); return(await SendErrorPageAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed introspection request has been received: " + "make sure to use either GET or POST." })); } if (string.IsNullOrWhiteSpace(request.Token)) { return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed introspection request has been received: " + "a 'token' parameter with an access, refresh, or identity token is required." })); } // Insert the introspection request in the OWIN context. Context.SetOpenIdConnectRequest(request); // 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 validatingContext = new ValidateIntrospectionRequestContext(Context, Options, request); await Options.Provider.ValidateIntrospectionRequest(validatingContext); if (validatingContext.IsRejected) { Options.Logger.LogInformation("The introspection request was rejected by application code."); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = validatingContext.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = validatingContext.ErrorDescription, ErrorUri = validatingContext.ErrorUri })); } // Ensure that the client_id has been set from the ValidateIntrospectionRequest event. else if (validatingContext.IsValidated && string.IsNullOrEmpty(request.ClientId)) { Options.Logger.LogError("The introspection request was validated but the client_id was not set."); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal server error occurred." })); } AuthenticationTicket ticket = null; // Note: use the "token_type_hint" parameter to determine // the type of the token sent by the client application. // See https://tools.ietf.org/html/rfc7662#section-2.1 switch (request.GetTokenTypeHint()) { case OpenIdConnectConstants.Usages.AccessToken: ticket = await DeserializeAccessTokenAsync(request.Token, request); break; case OpenIdConnectConstants.Usages.RefreshToken: ticket = await DeserializeRefreshTokenAsync(request.Token, request); break; case OpenIdConnectConstants.Usages.IdToken: ticket = await DeserializeIdentityTokenAsync(request.Token, request); break; } // Note: if the token can't be found using "token_type_hint", // the search must be extended to all supported token types. // See https://tools.ietf.org/html/rfc7662#section-2.1 if (ticket == null) { ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeIdentityTokenAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); } if (ticket == null) { Options.Logger.LogInformation("The introspection request was rejected because the token was invalid."); return(await SendPayloadAsync(new JObject { [OpenIdConnectConstants.Claims.Active] = false })); } // Note: unlike refresh or identity tokens that can only be validated by client applications, // access tokens can be validated by either resource servers or client applications: // in both cases, the caller must be authenticated if the ticket is marked as confidential. if (validatingContext.IsSkipped && ticket.IsConfidential()) { Options.Logger.LogWarning("The introspection request was rejected because the caller was not authenticated."); return(await SendPayloadAsync(new JObject { [OpenIdConnectConstants.Claims.Active] = false })); } // If the ticket is already expired, directly return active=false. if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Options.Logger.LogInformation("The introspection request was rejected because the token was expired."); return(await SendPayloadAsync(new JObject { [OpenIdConnectConstants.Claims.Active] = false })); } // When a client_id can be inferred from the introspection request, // ensure that the client application is a valid audience/presenter. if (!string.IsNullOrEmpty(request.ClientId)) { // Ensure the caller is listed as a valid audience or authorized presenter. if (ticket.IsAccessToken() && ticket.HasAudience() && !ticket.HasAudience(request.ClientId) && ticket.HasPresenter() && !ticket.HasPresenter(request.ClientId)) { Options.Logger.LogWarning("The introspection request was rejected because the access token " + "was issued to a different client or for another resource server."); return(await SendPayloadAsync(new JObject { [OpenIdConnectConstants.Claims.Active] = false })); } // Reject the request if the caller is not listed as a valid audience. else if (ticket.IsIdentityToken() && ticket.HasAudience() && !ticket.HasAudience(request.ClientId)) { Options.Logger.LogWarning("The introspection request was rejected because the " + "identity token was issued to a different client."); return(await SendPayloadAsync(new JObject { [OpenIdConnectConstants.Claims.Active] = false })); } // Reject the introspection request if the caller doesn't // correspond to the client application the token was issued to. else if (ticket.IsRefreshToken() && ticket.HasPresenter() && !ticket.HasPresenter(request.ClientId)) { Options.Logger.LogWarning("The introspection request was rejected because the " + "refresh token was issued to a different client."); return(await SendPayloadAsync(new JObject { [OpenIdConnectConstants.Claims.Active] = false })); } } var notification = new HandleIntrospectionRequestContext(Context, Options, request, ticket); notification.Active = true; // Use the unique ticket identifier to populate the "jti" claim. notification.TokenId = ticket.GetTicketId(); // Note: only set "token_type" when the received token is an access token. // See https://tools.ietf.org/html/rfc7662#section-2.2 // and https://tools.ietf.org/html/rfc6749#section-5.1 if (ticket.IsAccessToken()) { notification.TokenType = OpenIdConnectConstants.TokenTypes.Bearer; } notification.Issuer = Context.GetIssuer(Options); notification.Subject = ticket.Identity.GetClaim(ClaimTypes.NameIdentifier); notification.IssuedAt = ticket.Properties.IssuedUtc; notification.ExpiresAt = ticket.Properties.ExpiresUtc; // Copy the audiences extracted from the "aud" claim. foreach (var audience in ticket.GetAudiences()) { notification.Audiences.Add(audience); } // Note: non-metadata claims are only added if the caller is authenticated // AND is in the specified audiences, unless there's so explicit audience. if (!ticket.HasAudience() || (!string.IsNullOrEmpty(request.ClientId) && ticket.HasAudience(request.ClientId))) { notification.Username = ticket.Identity.Name; notification.Scope = ticket.GetProperty(OpenIdConnectConstants.Properties.Scopes); // Potentially sensitive claims are only exposed to trusted callers // if the ticket corresponds to an access or identity token. if (ticket.IsAccessToken() || ticket.IsIdentityToken()) { foreach (var claim in ticket.Identity.Claims) { // Exclude standard claims, that are already handled via strongly-typed properties. // Make sure to always update this list when adding new built-in claim properties. if (string.Equals(claim.Type, ticket.Identity.NameClaimType, StringComparison.Ordinal) || string.Equals(claim.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal)) { continue; } if (string.Equals(claim.Type, OpenIdConnectConstants.Claims.Audience, StringComparison.Ordinal) || string.Equals(claim.Type, OpenIdConnectConstants.Claims.ExpiresAt, StringComparison.Ordinal) || string.Equals(claim.Type, OpenIdConnectConstants.Claims.IssuedAt, StringComparison.Ordinal) || string.Equals(claim.Type, OpenIdConnectConstants.Claims.Issuer, StringComparison.Ordinal) || string.Equals(claim.Type, OpenIdConnectConstants.Claims.NotBefore, StringComparison.Ordinal) || string.Equals(claim.Type, OpenIdConnectConstants.Claims.Scope, StringComparison.Ordinal) || string.Equals(claim.Type, OpenIdConnectConstants.Claims.Subject, StringComparison.Ordinal) || string.Equals(claim.Type, OpenIdConnectConstants.Claims.TokenType, StringComparison.Ordinal)) { continue; } string type; // Try to resolve the short name associated with the claim type: // if none can be found, the claim type is used as-is. if (!JwtSecurityTokenHandler.OutboundClaimTypeMap.TryGetValue(claim.Type, out type)) { type = claim.Type; } // Note: make sure to use the indexer // syntax to avoid duplicate properties. notification.Claims[type] = claim.Value; } } } await Options.Provider.HandleIntrospectionRequest(notification); // Flow the changes made to the authentication ticket. ticket = notification.Ticket; if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } var payload = new JObject(); payload.Add(OpenIdConnectConstants.Claims.Active, notification.Active); // Only add the other properties if // the token is considered as active. if (notification.Active) { if (!string.IsNullOrEmpty(notification.Issuer)) { payload.Add(OpenIdConnectConstants.Claims.Issuer, notification.Issuer); } if (!string.IsNullOrEmpty(notification.Username)) { payload.Add(OpenIdConnectConstants.Claims.Username, notification.Username); } if (!string.IsNullOrEmpty(notification.Subject)) { payload.Add(OpenIdConnectConstants.Claims.Subject, notification.Subject); } if (!string.IsNullOrEmpty(notification.Scope)) { payload.Add(OpenIdConnectConstants.Claims.Scope, notification.Scope); } if (notification.IssuedAt.HasValue) { payload.Add(OpenIdConnectConstants.Claims.IssuedAt, EpochTime.GetIntDate(notification.IssuedAt.Value.UtcDateTime)); payload.Add(OpenIdConnectConstants.Claims.NotBefore, EpochTime.GetIntDate(notification.IssuedAt.Value.UtcDateTime)); } if (notification.ExpiresAt.HasValue) { payload.Add(OpenIdConnectConstants.Claims.ExpiresAt, EpochTime.GetIntDate(notification.ExpiresAt.Value.UtcDateTime)); } if (!string.IsNullOrEmpty(notification.TokenId)) { payload.Add(OpenIdConnectConstants.Claims.JwtId, notification.TokenId); } if (!string.IsNullOrEmpty(notification.TokenType)) { payload.Add(OpenIdConnectConstants.Claims.TokenType, notification.TokenType); } switch (notification.Audiences.Count) { case 0: break; case 1: payload.Add(OpenIdConnectConstants.Claims.Audience, notification.Audiences[0]); break; default: payload.Add(OpenIdConnectConstants.Claims.Audience, JArray.FromObject(notification.Audiences)); break; } foreach (var claim in notification.Claims) { // Ignore claims whose value is null. if (claim.Value == null) { continue; } // Note: make sure to use the indexer // syntax to avoid duplicate properties. payload[claim.Key] = claim.Value; } } var context = new ApplyIntrospectionResponseContext(Context, Options, payload); await Options.Provider.ApplyIntrospectionResponse(context); if (context.HandledResponse) { return(true); } else if (context.Skipped) { return(false); } using (var buffer = new MemoryStream()) using (var writer = new JsonTextWriter(new StreamWriter(buffer))) { payload.WriteTo(writer); writer.Flush(); Response.ContentLength = buffer.Length; Response.ContentType = "application/json;charset=UTF-8"; Response.Headers.Set("Cache-Control", "no-cache"); Response.Headers.Set("Pragma", "no-cache"); Response.Headers.Set("Expires", "-1"); buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Request.CallCancelled); return(true); } }
/// <summary> /// Redeems the authorization code for tokens at the token endpoint /// </summary> /// <param name="tokenEndpointRequest">The request that will be sent to the token endpoint and is available for customization.</param> /// <returns>OpenIdConnect message that has tokens inside it.</returns> protected virtual async Task <OpenIdConnectMessage> RedeemAuthorizationCodeAsync(OpenIdConnectMessage tokenEndpointRequest) { Logger.RedeemingCodeForTokens(); var requestMessage = new HttpRequestMessage(HttpMethod.Post, _configuration.TokenEndpoint); requestMessage.Content = new FormUrlEncodedContent(tokenEndpointRequest.Parameters); var responseMessage = await Backchannel.SendAsync(requestMessage); responseMessage.EnsureSuccessStatusCode(); var tokenResonse = await responseMessage.Content.ReadAsStringAsync(); var jsonTokenResponse = JObject.Parse(tokenResonse); return(new OpenIdConnectMessage(jsonTokenResponse)); }
private async Task <bool> HandleAuthorizationResponseAsync() { // request may be null when no authorization request has been received // or has been already handled by InvokeAuthorizationEndpointAsync. var request = Context.GetOpenIdConnectRequest(); if (request == null) { return(false); } // Stop processing the request if there's no response grant that matches // the authentication type associated with this middleware instance // or if the response status code doesn't indicate a successful response. var context = Helper.LookupSignIn(Options.AuthenticationType); if (context == null || Response.StatusCode != 200) { return(false); } if (!context.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."); } var response = new OpenIdConnectMessage { RedirectUri = request.RedirectUri, State = request.State }; if (!string.IsNullOrEmpty(request.Nonce)) { // Keep the original nonce parameter for later comparison. context.Properties.Dictionary[OpenIdConnectConstants.Properties.Nonce] = request.Nonce; } if (!string.IsNullOrEmpty(request.RedirectUri)) { // Keep original the original redirect_uri for later comparison. context.Properties.Dictionary[OpenIdConnectConstants.Properties.RedirectUri] = request.RedirectUri; } // Always include the "openid" scope when the developer doesn't explicitly call SetScopes. // Note: the application is allowed to specify a different "scopes" // parameter when calling AuthenticationManager.SignInAsync: in this case, // don't replace the "scopes" property stored in the authentication ticket. if (!context.Properties.Dictionary.ContainsKey(OpenIdConnectConstants.Properties.Scopes) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId)) { context.Properties.Dictionary[OpenIdConnectConstants.Properties.Scopes] = OpenIdConnectConstants.Scopes.OpenId; } string audiences; // When a "resources" property cannot be found in the authentication properties, infer it from the "audiences" property. if (!context.Properties.Dictionary.ContainsKey(OpenIdConnectConstants.Properties.Resources) && context.Properties.Dictionary.TryGetValue(OpenIdConnectConstants.Properties.Audiences, out audiences)) { context.Properties.Dictionary[OpenIdConnectConstants.Properties.Resources] = audiences; } // Determine whether an authorization code should be returned // and invoke SerializeAuthorizationCodeAsync if necessary. if (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 = context.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(context.Identity, properties, request, response); // Ensure that an authorization code is issued to avoid returning an invalid response. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations if (string.IsNullOrEmpty(response.Code)) { throw new InvalidOperationException("An error occurred during the serialization of the " + "authorization code and a null value was returned."); } } // Determine whether an access token should be returned // and invoke SerializeAccessTokenAsync if necessary. if (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 = context.Properties.Copy(); string resources; if (!properties.Dictionary.TryGetValue(OpenIdConnectConstants.Properties.Resources, out resources)) { Options.Logger.LogInformation("No explicit resource was associated with the authentication ticket: " + "the access token will be issued without any audience attached."); } // Note: when the "resource" parameter added to the OpenID Connect response // is identical to the request parameter, setting it is not necessary. if (!string.IsNullOrEmpty(request.Resource) && !string.Equals(request.Resource, resources, StringComparison.Ordinal)) { response.Resource = resources; } // Note: when the "scope" parameter added to the OpenID Connect response // is identical to the request parameter, setting it is not necessary. string scopes; properties.Dictionary.TryGetValue(OpenIdConnectConstants.Properties.Scopes, out scopes); if (!string.IsNullOrEmpty(request.Scope) && !string.Equals(request.Scope, scopes, StringComparison.Ordinal)) { response.Scope = scopes; } response.TokenType = OpenIdConnectConstants.TokenTypes.Bearer; response.AccessToken = await SerializeAccessTokenAsync(context.Identity, properties, request, response); // Ensure that an access token is issued to avoid returning an invalid response. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations if (string.IsNullOrEmpty(response.AccessToken)) { throw new InvalidOperationException("An error occurred during the serialization of the " + "access token and a null value was returned."); } // 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; var expiration = (long)(lifetime.TotalSeconds + .5); response.ExpiresIn = expiration.ToString(CultureInfo.InvariantCulture); } } // Determine whether an identity token should be returned // and invoke SerializeIdentityTokenAsync if necessary. if (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 = context.Properties.Copy(); response.IdToken = await SerializeIdentityTokenAsync(context.Identity, properties, request, response); // Ensure that an identity token is issued to avoid returning an invalid response. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations if (string.IsNullOrEmpty(response.IdToken)) { throw new InvalidOperationException("An error occurred during the serialization of the " + "identity token and a null value was returned."); } } // Remove the OpenID Connect request from the cache. var identifier = request.GetRequestId(); if (!string.IsNullOrEmpty(identifier)) { await Options.Cache.RemoveAsync($"asos-request:{identifier}"); } var ticket = new AuthenticationTicket(context.Identity, context.Properties); return(await SendAuthorizationResponseAsync(request, response, ticket)); }
private async Task <bool> InvokeAuthorizationEndpointAsync() { OpenIdConnectMessage request; if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { // Create a new authorization request using the // parameters retrieved from the query string. request = new OpenIdConnectMessage(Request.Query) { RequestType = OpenIdConnectRequestType.AuthenticationRequest }; } else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrEmpty(Request.ContentType)) { Options.Logger.LogError("The authorization request was rejected because " + "the mandatory 'Content-Type' header was missing."); return(await SendAuthorizationResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed authorization 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 authorization request was rejected because an invalid 'Content-Type' " + "header was received: {ContentType}.", Request.ContentType); return(await SendAuthorizationResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed authorization request has been received: " + "the 'Content-Type' header contained an unexcepted value. " + "Make sure to use 'application/x-www-form-urlencoded'." })); } // Create a new authorization request using the // parameters retrieved from the request form. request = new OpenIdConnectMessage(await Request.ReadFormAsync()) { RequestType = OpenIdConnectRequestType.AuthenticationRequest }; } else { Options.Logger.LogError("The authorization request was rejected because an invalid " + "HTTP method was received: {Method}.", Request.Method); return(await SendAuthorizationResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed authorization request has been received: " + "make sure to use either GET or POST." })); } // Re-assemble the authorization request using the distributed cache if // a 'unique_id' parameter has been extracted from the received message. var identifier = request.GetRequestId(); if (!string.IsNullOrEmpty(identifier)) { var buffer = await Options.Cache.GetAsync($"asos-request:{identifier}"); if (buffer == null) { Options.Logger.LogError("A request_id was extracted from the authorization request ({RequestId}) " + "but no corresponding entry was found in the cache.", identifier); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "Invalid request: timeout expired." })); } using (var stream = new MemoryStream(buffer)) using (var reader = new BinaryReader(stream)) { // Make sure the stored authorization request // has been serialized using the same method. var version = reader.ReadInt32(); if (version != 1) { await Options.Cache.RemoveAsync($"asos-request:{identifier}"); Options.Logger.LogError("The authorization request retrieved from the cache was invalid."); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "Invalid request: timeout expired." })); } for (int index = 0, length = reader.ReadInt32(); index < length; index++) { var name = reader.ReadString(); var value = reader.ReadString(); // Skip restoring the parameter retrieved from the stored request // if the OpenID Connect message extracted from the query string // or the request form defined the same parameter. if (!request.Parameters.ContainsKey(name)) { request.SetParameter(name, value); } } } } // Store the authorization request in the OWIN context. Context.SetOpenIdConnectRequest(request); // client_id is mandatory parameter and MUST cause an error when missing. // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest if (string.IsNullOrEmpty(request.ClientId)) { Options.Logger.LogError("The authorization request was rejected because " + "the mandatory 'client_id' parameter was missing."); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "client_id was missing" })); } // While redirect_uri was not mandatory in OAuth2, this parameter // is now declared as REQUIRED and MUST cause an error when missing. // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest // To keep AspNet.Security.OpenIdConnect.Server compatible with pure OAuth2 clients, // an error is only returned if the request was made by an OpenID Connect client. if (string.IsNullOrEmpty(request.RedirectUri) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId)) { Options.Logger.LogError("The authorization request was rejected because " + "the mandatory 'redirect_uri' parameter was missing."); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "redirect_uri must be included when making an OpenID Connect request" })); } if (!string.IsNullOrEmpty(request.RedirectUri)) { // Note: when specified, redirect_uri MUST be an absolute URI. // See http://tools.ietf.org/html/rfc6749#section-3.1.2 // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest Uri uri; if (!Uri.TryCreate(request.RedirectUri, UriKind.Absolute, out uri)) { Options.Logger.LogError("The authorization request was rejected because the 'redirect_uri' parameter " + "didn't correspond to a valid absolute URL: {RedirectUri}.", request.RedirectUri); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "redirect_uri must be absolute" })); } // Note: when specified, redirect_uri MUST NOT include a fragment component. // See http://tools.ietf.org/html/rfc6749#section-3.1.2 // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest else if (!string.IsNullOrEmpty(uri.Fragment)) { Options.Logger.LogError("The authorization request was rejected because the 'redirect_uri' " + "contained a URL segment: {RedirectUri}.", request.RedirectUri); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "redirect_uri must not include a fragment" })); } } // Reject requests using the unsupported request parameter. if (!string.IsNullOrEmpty(request.GetParameter(OpenIdConnectConstants.Parameters.Request))) { Options.Logger.LogError("The authorization request was rejected because it contained " + "an unsupported parameter: {Parameter}.", "request"); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.RequestNotSupported, ErrorDescription = "The request parameter is not supported." })); } // Reject requests using the unsupported request_uri parameter. else if (!string.IsNullOrEmpty(request.RequestUri)) { Options.Logger.LogError("The authorization request was rejected because it contained " + "an unsupported parameter: {Parameter}.", "request_uri"); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.RequestUriNotSupported, ErrorDescription = "The request_uri parameter is not supported." })); } // Reject requests missing the mandatory response_type parameter. else if (string.IsNullOrEmpty(request.ResponseType)) { Options.Logger.LogError("The authorization request was rejected because " + "the mandatory 'response_type' parameter was missing."); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "response_type parameter missing" })); } // Reject requests whose response_type parameter is unsupported. else if (!request.IsNoneFlow() && !request.IsAuthorizationCodeFlow() && !request.IsImplicitFlow() && !request.IsHybridFlow()) { Options.Logger.LogError("The authorization request was rejected because the 'response_type' " + "parameter was invalid: {ResponseType}.", request.ResponseType); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.UnsupportedResponseType, ErrorDescription = "response_type unsupported" })); } // Reject requests whose response_mode is unsupported. else if (!request.IsFormPostResponseMode() && !request.IsFragmentResponseMode() && !request.IsQueryResponseMode()) { Options.Logger.LogError("The authorization request was rejected because the 'response_mode' " + "parameter was invalid: {ResponseMode}.", request.ResponseMode); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "response_mode unsupported" })); } // response_mode=query (explicit or not) and a response_type containing id_token // or token are not considered as a safe combination and MUST be rejected. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security else if (request.IsQueryResponseMode() && (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) || request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token))) { Options.Logger.LogError("The authorization request was rejected because the 'response_type'/'response_mode' combination " + "was unsafe: {ResponseType} ; {ResponseMode}.", request.ResponseType, request.ResponseMode); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "response_type/response_mode combination unsupported" })); } // Reject OpenID Connect implicit/hybrid requests missing the mandatory nonce parameter. // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest, // http://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters // and http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken. else if (string.IsNullOrEmpty(request.Nonce) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && (request.IsImplicitFlow() || request.IsHybridFlow())) { Options.Logger.LogError("The authorization request was rejected because " + "the mandatory 'nonce' parameter was missing."); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "nonce parameter missing" })); } // Reject requests containing the id_token response_mode if no openid scope has been received. else if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) && !request.HasScope(OpenIdConnectConstants.Scopes.OpenId)) { Options.Logger.LogError("The authorization request was rejected because the 'openid' scope was missing."); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "openid scope missing" })); } // Reject requests containing the code response_mode if the token endpoint has been disabled. else if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code) && !Options.TokenEndpointPath.HasValue) { Options.Logger.LogError("The authorization request was rejected because the authorization code flow was disabled."); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.UnsupportedResponseType, ErrorDescription = "response_type=code is not supported by this server" })); } var context = new ValidateAuthorizationRequestContext(Context, Options, request); await Options.Provider.ValidateAuthorizationRequest(context); // Stop processing the request if Validated was not called. if (!context.IsValidated) { Options.Logger.LogInformation("The authorization request was rejected by application code."); return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } identifier = request.GetRequestId(); if (string.IsNullOrEmpty(identifier)) { // Generate a new 256-bits identifier and associate it with the authorization request. identifier = Options.RandomNumberGenerator.GenerateKey(length: 256 / 8); request.SetRequestId(identifier); using (var stream = new MemoryStream()) using (var writer = new BinaryWriter(stream)) { writer.Write(/* version: */ 1); writer.Write(request.Parameters.Count); foreach (var parameter in request.Parameters) { writer.Write(parameter.Key); writer.Write(parameter.Value); } // Serialize the authorization request. var bytes = stream.ToArray(); // Store the authorization request in the distributed cache. await Options.Cache.SetAsync($"asos-request:{identifier}", bytes, new DistributedCacheEntryOptions { AbsoluteExpiration = Options.SystemClock.UtcNow + TimeSpan.FromHours(1) }); } } var notification = new HandleAuthorizationRequestContext(Context, Options, request); await Options.Provider.HandleAuthorizationRequest(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } return(false); }
private async Task <bool> InvokeUserinfoEndpointAsync() { OpenIdConnectMessage request; if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { request = new OpenIdConnectMessage(Request.Query.ToDictionary()); } else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrWhiteSpace(Request.ContentType)) { Logger.LogError("The userinfo request was rejected because " + "the mandatory 'Content-Type' header was missing."); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed userinfo 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)) { Logger.LogError("The userinfo request was rejected because an invalid 'Content-Type' " + "header was received: {ContentType}.", Request.ContentType); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed userinfo request has been received: " + "the 'Content-Type' header contained an unexcepted value. " + "Make sure to use 'application/x-www-form-urlencoded'." })); } var form = await Request.ReadFormAsync(Context.RequestAborted); request = new OpenIdConnectMessage(form.ToDictionary()); } else { Logger.LogError("The userinfo request was rejected because an invalid " + "HTTP method was received: {Method}.", Request.Method); return(await SendErrorPageAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed userinfo request has been received: " + "make sure to use either GET or POST." })); } // Insert the userinfo request in the ASP.NET context. Context.SetOpenIdConnectRequest(request); string token; if (!string.IsNullOrEmpty(request.AccessToken)) { token = request.AccessToken; } else { string header = Request.Headers[HeaderNames.Authorization]; if (string.IsNullOrEmpty(header)) { Logger.LogError("The userinfo request was rejected because " + "the 'Authorization' header was missing."); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed userinfo request has been received." })); } if (!header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The userinfo request was rejected because the " + "'Authorization' header was invalid: {Header}.", header); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed userinfo request has been received." })); } token = header.Substring("Bearer ".Length); if (string.IsNullOrEmpty(token)) { Logger.LogError("The userinfo request was rejected because the access token was missing."); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed userinfo request has been received." })); } } var ticket = await DeserializeAccessTokenAsync(token, request); if (ticket == null) { Logger.LogError("The userinfo request was rejected because access token was invalid."); // Note: an invalid token should result in an unauthorized response // but returning a 401 status would invoke the previously registered // authentication middleware and potentially replace it by a 302 response. // To work around this limitation, a 400 error is returned instead. // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoError return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Invalid token." })); } if (!ticket.Properties.ExpiresUtc.HasValue || ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Logger.LogError("The userinfo request was rejected because access token was expired."); // Note: an invalid token should result in an unauthorized response // but returning a 401 status would invoke the previously registered // authentication middleware and potentially replace it by a 302 response. // To work around this limitation, a 400 error is returned instead. // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoError return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Expired token." })); } var validatingContext = new ValidateUserinfoRequestContext(Context, Options, request); await Options.Provider.ValidateUserinfoRequest(validatingContext); if (!validatingContext.IsValidated) { Logger.LogInformation("The userinfo request was rejected by application code."); return(await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = validatingContext.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = validatingContext.ErrorDescription, ErrorUri = validatingContext.ErrorUri })); } var notification = new HandleUserinfoRequestContext(Context, Options, request, ticket); notification.Subject = ticket.Principal.GetClaim(ClaimTypes.NameIdentifier); notification.Issuer = Context.GetIssuer(Options); // Note: when receiving an access token, its audiences list cannot be used for the "aud" claim // as the client application is not the intented audience but only an authorized presenter. // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse foreach (var presenter in ticket.GetPresenters()) { notification.Audiences.Add(presenter); } // The following claims are all optional and should be excluded when // no corresponding value has been found in the authentication ticket. if (ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) { notification.FamilyName = ticket.Principal.GetClaim(ClaimTypes.Surname); notification.GivenName = ticket.Principal.GetClaim(ClaimTypes.GivenName); notification.BirthDate = ticket.Principal.GetClaim(ClaimTypes.DateOfBirth); } if (ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) { notification.Email = ticket.Principal.GetClaim(ClaimTypes.Email); } ; if (ticket.HasScope(OpenIdConnectConstants.Scopes.Phone)) { notification.PhoneNumber = ticket.Principal.GetClaim(ClaimTypes.HomePhone) ?? ticket.Principal.GetClaim(ClaimTypes.MobilePhone) ?? ticket.Principal.GetClaim(ClaimTypes.OtherPhone); } ; await Options.Provider.HandleUserinfoRequest(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } // Ensure the "sub" claim has been correctly populated. if (string.IsNullOrEmpty(notification.Subject)) { Logger.LogError("The mandatory 'sub' claim was missing from the userinfo response."); Response.StatusCode = 500; await SendErrorPayloadAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "The mandatory 'sub' claim was missing." }); return(true); } var payload = new JObject(); payload.Add(OpenIdConnectConstants.Claims.Subject, notification.Subject); if (notification.Address != null) { payload[OpenIdConnectConstants.Claims.Address] = notification.Address; } if (!string.IsNullOrEmpty(notification.BirthDate)) { payload[OpenIdConnectConstants.Claims.Birthdate] = notification.BirthDate; } if (!string.IsNullOrEmpty(notification.Email)) { payload[OpenIdConnectConstants.Claims.Email] = notification.Email; } if (notification.EmailVerified.HasValue) { payload[OpenIdConnectConstants.Claims.EmailVerified] = notification.EmailVerified.Value; } if (!string.IsNullOrEmpty(notification.FamilyName)) { payload[OpenIdConnectConstants.Claims.FamilyName] = notification.FamilyName; } if (!string.IsNullOrEmpty(notification.GivenName)) { payload[OpenIdConnectConstants.Claims.GivenName] = notification.GivenName; } if (!string.IsNullOrEmpty(notification.Issuer)) { payload[OpenIdConnectConstants.Claims.Issuer] = notification.Issuer; } if (!string.IsNullOrEmpty(notification.PhoneNumber)) { payload[OpenIdConnectConstants.Claims.PhoneNumber] = notification.PhoneNumber; } if (notification.PhoneNumberVerified.HasValue) { payload[OpenIdConnectConstants.Claims.PhoneNumberVerified] = notification.PhoneNumberVerified.Value; } if (!string.IsNullOrEmpty(notification.PreferredUsername)) { payload[OpenIdConnectConstants.Claims.PreferredUsername] = notification.PreferredUsername; } if (!string.IsNullOrEmpty(notification.Profile)) { payload[OpenIdConnectConstants.Claims.Profile] = notification.Profile; } if (!string.IsNullOrEmpty(notification.Website)) { payload[OpenIdConnectConstants.Claims.Website] = notification.Website; } switch (notification.Audiences.Count) { case 0: break; case 1: payload.Add(OpenIdConnectConstants.Claims.Audience, notification.Audiences[0]); break; default: payload.Add(OpenIdConnectConstants.Claims.Audience, JArray.FromObject(notification.Audiences)); break; } foreach (var claim in notification.Claims) { // Ignore claims whose value is null. if (claim.Value == null) { continue; } payload.Add(claim.Key, claim.Value); } var context = new ApplyUserinfoResponseContext(Context, Options, request, payload); await Options.Provider.ApplyUserinfoResponse(context); if (context.HandledResponse) { return(true); } else if (context.Skipped) { return(false); } using (var buffer = new MemoryStream()) using (var writer = new JsonTextWriter(new StreamWriter(buffer))) { payload.WriteTo(writer); writer.Flush(); Response.ContentLength = buffer.Length; Response.ContentType = "application/json;charset=UTF-8"; Response.Headers[HeaderNames.CacheControl] = "no-cache"; Response.Headers[HeaderNames.Pragma] = "no-cache"; Response.Headers[HeaderNames.Expires] = "-1"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted); } return(true); }
private Task <bool> SendRevocationResponseAsync(OpenIdConnectMessage request, OpenIdConnectMessage response) { var payload = new JObject(); foreach (var parameter in response.Parameters) { payload[parameter.Key] = parameter.Value; } return(SendRevocationResponseAsync(request, payload)); }
private async Task <bool> InvokeRevocationEndpointAsync() { if (!string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { Options.Logger.LogError("The revocation request was rejected because an invalid " + "HTTP method was received: {Method}.", Request.Method); return(await SendRevocationResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed revocation request has been received: " + "make sure to use either GET or POST." })); } // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrEmpty(Request.ContentType)) { Options.Logger.LogError("The revocation request was rejected because " + "the mandatory 'Content-Type' header was missing."); return(await SendRevocationResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed revocation 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 revocation request was rejected because an invalid 'Content-Type' " + "header was received: {ContentType}.", Request.ContentType); return(await SendRevocationResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed revocation request has been received: " + "the 'Content-Type' header contained an unexcepted value. " + "Make sure to use 'application/x-www-form-urlencoded'." })); } var request = new OpenIdConnectMessage(await Request.ReadFormAsync()); if (string.IsNullOrWhiteSpace(request.Token)) { return(await SendRevocationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed revocation request has been received: " + "a 'token' parameter with an access or refresh token is required." })); } // Insert the revocation request in the OWIN context. Context.SetOpenIdConnectRequest(request); // 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 ValidateRevocationRequestContext(Context, Options, request); await Options.Provider.ValidateRevocationRequest(context); if (context.IsRejected) { Options.Logger.LogError("The revocation request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendRevocationResponseAsync(request, new OpenIdConnectMessage { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } // Ensure that the client_id has been set from the ValidateRevocationRequest event. else if (context.IsValidated && string.IsNullOrEmpty(request.ClientId)) { Options.Logger.LogError("The revocation request was validated but the client_id was not set."); return(await SendRevocationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal server error occurred." })); } AuthenticationTicket ticket = null; // Note: use the "token_type_hint" parameter to determine // the type of the token sent by the client application. // See https://tools.ietf.org/html/rfc7009#section-2.1 switch (request.GetTokenTypeHint()) { case OpenIdConnectConstants.TokenTypeHints.AccessToken: ticket = await DeserializeAccessTokenAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.RefreshToken: ticket = await DeserializeRefreshTokenAsync(request.Token, request); break; } // Note: if the token can't be found using "token_type_hint", // the search must be extended to all supported token types. // See https://tools.ietf.org/html/rfc7009#section-2.1 if (ticket == null) { ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); } if (ticket == null) { Options.Logger.LogInformation("The revocation request was ignored because the token was invalid."); return(await SendRevocationResponseAsync(request, new OpenIdConnectMessage())); } // If the ticket is already expired, directly return a 200 response. else if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Options.Logger.LogInformation("The revocation request was ignored because the token was already expired."); return(await SendRevocationResponseAsync(request, new OpenIdConnectMessage())); } // Note: unlike refresh tokens that can only be revoked by client applications, // access tokens can be revoked by either resource servers or client applications: // in both cases, the caller must be authenticated if the ticket is marked as confidential. if (context.IsSkipped && ticket.IsConfidential()) { Options.Logger.LogWarning("The revocation request was rejected because the caller was not authenticated."); return(await SendRevocationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest })); } // When a client_id can be inferred from the revocation request, // ensure that the client application is an authorized presenter. if (!string.IsNullOrEmpty(request.ClientId) && ticket.HasPresenter() && !ticket.HasPresenter(request.ClientId)) { Options.Logger.LogWarning("The revocation request was rejected because the " + "refresh token was issued to a different client."); return(await SendRevocationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest })); } var notification = new HandleRevocationRequestContext(Context, Options, request, ticket); await Options.Provider.HandleRevocationRequest(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } if (!notification.Revoked) { return(await SendRevocationResponseAsync(request, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.UnsupportedTokenType, ErrorDescription = "The token cannot be revoked." })); } return(await SendRevocationResponseAsync(request, new JObject())); }
private async Task <bool> InvokeLogoutEndpointAsync() { OpenIdConnectMessage request; // Note: logout requests must be made via GET but POST requests // are also accepted to allow flowing large logout payloads. // See https://openid.net/specs/openid-connect-session-1_0.html#RPLogout if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { request = new OpenIdConnectMessage(Request.Query) { RequestType = OpenIdConnectRequestType.LogoutRequest }; } else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrEmpty(Request.ContentType)) { Options.Logger.LogError("The logout request was rejected because " + "the mandatory 'Content-Type' header was missing."); return(await SendLogoutResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed logout 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 logout request was rejected because an invalid 'Content-Type' " + "header was received: {ContentType}.", Request.ContentType); return(await SendLogoutResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed logout request has been received: " + "the 'Content-Type' header contained an unexcepted value. " + "Make sure to use 'application/x-www-form-urlencoded'." })); } request = new OpenIdConnectMessage(await Request.ReadFormAsync()) { RequestType = OpenIdConnectRequestType.LogoutRequest }; } else { Options.Logger.LogError("The logout request was rejected because an invalid " + "HTTP method was received: {Method}.", Request.Method); return(await SendLogoutResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed logout request has been received: " + "make sure to use either GET or POST." })); } // Store the logout request in the OWIN context. Context.SetOpenIdConnectRequest(request); var context = new ValidateLogoutRequestContext(Context, Options, request); await Options.Provider.ValidateLogoutRequest(context); if (context.IsRejected) { Options.Logger.LogError("The logout request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendLogoutResponseAsync(request, new OpenIdConnectMessage { Error = context.Error, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } var notification = new HandleLogoutRequestContext(Context, Options, request); await Options.Provider.HandleLogoutRequest(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } return(false); }
private async Task <bool> SendLogoutResponseAsync(OpenIdConnectMessage request, OpenIdConnectMessage response) { if (request == null) { request = new OpenIdConnectMessage(); } var notification = new ApplyLogoutResponseContext(Context, Options, request, response); await Options.Provider.ApplyLogoutResponse(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } if (!string.IsNullOrEmpty(response.Error)) { // When returning an error, remove the logout request from the OWIN context // to inform TeardownCoreAsync that there's nothing more to handle. Context.SetOpenIdConnectRequest(request: null); // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { Context.SetOpenIdConnectResponse(response); // Return false to allow the rest of // the pipeline to handle the request. return(false); } return(await SendNativePageAsync(response)); } // Don't redirect the user agent if no explicit post_logout_redirect_uri was // provided or if the URI was not fully validated by the application code. if (string.IsNullOrEmpty(response.PostLogoutRedirectUri)) { return(true); } var location = response.PostLogoutRedirectUri; foreach (var parameter in response.Parameters) { // Don't include post_logout_redirect_uri in the query string. if (string.Equals(parameter.Key, OpenIdConnectParameterNames.PostLogoutRedirectUri, StringComparison.Ordinal)) { continue; } location = WebUtilities.AddQueryString(location, parameter.Key, parameter.Value); } Response.Redirect(location); return(true); }
/// <summary> /// Tells the middleware to skip the code redemption process. The developer may have redeemed the code themselves, or /// decided that the redemption was not required. If tokens were retrieved that are needed for further processing then /// call one of the overloads that allows providing tokens. An IdToken is required if one had not been previously received /// in the authorization response. An access token can optionally be provided for the middleware to contact the /// user-info endpoint. Calling this is the same as setting TokenEndpointResponse. /// </summary> public void HandleCodeRedemption(OpenIdConnectMessage tokenEndpointResponse) { TokenEndpointResponse = tokenEndpointResponse; }
/// <summary> /// Handles Signout /// </summary> /// <returns></returns> protected override async Task HandleSignOutAsync(SignOutContext signout) { if (signout != null) { if (_configuration == null && Options.ConfigurationManager != null) { _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); } var message = new OpenIdConnectMessage() { IssuerAddress = _configuration == null ? string.Empty : (_configuration.EndSessionEndpoint ?? string.Empty), }; // Set End_Session_Endpoint in order: // 1. properties.Redirect // 2. Options.PostLogoutRedirectUri var properties = new AuthenticationProperties(signout.Properties); var logoutRedirectUri = properties.RedirectUri; if (!string.IsNullOrEmpty(logoutRedirectUri)) { // Relative to PathBase if (logoutRedirectUri.StartsWith("/", StringComparison.Ordinal)) { logoutRedirectUri = BuildRedirectUri(logoutRedirectUri); } message.PostLogoutRedirectUri = logoutRedirectUri; } else if (!string.IsNullOrEmpty(Options.PostLogoutRedirectUri)) { logoutRedirectUri = Options.PostLogoutRedirectUri; // Relative to PathBase if (logoutRedirectUri.StartsWith("/", StringComparison.Ordinal)) { logoutRedirectUri = BuildRedirectUri(logoutRedirectUri); } message.PostLogoutRedirectUri = logoutRedirectUri; } message.IdTokenHint = await Context.Authentication.GetTokenAsync(OpenIdConnectParameterNames.IdToken); var redirectContext = new RedirectContext(Context, Options, properties) { ProtocolMessage = message }; await Options.Events.RedirectToIdentityProviderForSignOut(redirectContext); if (redirectContext.HandledResponse) { Logger.RedirectToIdentityProviderForSignOutHandledResponse(); return; } else if (redirectContext.Skipped) { Logger.RedirectToIdentityProviderForSignOutSkipped(); return; } message = redirectContext.ProtocolMessage; if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet) { var redirectUri = message.CreateLogoutRequestUrl(); if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute)) { Logger.InvalidLogoutQueryStringRedirectUrl(redirectUri); } Response.Redirect(redirectUri); } else if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost) { var inputs = new StringBuilder(); foreach (var parameter in message.Parameters) { var name = HtmlEncoder.Encode(parameter.Key); var value = HtmlEncoder.Encode(parameter.Value); var input = string.Format(CultureInfo.InvariantCulture, InputTagFormat, name, value); inputs.AppendLine(input); } var issuer = HtmlEncoder.Encode(message.IssuerAddress); var content = string.Format(CultureInfo.InvariantCulture, HtmlFormFormat, issuer, inputs); var buffer = Encoding.UTF8.GetBytes(content); Response.ContentLength = buffer.Length; Response.ContentType = "text/html;charset=UTF-8"; // Emit Cache-Control=no-cache to prevent client caching. Response.Headers[HeaderNames.CacheControl] = "no-cache"; Response.Headers[HeaderNames.Pragma] = "no-cache"; Response.Headers[HeaderNames.Expires] = "-1"; await Response.Body.WriteAsync(buffer, 0, buffer.Length); } } }
private async Task <bool> SendAuthorizationResponseAsync( OpenIdConnectMessage request, OpenIdConnectMessage response, AuthenticationTicket ticket = null) { if (request == null) { request = new OpenIdConnectMessage(); } var notification = new ApplyAuthorizationResponseContext(Context, Options, ticket, request, response); await Options.Provider.ApplyAuthorizationResponse(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } if (!string.IsNullOrEmpty(response.Error)) { // When returning an error, remove the authorization request from the OWIN context // to inform TeardownCoreAsync that there's nothing more to handle. Context.SetOpenIdConnectRequest(request: null); // Directly display an error page if redirect_uri cannot be used to // redirect the user agent back to the client application. if (string.IsNullOrEmpty(response.RedirectUri)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { Context.SetOpenIdConnectResponse(response); // Return false to allow the rest of // the pipeline to handle the request. return(false); } return(await SendNativePageAsync(response)); } } // Note: at this stage, the redirect_uri parameter MUST be trusted. if (request.IsFormPostResponseMode()) { using (var buffer = new MemoryStream()) using (var writer = new StreamWriter(buffer)) { writer.WriteLine("<!doctype html>"); writer.WriteLine("<html>"); writer.WriteLine("<body>"); // While the redirect_uri parameter should be guarded against unknown values // by IOpenIdConnectServerProvider.ValidateAuthorizationRequest, // it's still safer to encode it to avoid cross-site scripting attacks // if the authorization server has a relaxed policy concerning redirect URIs. writer.WriteLine($"<form name='form' method='post' action='{Options.HtmlEncoder.Encode(response.RedirectUri)}'>"); foreach (var parameter in response.Parameters) { // Don't include redirect_uri in the form. if (string.Equals(parameter.Key, OpenIdConnectParameterNames.RedirectUri, StringComparison.Ordinal)) { continue; } var key = Options.HtmlEncoder.Encode(parameter.Key); var value = Options.HtmlEncoder.Encode(parameter.Value); writer.WriteLine($"<input type='hidden' name='{key}' value='{value}' />"); } writer.WriteLine("<noscript>Click here to finish the authorization process: <input type='submit' /></noscript>"); writer.WriteLine("</form>"); writer.WriteLine("<script>document.form.submit();</script>"); writer.WriteLine("</body>"); writer.WriteLine("</html>"); writer.Flush(); Response.StatusCode = 200; Response.ContentLength = buffer.Length; Response.ContentType = "text/html;charset=UTF-8"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Request.CallCancelled); return(true); } } else if (request.IsFragmentResponseMode()) { var location = response.RedirectUri; var appender = new Appender(location, '#'); foreach (var parameter in response.Parameters) { // Don't include redirect_uri in the fragment. if (string.Equals(parameter.Key, OpenIdConnectParameterNames.RedirectUri, StringComparison.Ordinal)) { continue; } appender.Append(parameter.Key, parameter.Value); } Response.Redirect(appender.ToString()); return(true); } else if (request.IsQueryResponseMode()) { var location = response.RedirectUri; foreach (var parameter in response.Parameters) { // Don't include redirect_uri in the query string. if (string.Equals(parameter.Key, OpenIdConnectParameterNames.RedirectUri, StringComparison.Ordinal)) { continue; } location = WebUtilities.AddQueryString(location, parameter.Key, parameter.Value); } Response.Redirect(location); return(true); } return(await SendNativePageAsync(response)); }
protected override async Task <OpenIdConnectMessage> RedeemAuthorizationCodeAsync(OpenIdConnectMessage tokenEndpointRequest) { //Logger.RedeemingCodeForTokens(); OpenIdConnectHandler idConnectHandler = this; OpenIdConnectConfiguration configurationAsync = await idConnectHandler.Options.ConfigurationManager.GetConfigurationAsync(CancellationToken.None); var requestMessage = new HttpRequestMessage(HttpMethod.Post, configurationAsync.TokenEndpoint); //add header ipv body var basicAuthHeader = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Options.ClientId}:{Options.ClientSecret}")); requestMessage.Headers.Add("Authorization", basicAuthHeader); var parameters = tokenEndpointRequest.Parameters; parameters.Remove("client_id"); parameters.Remove("client_secret"); requestMessage.Content = new FormUrlEncodedContent(parameters); var responseMessage = await Backchannel.SendAsync(requestMessage); var contentMediaType = responseMessage.Content.Headers.ContentType?.MediaType; if (string.IsNullOrEmpty(contentMediaType)) { Logger.LogDebug($"Unexpected token response format. Status Code: {(int)responseMessage.StatusCode}. Content-Type header is missing."); } else if (!string.Equals(contentMediaType, "application/json", StringComparison.OrdinalIgnoreCase)) { Logger.LogDebug($"Unexpected token response format. Status Code: {(int)responseMessage.StatusCode}. Content-Type {responseMessage.Content.Headers.ContentType}."); } // Error handling: // 1. If the response body can't be parsed as json, throws. // 2. If the response's status code is not in 2XX range, throw OpenIdConnectProtocolException. If the body is correct parsed, // pass the error information from body to the exception. OpenIdConnectMessage message; try { var responseContent = await responseMessage.Content.ReadAsStringAsync(); message = new OpenIdConnectMessage(responseContent); } catch (Exception ex) { throw new OpenIdConnectProtocolException($"Failed to parse token response body as JSON. Status Code: {(int)responseMessage.StatusCode}. Content-Type: {responseMessage.Content.Headers.ContentType}", ex); } if (!responseMessage.IsSuccessStatusCode) { //throw CreateOpenIdConnectProtocolException(message, responseMessage); } return(message); }
/// <summary> /// Tells the handler to skip the code redemption process. The developer may have redeemed the code themselves, or /// decided that the redemption was not required. If tokens were retrieved that are needed for further processing then /// call one of the overloads that allows providing tokens. An IdToken is required if one had not been previously received /// in the authorization response. Calling this is the same as setting TokenEndpointResponse. /// </summary> public void HandleCodeRedemption(OpenIdConnectMessage tokenEndpointResponse) { TokenEndpointResponse = tokenEndpointResponse; }
public async Task AuthenticateCoreState(Action<OpenIdConnectOptions> action, OpenIdConnectMessage message) { var handler = new OpenIdConnectHandlerForTestingAuthenticate(); var server = CreateServer(action, UrlEncoder.Default, handler); await server.CreateClient().PostAsync("http://localhost", new FormUrlEncodedContent(message.Parameters.Where(pair => pair.Value != null))); }
/// <summary> /// Tells the handler to skip the code redemption process. The developer may have redeemed the code themselves, or /// decided that the redemption was not required. If tokens were retrieved that are needed for further processing then /// call one of the overloads that allows providing tokens. An IdToken is required if one had not been previously received /// in the authorization response. Calling this is the same as setting TokenEndpointResponse. /// </summary> public void HandleCodeRedemption() { TokenEndpointResponse = new OpenIdConnectMessage(); }
/// <summary> /// Initializes a new instance of the <see cref="ValidateClientAuthenticationContext"/> class /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <param name="request"></param> internal ValidateClientAuthenticationContext( HttpContext context, OpenIdConnectServerOptions options, OpenIdConnectMessage request) : base(context, options, request) { }
private async Task <string> SerializeIdentityTokenAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectMessage request, OpenIdConnectMessage response) { // properties.IssuedUtc and properties.ExpiresUtc // should always be preferred when explicitly set. if (properties.IssuedUtc == null) { properties.IssuedUtc = Options.SystemClock.UtcNow; } if (properties.ExpiresUtc == null) { properties.ExpiresUtc = properties.IssuedUtc + Options.IdentityTokenLifetime; } // Replace the identity by a new one containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). identity = identity.Clone(claim => { // Never exclude ClaimTypes.NameIdentifier. if (string.Equals(claim.Type, ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase)) { return(true); } // Claims whose destination is not explicitly referenced or doesn't // contain "id_token" are not included in the identity token. return(claim.HasDestination(OpenIdConnectConstants.Destinations.IdentityToken)); }); // Create a new ticket containing the updated properties and the filtered identity. var ticket = new AuthenticationTicket(identity, properties); ticket.SetUsage(OpenIdConnectConstants.Usages.IdentityToken); // Associate a random identifier with the identity token. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the identity token. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetAudiences(request.ClientId); ticket.SetPresenters(request.ClientId); } var notification = new SerializeIdentityTokenContext(Context, Options, request, response, ticket) { Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.IdentityTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault() }; await Options.Provider.SerializeIdentityToken(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.IdentityToken)) { return(notification.IdentityToken); } else if (notification.Skipped) { return(null); } if (notification.SecurityTokenHandler == null) { return(null); } if (!identity.HasClaim(claim => claim.Type == OpenIdConnectConstants.Claims.Subject) && !identity.HasClaim(claim => claim.Type == ClaimTypes.NameIdentifier)) { throw new InvalidOperationException("A unique identifier cannot be found to generate a 'sub' claim: " + "make sure to add a 'ClaimTypes.NameIdentifier' claim."); } if (notification.SigningCredentials == null) { throw new InvalidOperationException("A signing key must be provided."); } // Store the unique subject identifier as a claim. if (!identity.HasClaim(claim => claim.Type == OpenIdConnectConstants.Claims.Subject)) { identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identity.GetClaim(ClaimTypes.NameIdentifier)); } // Remove the ClaimTypes.NameIdentifier claims to avoid getting duplicate claims. // Note: the "sub" claim is automatically mapped by JwtSecurityTokenHandler // to ClaimTypes.NameIdentifier when validating a JWT token. // Note: make sure to call ToArray() to avoid an InvalidOperationException // on old versions of Mono, where FindAll() is implemented using an iterator. foreach (var claim in identity.FindAll(ClaimTypes.NameIdentifier).ToArray()) { identity.RemoveClaim(claim); } // Store the "unique_id" property as a claim. ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.JwtId, ticket.GetTicketId()); // Store the "usage" property as a claim. ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Usage, ticket.GetUsage()); // If the ticket is marked as confidential, add a new // "confidential" claim in the security token. if (ticket.IsConfidential()) { ticket.Identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Confidential, "true", ClaimValueTypes.Boolean)); } // Store the audiences as claims. foreach (var audience in notification.Audiences) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Audience, audience); } // If a nonce was present in the authorization request, it MUST // be included in the id_token generated by the token endpoint. // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation var nonce = request.Nonce; if (request.IsAuthorizationCodeGrantType()) { // Restore the nonce stored in the authentication // ticket extracted from the authorization code. nonce = ticket.GetNonce(); } if (!string.IsNullOrEmpty(nonce)) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Nonce, nonce); } using (var algorithm = HashAlgorithm.Create(notification.SigningCredentials.DigestAlgorithm)) { // Create an authorization code hash if necessary. if (!string.IsNullOrEmpty(response.Code)) { var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(response.Code)); // Note: only the left-most half of the hash of the octets is used. // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken identity.AddClaim(OpenIdConnectConstants.Claims.CodeHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2)); } // Create an access token hash if necessary. if (!string.IsNullOrEmpty(response.AccessToken)) { var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(response.AccessToken)); // Note: only the left-most half of the hash of the octets is used. // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken identity.AddClaim(OpenIdConnectConstants.Claims.AccessTokenHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2)); } } // Extract the presenters from the authentication ticket. var presenters = notification.Presenters.ToArray(); switch (presenters.Length) { case 0: break; case 1: identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]); break; default: Options.Logger.LogWarning("Multiple presenters have been associated with the identity token " + "but the JWT format only accepts single values."); // Only add the first authorized party. identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]); break; } var token = notification.SecurityTokenHandler.CreateToken( subject: ticket.Identity, issuer: notification.Issuer, signingCredentials: notification.SigningCredentials, notBefore: ticket.Properties.IssuedUtc.Value.UtcDateTime, expires: ticket.Properties.ExpiresUtc.Value.UtcDateTime); token.Payload[OpenIdConnectConstants.Claims.IssuedAt] = EpochTime.GetIntDate(ticket.Properties.IssuedUtc.Value.UtcDateTime); // Try to extract a key identifier from the signing credentials // and add the "kid" property to the JWT header if applicable. LocalIdKeyIdentifierClause clause = null; if (notification.SigningCredentials.SigningKeyIdentifier.TryFind(out clause)) { token.Header[JwtHeaderParameterNames.Kid] = clause.LocalId; } return(notification.SecurityTokenHandler.WriteToken(token)); }
private async Task <bool> InvokeConfigurationEndpointAsync() { // Metadata requests must be made via GET. // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The discovery request was rejected because an invalid " + "HTTP method was used: {Method}.", Request.Method); return(await SendConfigurationResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "Invalid HTTP method: make sure to use GET." })); } var request = new OpenIdConnectMessage(Request.Query.ToDictionary()); var context = new ValidateConfigurationRequestContext(Context, Options); await Options.Provider.ValidateConfigurationRequest(context); if (!context.IsValidated) { Logger.LogInformation("The discovery request was rejected by application code."); return(await SendConfigurationResponseAsync(request, new OpenIdConnectMessage { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } var notification = new HandleConfigurationRequestContext(Context, Options, request); notification.Issuer = Context.GetIssuer(Options); if (Options.AuthorizationEndpointPath.HasValue) { notification.AuthorizationEndpoint = notification.Issuer.AddPath(Options.AuthorizationEndpointPath); } if (Options.CryptographyEndpointPath.HasValue) { notification.CryptographyEndpoint = notification.Issuer.AddPath(Options.CryptographyEndpointPath); } if (Options.UserinfoEndpointPath.HasValue) { notification.UserinfoEndpoint = notification.Issuer.AddPath(Options.UserinfoEndpointPath); } if (Options.IntrospectionEndpointPath.HasValue) { notification.IntrospectionEndpoint = notification.Issuer.AddPath(Options.IntrospectionEndpointPath); } if (Options.TokenEndpointPath.HasValue) { notification.TokenEndpoint = notification.Issuer.AddPath(Options.TokenEndpointPath); } if (Options.LogoutEndpointPath.HasValue) { notification.LogoutEndpoint = notification.Issuer.AddPath(Options.LogoutEndpointPath); } if (Options.AuthorizationEndpointPath.HasValue) { // Only expose the implicit grant type if the token // endpoint has not been explicitly disabled. notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit); if (Options.TokenEndpointPath.HasValue) { // Only expose the authorization code and refresh token grant types // if both the authorization and the token endpoints are enabled. notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode); } } if (Options.TokenEndpointPath.HasValue) { notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken); // If the authorization endpoint is disabled, assume the authorization server will // allow the client credentials and resource owner password credentials grant types. if (!Options.AuthorizationEndpointPath.HasValue) { notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.ClientCredentials); notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Password); } } // Only populate response_modes_supported and response_types_supported // if the authorization endpoint is available. if (Options.AuthorizationEndpointPath.HasValue) { notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.FormPost); notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.Fragment); notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.Query); notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.Token); notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.IdToken); notification.ResponseTypes.Add( OpenIdConnectConstants.ResponseTypes.IdToken + ' ' + OpenIdConnectConstants.ResponseTypes.Token); // Only expose response types containing code when // the token endpoint has not been explicitly disabled. if (Options.TokenEndpointPath.HasValue) { notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.Code); notification.ResponseTypes.Add( OpenIdConnectConstants.ResponseTypes.Code + ' ' + OpenIdConnectConstants.ResponseTypes.Token); notification.ResponseTypes.Add( OpenIdConnectConstants.ResponseTypes.Code + ' ' + OpenIdConnectConstants.ResponseTypes.IdToken); notification.ResponseTypes.Add( OpenIdConnectConstants.ResponseTypes.Code + ' ' + OpenIdConnectConstants.ResponseTypes.IdToken + ' ' + OpenIdConnectConstants.ResponseTypes.Token); } } notification.Scopes.Add(OpenIdConnectConstants.Scopes.OpenId); notification.SubjectTypes.Add(OpenIdConnectConstants.SubjectTypes.Public); notification.SigningAlgorithms.Add(OpenIdConnectConstants.Algorithms.RsaSha256); await Options.Provider.HandleConfigurationRequest(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } var response = new JObject(); response.Add(OpenIdConnectConstants.Metadata.Issuer, notification.Issuer); if (!string.IsNullOrEmpty(notification.AuthorizationEndpoint)) { response.Add(OpenIdConnectConstants.Metadata.AuthorizationEndpoint, notification.AuthorizationEndpoint); } if (!string.IsNullOrEmpty(notification.UserinfoEndpoint)) { response.Add(OpenIdConnectConstants.Metadata.UserinfoEndpoint, notification.UserinfoEndpoint); } if (!string.IsNullOrEmpty(notification.IntrospectionEndpoint)) { response.Add(OpenIdConnectConstants.Metadata.IntrospectionEndpoint, notification.IntrospectionEndpoint); } if (!string.IsNullOrEmpty(notification.TokenEndpoint)) { response.Add(OpenIdConnectConstants.Metadata.TokenEndpoint, notification.TokenEndpoint); } if (!string.IsNullOrEmpty(notification.LogoutEndpoint)) { response.Add(OpenIdConnectConstants.Metadata.EndSessionEndpoint, notification.LogoutEndpoint); } if (!string.IsNullOrEmpty(notification.CryptographyEndpoint)) { response.Add(OpenIdConnectConstants.Metadata.JwksUri, notification.CryptographyEndpoint); } response.Add(OpenIdConnectConstants.Metadata.GrantTypesSupported, JArray.FromObject(notification.GrantTypes.Distinct())); response.Add(OpenIdConnectConstants.Metadata.ResponseModesSupported, JArray.FromObject(notification.ResponseModes.Distinct())); response.Add(OpenIdConnectConstants.Metadata.ResponseTypesSupported, JArray.FromObject(notification.ResponseTypes.Distinct())); response.Add(OpenIdConnectConstants.Metadata.SubjectTypesSupported, JArray.FromObject(notification.SubjectTypes.Distinct())); response.Add(OpenIdConnectConstants.Metadata.ScopesSupported, JArray.FromObject(notification.Scopes.Distinct())); response.Add(OpenIdConnectConstants.Metadata.IdTokenSigningAlgValuesSupported, JArray.FromObject(notification.SigningAlgorithms.Distinct())); return(await SendConfigurationResponseAsync(request, response)); }
private async Task <AuthenticationTicket> DeserializeIdentityTokenAsync(string token, OpenIdConnectMessage 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.SigningKey), ValidIssuer = Context.GetIssuer(Options), ValidateAudience = false, ValidateLifetime = false }; if (notification.SecurityTokenHandler is JwtSecurityTokenHandler) { notification.TokenValidationParameters.IssuerSigningTokens = from credentials in Options.SigningCredentials where credentials.SigningKeyIdentifier != null from clause in credentials.SigningKeyIdentifier.OfType <LocalIdKeyIdentifierClause>() select new NamedKeySecurityToken(OpenIdConnectConstants.Claims.KeyId, clause.LocalId, credentials.SigningKey); } await Options.Provider.DeserializeIdentityToken(notification); if (notification.HandledResponse || notification.Ticket != null) { 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)) { Options.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) { Options.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((ClaimsIdentity)principal.Identity, properties); 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 confidential = principal.FindFirst(OpenIdConnectConstants.Claims.Confidential); if (confidential != null && string.Equals(confidential.Value, "true", StringComparison.OrdinalIgnoreCase)) { ticket.Properties.Dictionary[OpenIdConnectConstants.Properties.Confidential] = "true"; } // Ensure the received ticket is an identity token. if (!ticket.IsIdentityToken()) { Options.Logger.LogDebug("The received token was not an identity token: {Token}.", token); return(null); } return(ticket); }
private async Task <UserInformationReceivedContext> RunUserInformationReceivedEventAsync(AuthenticationTicket ticket, OpenIdConnectMessage message, JObject user) { Logger.UserInformationReceived(user.ToString()); var userInformationReceivedContext = new UserInformationReceivedContext(Context, Options) { Ticket = ticket, ProtocolMessage = message, User = user, }; await Options.Events.UserInformationReceived(userInformationReceivedContext); if (userInformationReceivedContext.HandledResponse) { Logger.UserInformationReceivedHandledResponse(); } else if (userInformationReceivedContext.Skipped) { Logger.UserInformationReceivedSkipped(); } return(userInformationReceivedContext); }
/// <summary> /// Invoked to process incoming OpenIdConnect messages. /// </summary> /// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns> protected override async Task <AuthenticateResult> HandleRemoteAuthenticateAsync() { Logger.EnteringOpenIdAuthenticationHandlerHandleRemoteAuthenticateAsync(GetType().FullName); OpenIdConnectMessage authorizationResponse = null; if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { authorizationResponse = new OpenIdConnectMessage(Request.Query.Select(pair => new KeyValuePair <string, string[]>(pair.Key, pair.Value))); // response_mode=query (explicit or not) and a response_type containing id_token // or token are not considered as a safe combination and MUST be rejected. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security if (!string.IsNullOrEmpty(authorizationResponse.IdToken) || !string.IsNullOrEmpty(authorizationResponse.AccessToken)) { if (Options.SkipUnrecognizedRequests) { // Not for us? return(AuthenticateResult.Skip()); } return(AuthenticateResult.Fail("An OpenID Connect response cannot contain an " + "identity token or an access token when using response_mode=query")); } } // assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small. else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(Request.ContentType) // May have media/type; charset=utf-8, allow partial match. && Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) && Request.Body.CanRead) { var form = await Request.ReadFormAsync(); authorizationResponse = new OpenIdConnectMessage(form.Select(pair => new KeyValuePair <string, string[]>(pair.Key, pair.Value))); } if (authorizationResponse == null) { if (Options.SkipUnrecognizedRequests) { // Not for us? return(AuthenticateResult.Skip()); } return(AuthenticateResult.Fail("No message.")); } AuthenticateResult result; try { AuthenticationProperties properties = null; if (!string.IsNullOrEmpty(authorizationResponse.State)) { properties = Options.StateDataFormat.Unprotect(authorizationResponse.State); } var messageReceivedContext = await RunMessageReceivedEventAsync(authorizationResponse, properties); if (messageReceivedContext.CheckEventResult(out result)) { return(result); } authorizationResponse = messageReceivedContext.ProtocolMessage; properties = messageReceivedContext.Properties; if (properties == null) { // Fail if state is missing, it's required for the correlation id. if (string.IsNullOrEmpty(authorizationResponse.State)) { // This wasn't a valid OIDC message, it may not have been intended for us. Logger.NullOrEmptyAuthorizationResponseState(); if (Options.SkipUnrecognizedRequests) { return(AuthenticateResult.Skip()); } return(AuthenticateResult.Fail(Resources.MessageStateIsNullOrEmpty)); } // if state exists and we failed to 'unprotect' this is not a message we should process. properties = Options.StateDataFormat.Unprotect(authorizationResponse.State); } if (properties == null) { Logger.UnableToReadAuthorizationResponseState(); if (Options.SkipUnrecognizedRequests) { // Not for us? return(AuthenticateResult.Skip()); } return(AuthenticateResult.Fail(Resources.MessageStateIsInvalid)); } string userstate = null; properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate); authorizationResponse.State = userstate; if (!ValidateCorrelationId(properties)) { return(AuthenticateResult.Fail("Correlation failed.")); } // if any of the error fields are set, throw error null if (!string.IsNullOrEmpty(authorizationResponse.Error)) { Logger.AuthorizationResponseError( authorizationResponse.Error, authorizationResponse.ErrorDescription ?? "ErrorDecription null", authorizationResponse.ErrorUri ?? "ErrorUri null"); return(AuthenticateResult.Fail(new OpenIdConnectProtocolException( string.Format(CultureInfo.InvariantCulture, Resources.MessageContainsError, authorizationResponse.Error, authorizationResponse.ErrorDescription ?? "ErrorDecription null", authorizationResponse.ErrorUri ?? "ErrorUri null")))); } if (_configuration == null && Options.ConfigurationManager != null) { Logger.UpdatingConfiguration(); _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); } PopulateSessionProperties(authorizationResponse, properties); AuthenticationTicket ticket = null; JwtSecurityToken jwt = null; string nonce = null; var validationParameters = Options.TokenValidationParameters.Clone(); // Hybrid or Implicit flow if (!string.IsNullOrEmpty(authorizationResponse.IdToken)) { Logger.ReceivedIdToken(); ticket = ValidateToken(authorizationResponse.IdToken, properties, validationParameters, out jwt); nonce = jwt?.Payload.Nonce; if (!string.IsNullOrEmpty(nonce)) { nonce = ReadNonceCookie(nonce); } var tokenValidatedContext = await RunTokenValidatedEventAsync(authorizationResponse, null, properties, ticket, jwt, nonce); if (tokenValidatedContext.CheckEventResult(out result)) { return(result); } authorizationResponse = tokenValidatedContext.ProtocolMessage; properties = tokenValidatedContext.Properties; ticket = tokenValidatedContext.Ticket; jwt = tokenValidatedContext.SecurityToken; nonce = tokenValidatedContext.Nonce; } Options.ProtocolValidator.ValidateAuthenticationResponse(new OpenIdConnectProtocolValidationContext() { ClientId = Options.ClientId, ProtocolMessage = authorizationResponse, ValidatedIdToken = jwt, Nonce = nonce }); OpenIdConnectMessage tokenEndpointResponse = null; // Authorization Code or Hybrid flow if (!string.IsNullOrEmpty(authorizationResponse.Code)) { var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(authorizationResponse, properties, ticket, jwt); if (authorizationCodeReceivedContext.CheckEventResult(out result)) { return(result); } authorizationResponse = authorizationCodeReceivedContext.ProtocolMessage; properties = authorizationCodeReceivedContext.Properties; var tokenEndpointRequest = authorizationCodeReceivedContext.TokenEndpointRequest; // If the developer redeemed the code themselves... tokenEndpointResponse = authorizationCodeReceivedContext.TokenEndpointResponse; ticket = authorizationCodeReceivedContext.Ticket; jwt = authorizationCodeReceivedContext.JwtSecurityToken; if (!authorizationCodeReceivedContext.HandledCodeRedemption) { tokenEndpointResponse = await RedeemAuthorizationCodeAsync(tokenEndpointRequest); } var tokenResponseReceivedContext = await RunTokenResponseReceivedEventAsync(authorizationResponse, tokenEndpointResponse, properties); if (tokenResponseReceivedContext.CheckEventResult(out result)) { return(result); } authorizationResponse = tokenResponseReceivedContext.ProtocolMessage; tokenEndpointResponse = tokenResponseReceivedContext.TokenEndpointResponse; // We only have to process the IdToken if we didn't already get one in the AuthorizationResponse if (ticket == null) { // no need to validate signature when token is received using "code flow" as per spec // [http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation]. validationParameters.RequireSignedTokens = false; ticket = ValidateToken(tokenEndpointResponse.IdToken, properties, validationParameters, out jwt); nonce = jwt?.Payload.Nonce; if (!string.IsNullOrEmpty(nonce)) { nonce = ReadNonceCookie(nonce); } var tokenValidatedContext = await RunTokenValidatedEventAsync(authorizationResponse, tokenEndpointResponse, properties, ticket, jwt, nonce); if (tokenValidatedContext.CheckEventResult(out result)) { return(result); } authorizationResponse = tokenValidatedContext.ProtocolMessage; tokenEndpointResponse = tokenValidatedContext.TokenEndpointResponse; properties = tokenValidatedContext.Properties; ticket = tokenValidatedContext.Ticket; jwt = tokenValidatedContext.SecurityToken; nonce = tokenValidatedContext.Nonce; } // Validate the token response if it wasn't provided manually if (!authorizationCodeReceivedContext.HandledCodeRedemption) { Options.ProtocolValidator.ValidateTokenResponse(new OpenIdConnectProtocolValidationContext() { ClientId = Options.ClientId, ProtocolMessage = tokenEndpointResponse, ValidatedIdToken = jwt, Nonce = nonce }); } } if (Options.SaveTokens) { SaveTokens(ticket.Properties, tokenEndpointResponse ?? authorizationResponse); } if (Options.GetClaimsFromUserInfoEndpoint) { return(await GetUserInformationAsync(tokenEndpointResponse ?? authorizationResponse, jwt, ticket)); } return(AuthenticateResult.Success(ticket)); } catch (Exception exception) { Logger.ExceptionProcessingMessage(exception); // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event. if (Options.RefreshOnIssuerKeyNotFound && exception.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException))) { if (Options.ConfigurationManager != null) { Logger.ConfigurationManagerRequestRefreshCalled(); Options.ConfigurationManager.RequestRefresh(); } } var authenticationFailedContext = await RunAuthenticationFailedEventAsync(authorizationResponse, exception); if (authenticationFailedContext.CheckEventResult(out result)) { return(result); } return(AuthenticateResult.Fail(exception)); } }
private async Task <AuthorizationCodeReceivedContext> RunAuthorizationCodeReceivedEventAsync(OpenIdConnectMessage authorizationResponse, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt) { Logger.AuthorizationCodeReceived(); var tokenEndpointRequest = new OpenIdConnectMessage() { ClientId = Options.ClientId, ClientSecret = Options.ClientSecret, Code = authorizationResponse.Code, GrantType = OpenIdConnectGrantTypes.AuthorizationCode, RedirectUri = properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey] }; var authorizationCodeReceivedContext = new AuthorizationCodeReceivedContext(Context, Options) { ProtocolMessage = authorizationResponse, Properties = properties, TokenEndpointRequest = tokenEndpointRequest, Ticket = ticket, JwtSecurityToken = jwt, Backchannel = Backchannel, }; await Options.Events.AuthorizationCodeReceived(authorizationCodeReceivedContext); if (authorizationCodeReceivedContext.HandledResponse) { Logger.AuthorizationCodeReceivedContextHandledResponse(); } else if (authorizationCodeReceivedContext.Skipped) { Logger.AuthorizationCodeReceivedContextSkipped(); } return(authorizationCodeReceivedContext); }
private async Task <AuthenticationTicket> DeserializeRefreshTokenAsync(string token, OpenIdConnectMessage request) { var notification = new DeserializeRefreshTokenContext(Context, Options, request, token) { DataFormat = Options.RefreshTokenFormat }; await Options.Provider.DeserializeRefreshToken(notification); if (notification.HandledResponse || notification.Ticket != null) { return(notification.Ticket); } else if (notification.Skipped) { return(null); } var ticket = notification.DataFormat?.Unprotect(token); if (ticket == null) { return(null); } // Ensure the received ticket is an identity token. if (!ticket.IsRefreshToken()) { Options.Logger.LogDebug("The received token was not a refresh token: {Token}.", token); return(null); } return(ticket); }
/// <summary> /// Tells the middleware to skip the code redemption process. The developer may have redeemed the code themselves, or /// decided that the redemption was not required. If tokens were retrieved that are needed for further processing then /// call one of the overloads that allows providing tokens. An IdToken is required if one had not been previously received /// in the authorization response. An access token can optionally be provided for the middleware to contact the /// user-info endpoint. Calling this is the same as setting TokenEndpointResponse. /// </summary> public void HandleCodeRedemption(string accessToken, string idToken) { TokenEndpointResponse = new OpenIdConnectMessage() { AccessToken = accessToken, IdToken = idToken }; }
public RemoteSignOutContext(HttpContext context, AuthenticationScheme scheme, OpenIdConnectOptions options, OpenIdConnectMessage message) : base(context, scheme, options, new AuthenticationProperties()) => ProtocolMessage = message;
private async Task <TokenValidatedContext> RunTokenValidatedEventAsync(OpenIdConnectMessage authorizationResponse, OpenIdConnectMessage tokenEndpointResponse, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt, string nonce) { var tokenValidatedContext = new TokenValidatedContext(Context, Options) { ProtocolMessage = authorizationResponse, TokenEndpointResponse = tokenEndpointResponse, Properties = properties, Ticket = ticket, SecurityToken = jwt, Nonce = nonce, }; await Options.Events.TokenValidated(tokenValidatedContext); if (tokenValidatedContext.HandledResponse) { Logger.TokenValidatedHandledResponse(); } else if (tokenValidatedContext.Skipped) { Logger.TokenValidatedSkipped(); } return(tokenValidatedContext); }
private async Task <bool> InvokeCryptographyEndpointAsync() { // Metadata requests must be made via GET. // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The discovery request was rejected because an invalid " + "HTTP method was used: {Method}.", Request.Method); return(await SendCryptographyResponseAsync(null, new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "Invalid HTTP method: make sure to use GET." })); } var request = new OpenIdConnectMessage(Request.Query.ToDictionary()); var context = new ValidateCryptographyRequestContext(Context, Options); await Options.Provider.ValidateCryptographyRequest(context); if (!context.IsValidated) { Logger.LogInformation("The discovery request was rejected by application code."); return(await SendCryptographyResponseAsync(request, new OpenIdConnectMessage { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } var notification = new HandleCryptographyRequestContext(Context, Options, request); foreach (var credentials in Options.SigningCredentials) { // Ignore the key if it's not supported. if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256Signature) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha384Signature) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha512Signature)) { Logger.LogInformation("An unsupported signing key was ignored and excluded " + "from the key set: {Type}. Only asymmetric security keys " + "supporting RS256, RS384 or RS512 can be exposed " + "via the JWKS endpoint.", credentials.Key.GetType().Name); continue; } // Determine whether the security key is a RSA key embedded in a X.509 certificate. var x509SecurityKey = credentials.Key as X509SecurityKey; if (x509SecurityKey != null) { // Create a new JSON Web Key exposing the // certificate instead of its public RSA key. notification.Keys.Add(new JsonWebKey { Use = JsonWebKeyUseNames.Sig, Kty = JsonWebAlgorithmsKeyTypes.RSA, // Resolve the JWA identifier from the algorithm specified in the credentials. Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm), // Use the key identifier specified // in the signing credentials. Kid = credentials.Kid, // x5t must be base64url-encoded. // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.8 X5t = Base64UrlEncoder.Encode(x509SecurityKey.Certificate.GetCertHash()), // Unlike E or N, the certificates contained in x5c // must be base64-encoded and not base64url-encoded. // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.7 X5c = { Convert.ToBase64String(x509SecurityKey.Certificate.RawData) } }); } var rsaSecurityKey = credentials.Key as RsaSecurityKey; if (rsaSecurityKey != null) { // Export the RSA public key. notification.Keys.Add(new JsonWebKey { Use = JsonWebKeyUseNames.Sig, Kty = JsonWebAlgorithmsKeyTypes.RSA, // Resolve the JWA identifier from the algorithm specified in the credentials. Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm), // Use the key identifier specified // in the signing credentials. Kid = credentials.Kid, // Both E and N must be base64url-encoded. // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#appendix-A.1 E = Base64UrlEncoder.Encode(rsaSecurityKey.Parameters.Exponent), N = Base64UrlEncoder.Encode(rsaSecurityKey.Parameters.Modulus) }); } } await Options.Provider.HandleCryptographyRequest(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } var response = new JObject(); var keys = new JArray(); foreach (var key in notification.Keys) { var item = new JObject(); // Ensure a key type has been provided. // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.1 if (string.IsNullOrEmpty(key.Kty)) { Logger.LogError("A JSON Web Key was excluded from the key set because " + "it didn't contain the mandatory 'kid' parameter."); continue; } // Create a dictionary associating the // JsonWebKey components with their values. var parameters = new Dictionary <string, string> { [JsonWebKeyParameterNames.Kid] = key.Kid, [JsonWebKeyParameterNames.Use] = key.Use, [JsonWebKeyParameterNames.Kty] = key.Kty, [JsonWebKeyParameterNames.Alg] = key.Alg, [JsonWebKeyParameterNames.X5t] = key.X5t, [JsonWebKeyParameterNames.X5u] = key.X5u, [JsonWebKeyParameterNames.E] = key.E, [JsonWebKeyParameterNames.N] = key.N }; foreach (var parameter in parameters) { if (!string.IsNullOrEmpty(parameter.Value)) { item.Add(parameter.Key, parameter.Value); } } if (key.KeyOps.Any()) { item.Add(JsonWebKeyParameterNames.KeyOps, JArray.FromObject(key.KeyOps)); } if (key.X5c.Any()) { item.Add(JsonWebKeyParameterNames.X5c, JArray.FromObject(key.X5c)); } keys.Add(item); } response.Add(JsonWebKeyParameterNames.Keys, keys); return(await SendCryptographyResponseAsync(request, response)); }
/// <summary> /// Goes to UserInfo endpoint to retrieve additional claims and add any unique claims to the given identity. /// </summary> /// <param name="message">message that is being processed</param> /// <param name="jwt">The <see cref="JwtSecurityToken"/>.</param> /// <param name="ticket">authentication ticket with claims principal and identities</param> /// <returns>Authentication ticket with identity with additional claims, if any.</returns> protected virtual async Task <AuthenticateResult> GetUserInformationAsync(OpenIdConnectMessage message, JwtSecurityToken jwt, AuthenticationTicket ticket) { var userInfoEndpoint = _configuration?.UserInfoEndpoint; if (string.IsNullOrEmpty(userInfoEndpoint)) { Logger.UserInfoEndpointNotSet(); return(AuthenticateResult.Success(ticket)); } if (string.IsNullOrEmpty(message.AccessToken)) { Logger.AccessTokenNotAvailable(); return(AuthenticateResult.Success(ticket)); } Logger.RetrievingClaims(); var requestMessage = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", message.AccessToken); var responseMessage = await Backchannel.SendAsync(requestMessage); responseMessage.EnsureSuccessStatusCode(); var userInfoResponse = await responseMessage.Content.ReadAsStringAsync(); JObject user; var contentType = responseMessage.Content.Headers.ContentType; if (contentType.MediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase)) { user = JObject.Parse(userInfoResponse); } else if (contentType.MediaType.Equals("application/jwt", StringComparison.OrdinalIgnoreCase)) { var userInfoEndpointJwt = new JwtSecurityToken(userInfoResponse); user = JObject.FromObject(userInfoEndpointJwt.Payload); } else { return(AuthenticateResult.Fail("Unknown response type: " + contentType.MediaType)); } var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(ticket, message, user); AuthenticateResult result; if (userInformationReceivedContext.CheckEventResult(out result)) { return(result); } ticket = userInformationReceivedContext.Ticket; user = userInformationReceivedContext.User; Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext() { UserInfoEndpointResponse = userInfoResponse, ValidatedIdToken = jwt, }); var identity = (ClaimsIdentity)ticket.Principal.Identity; foreach (var claim in identity.Claims) { // If this claimType is mapped by the JwtSeurityTokenHandler, then this property will be set var shortClaimTypeName = claim.Properties.ContainsKey(JwtSecurityTokenHandler.ShortClaimTypeProperty) ? claim.Properties[JwtSecurityTokenHandler.ShortClaimTypeProperty] : string.Empty; // checking if claim in the identity (generated from id_token) has the same type as a claim retrieved from userinfo endpoint JToken value; var isClaimIncluded = user.TryGetValue(claim.Type, out value) || user.TryGetValue(shortClaimTypeName, out value); // if a same claim exists (matching both type and value) both in id_token identity and userinfo response, remove the json entry from the userinfo response if (isClaimIncluded && claim.Value.Equals(value.ToString(), StringComparison.Ordinal)) { if (!user.Remove(claim.Type)) { user.Remove(shortClaimTypeName); } } } // adding remaining unique claims from userinfo endpoint to the identity foreach (var pair in user) { JToken value; var claimValue = user.TryGetValue(pair.Key, out value) ? value.ToString() : null; identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String, jwt.Issuer)); } return(AuthenticateResult.Success(ticket)); }
protected override async Task ApplyResponseChallengeAsync() { if (Response.StatusCode == 401) { AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); if (challenge == null) { return; } // order for redirect_uri // 1. challenge.Properties.RedirectUri // 2. CurrentUri AuthenticationProperties properties = challenge.Properties; if (string.IsNullOrEmpty(properties.RedirectUri)) { properties.RedirectUri = this.CurrentUri; } // this value will be passed to the AuthorizationCodeReceivedNotification if (!string.IsNullOrWhiteSpace(Options.RedirectUri)) { properties.Dictionary.Add(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey, Options.RedirectUri); } if (_configuration == null) { _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.Request.CallCancelled); } OpenIdConnectMessage openIdConnectMessage = new OpenIdConnectMessage { ClientId = Options.ClientId, IssuerAddress = _configuration.AuthorizationEndpoint ?? string.Empty, RedirectUri = Options.RedirectUri, RequestType = OpenIdConnectRequestType.AuthenticationRequest, Resource = Options.Resource, ResponseMode = OpenIdConnectResponseModes.FormPost, ResponseType = Options.ResponseType, Scope = Options.Scope, State = "OpenIdConnect.AuthenticationProperties" + Uri.EscapeDataString(Options.StateDataFormat.Protect(properties)), }; if (Options.ProtocolValidator.RequireNonce) { AddNonceToMessage(openIdConnectMessage); } var notification = new RedirectToIdentityProviderNotification <OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options) { ProtocolMessage = openIdConnectMessage }; await Options.Notifications.RedirectToIdentityProvider(notification); if (!notification.HandledResponse) { string redirectUri = notification.ProtocolMessage.CreateAuthenticationRequestUrl(); if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute)) { //_logger.WriteWarning("The authenticate redirect URI is malformed: " + redirectUri); } Response.Redirect(redirectUri); } } return; }
private async Task <AuthenticationTicket> DeserializeAuthorizationCodeAsync(string code, OpenIdConnectMessage request) { var notification = new DeserializeAuthorizationCodeContext(Context, Options, request, code) { DataFormat = Options.AuthorizationCodeFormat }; await Options.Provider.DeserializeAuthorizationCode(notification); if (notification.HandledResponse || notification.Ticket != null) { return(notification.Ticket); } else if (notification.Skipped) { return(null); } var ticket = notification.DataFormat?.Unprotect(code); if (ticket == null) { return(null); } // Ensure the received ticket is an authorization code. if (!ticket.IsAuthorizationCode()) { Options.Logger.LogDebug("The received token was not an authorization code: {Code}.", code); return(null); } return(ticket); }
private AuthenticationProperties GetAuthenticationPropertiesFromProtocolMessage(OpenIdConnectMessage message, OpenIdConnectAuthenticationOptions options) { var authenticationPropertiesEncodedString = message.State.Split('='); return(options.StateDataFormat.Unprotect(authenticationPropertiesEncodedString[1])); }
private async Task <string> SerializeAccessTokenAsync( ClaimsIdentity identity, AuthenticationProperties properties, OpenIdConnectMessage request, OpenIdConnectMessage response) { // properties.IssuedUtc and properties.ExpiresUtc // should always be preferred when explicitly set. if (properties.IssuedUtc == null) { properties.IssuedUtc = Options.SystemClock.UtcNow; } if (properties.ExpiresUtc == null) { properties.ExpiresUtc = properties.IssuedUtc + Options.AccessTokenLifetime; } // Create a new identity containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). identity = identity.Clone(claim => { // Never exclude ClaimTypes.NameIdentifier. if (string.Equals(claim.Type, ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase)) { return(true); } // Claims whose destination is not explicitly referenced or doesn't // contain "access_token" are not included in the access token. return(claim.HasDestination(OpenIdConnectConstants.Destinations.AccessToken)); }); // Create a new ticket containing the updated properties and the filtered identity. var ticket = new AuthenticationTicket(identity, properties); ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken); ticket.SetAudiences(ticket.GetResources()); // Associate a random identifier with the access token. ticket.SetTicketId(Guid.NewGuid().ToString()); // By default, add the client_id to the list of the // presenters allowed to use the access token. if (!string.IsNullOrEmpty(request.ClientId)) { ticket.SetPresenters(request.ClientId); } var notification = new SerializeAccessTokenContext(Context, Options, request, response, ticket) { DataFormat = Options.AccessTokenFormat, Issuer = Context.GetIssuer(Options), SecurityTokenHandler = Options.AccessTokenHandler, SigningCredentials = Options.SigningCredentials.FirstOrDefault() }; await Options.Provider.SerializeAccessToken(notification); if (notification.HandledResponse || !string.IsNullOrEmpty(notification.AccessToken)) { return(notification.AccessToken); } else if (notification.Skipped) { return(null); } if (!notification.Audiences.Any()) { Options.Logger.LogInformation("No explicit audience was associated with the access token."); } if (notification.SecurityTokenHandler == null) { return(notification.DataFormat?.Protect(ticket)); } if (notification.SigningCredentials == null) { throw new InvalidOperationException("A signing key must be provided."); } // Store the "unique_id" property as a claim. ticket.Identity.AddClaim(notification.SecurityTokenHandler is JwtSecurityTokenHandler ? OpenIdConnectConstants.Claims.JwtId : OpenIdConnectConstants.Claims.TokenId, ticket.GetTicketId()); // Store the "usage" property as a claim. ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Usage, ticket.GetUsage()); // If the ticket is marked as confidential, add a new // "confidential" claim in the security token. if (ticket.IsConfidential()) { ticket.Identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Confidential, "true", ClaimValueTypes.Boolean)); } // Create a new claim per scope item, that will result // in a "scope" array being added in the access token. foreach (var scope in notification.Scopes) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Scope, scope); } var handler = notification.SecurityTokenHandler as JwtSecurityTokenHandler; if (handler != null) { // Note: when used as an access token, a JWT token doesn't have to expose a "sub" claim // but the name identifier claim is used as a substitute when it has been explicitly added. // See https://tools.ietf.org/html/rfc7519#section-4.1.2 var subject = identity.FindFirst(OpenIdConnectConstants.Claims.Subject); if (subject == null) { var identifier = identity.FindFirst(ClaimTypes.NameIdentifier); if (identifier != null) { identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identifier.Value); } } // Remove the ClaimTypes.NameIdentifier claims to avoid getting duplicate claims. // Note: the "sub" claim is automatically mapped by JwtSecurityTokenHandler // to ClaimTypes.NameIdentifier when validating a JWT token. // Note: make sure to call ToArray() to avoid an InvalidOperationException // on old versions of Mono, where FindAll() is implemented using an iterator. foreach (var claim in ticket.Identity.FindAll(ClaimTypes.NameIdentifier).ToArray()) { ticket.Identity.RemoveClaim(claim); } // Store the audiences as claims. foreach (var audience in notification.Audiences) { ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Audience, audience); } // Extract the presenters from the authentication ticket. var presenters = notification.Presenters.ToArray(); switch (presenters.Length) { case 0: break; case 1: identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]); break; default: Options.Logger.LogWarning("Multiple presenters have been associated with the access token " + "but the JWT format only accepts single values."); // Only add the first authorized party. identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]); break; } var token = handler.CreateToken( subject: ticket.Identity, issuer: notification.Issuer, signingCredentials: notification.SigningCredentials, notBefore: ticket.Properties.IssuedUtc.Value.UtcDateTime, expires: ticket.Properties.ExpiresUtc.Value.UtcDateTime); token.Payload[OpenIdConnectConstants.Claims.IssuedAt] = EpochTime.GetIntDate(ticket.Properties.IssuedUtc.Value.UtcDateTime); // Try to extract a key identifier from the signing credentials // and add the "kid" property to the JWT header if applicable. LocalIdKeyIdentifierClause clause = null; if (notification.SigningCredentials.SigningKeyIdentifier.TryFind(out clause)) { token.Header[JwtHeaderParameterNames.Kid] = clause.LocalId; } return(handler.WriteToken(token)); } else { var descriptor = new SecurityTokenDescriptor { Subject = ticket.Identity, AppliesToAddress = notification.Audiences.ElementAtOrDefault(0), TokenIssuerName = notification.Issuer, EncryptingCredentials = notification.EncryptingCredentials, SigningCredentials = notification.SigningCredentials, Lifetime = new Lifetime( notification.Ticket.Properties.IssuedUtc.Value.UtcDateTime, notification.Ticket.Properties.ExpiresUtc.Value.UtcDateTime) }; // When the encrypting credentials use an asymmetric key, replace them by a // EncryptedKeyEncryptingCredentials instance to generate a symmetric key. if (descriptor.EncryptingCredentials?.SecurityKey is AsymmetricSecurityKey) { // Note: EncryptedKeyEncryptingCredentials automatically generates an in-memory key // that will be encrypted using the original credentials and added to the resulting token // if the security token handler fully supports token encryption (e.g SAML or SAML2). descriptor.EncryptingCredentials = new EncryptedKeyEncryptingCredentials( wrappingCredentials: notification.EncryptingCredentials, keySizeInBits: 256, encryptionAlgorithm: SecurityAlgorithms.Aes256Encryption); } var token = notification.SecurityTokenHandler.CreateToken(descriptor); // Note: the security token is manually serialized to prevent // an exception from being thrown if the handler doesn't implement // the SecurityTokenHandler.WriteToken overload returning a string. var builder = new StringBuilder(); using (var writer = XmlWriter.Create(builder, new XmlWriterSettings { Encoding = new UTF8Encoding(false), OmitXmlDeclaration = true })) { notification.SecurityTokenHandler.WriteToken(writer, token); } return(builder.ToString()); } }
public async Task AuthenticateCore(LogLevel logLevel, int[] expectedLogIndexes, Action<OpenIdConnectAuthenticationOptions> action, OpenIdConnectMessage message) { var errors = new List<Tuple<LogEntry, LogEntry>>(); var expectedLogs = LoggingUtilities.PopulateLogEntries(expectedLogIndexes); var handler = new OpenIdConnectAuthenticationHandlerForTestingAuthenticate(); var loggerFactory = new InMemoryLoggerFactory(logLevel); var server = CreateServer(new ConfigureOptions<OpenIdConnectAuthenticationOptions>(action), UrlEncoder.Default, loggerFactory, handler); await server.CreateClient().PostAsync("http://localhost", new FormUrlEncodedContent(message.Parameters)); LoggingUtilities.CheckLogs(loggerFactory.Logger.Logs, expectedLogs, errors); Debug.WriteLine(LoggingUtilities.LoggingErrors(errors)); Assert.True(errors.Count == 0, LoggingUtilities.LoggingErrors(errors)); }
protected override Task<AuthenticationTicket> GetUserInformationAsync(OpenIdConnectMessage message, JwtSecurityToken jwt, AuthenticationTicket ticket) { var claimsIdentity = (ClaimsIdentity)ticket.Principal.Identity; if (claimsIdentity == null) { claimsIdentity = new ClaimsIdentity(); } claimsIdentity.AddClaim(new Claim("test claim", "test value")); return Task.FromResult(new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), ticket.Properties, ticket.AuthenticationScheme)); }