/// <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();
 }
Example #4
0
 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);
        }
Example #22
0
        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);
        }
Example #23
0
        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));
        }
Example #24
0
        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));
        }
Example #30
0
        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 };
 }
Example #43
0
 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);
        }
Example #49
0
        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));
 }