public async Task<IActionResult> Accept(OpenIdConnectRequest request) { // Retrieve the profile of the logged in user. var user = await _userManager.GetUserAsync(User); if (user == null) { return View(); /* return View("Error", new ErrorViewModel { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "An internal error has occurred" }); */ } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); }
public async Task<IActionResult> Authorize(OpenIdConnectRequest request) { // Retrieve the application details from the database. var application = await _applicationManager.FindByClientIdAsync(request.ClientId); if (application == null) { return View(); /*return View("Error", new ErrorViewModel { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "Details concerning the calling client application cannot be found in the database" });*/ } // Flow the request_id to allow OpenIddict to restore // the original authorization request from the cache. return View(new AuthorizeViewModel { ApplicationName = application.DisplayName, RequestId = request.RequestId, Scope = request.Scope }); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { if (request.IsPasswordGrantType()) { var user = await _userManager.FindByEmailAsync(request.Username) ?? await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Please check that your email and password is correct" })); } // Ensure the user is enabled. if (!user.IsEnabled) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user account is disabled" })); } // Validate the username/password parameters and ensure the account is not locked out. var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, true); // Ensure the user is not already locked out. if (result.IsLockedOut) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user account has been suspended" })); } // Reject the token request if two-factor authentication has been enabled by the user. if (result.RequiresTwoFactor) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Invalid login procedure" })); } // Ensure the user is allowed to sign in. if (result.IsNotAllowed) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in" })); } if (!result.Succeeded) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Please check that your email and password is correct" })); } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the refresh token. var info = await HttpContext.AuthenticateAsync(OpenIddictServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the refresh token. // Note: if you want to automatically invalidate the refresh token // when the user password/roles change, use the following line instead: // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The refresh token is no longer valid" })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in" })); } // Create a new authentication ticket, but reuse the properties stored // in the refresh token, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported" })); }
public IActionResult Authorize(OpenIdConnectRequest request, string remoteError = null) { this.ViewData["State"] = this.dataProtectionProvider.ProtectQueryString(this.Request.Query); return(this.View(nameof(this.Login))); }
public async Task <IActionResult> Post(OpenIdConnectRequest req) { Debug.Assert(req.IsTokenRequest(), "The OpenIddict binder for ASP.NET Core MVC is not registered. " + "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); if (req.IsPasswordGrantType()) { var user = await _userManager.FindByNameAsync(req.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Invalid credentials" })); } if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "User is not allowed to sign in" })); } //Check user account is not locked if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "User is not allowed to sign in" })); } if (!await _userManager.CheckPasswordAsync(user, req.Password)) { if (_userManager.SupportsUserLockout) { await _userManager.AccessFailedAsync(user); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Invalid credentials" })); } if (_userManager.SupportsUserLockout) { await _userManager.ResetAccessFailedCountAsync(user); } // Create a new authentication ticket. var ticket = await _CreateTicketAsync(req, user); if (req.Resource != null) { ticket.SetAudiences(new string[] { req.Resource }); } return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (req.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the refresh token. var info = await HttpContext.Authentication.GetAuthenticateInfoAsync(OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the refresh token. var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The refresh token is no longer valid." })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } // Create a new authentication ticket, but reuse the properties stored // in the refresh token, including the scopes originally granted. var ticket = await _CreateTicketAsync(req, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "Grant type not supported." })); } }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { Debug.Assert(request.IsTokenRequest(), "The OpenIddict binder for ASP.NET Core MVC is not registered. " + "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); if (request.IsPasswordGrantType()) { var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Validate the username/password parameters and ensure the account is not locked out. var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure : true); if (!result.Succeeded) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); var claims = await _userClaimsPrincipalFactory.CreateAsync(user); var limitedPermissions = _authorizationOptions.LimitedCookiePermissions?.Split(PlatformConstants.Security.Claims.PermissionClaimTypeDelimiter, StringSplitOptions.RemoveEmptyEntries) ?? new string[0]; if (!user.Roles.Select(r => r.Name).Contains(PlatformConstants.Security.Roles.Administrator)) { limitedPermissions = claims .Claims .Where(c => c.Type == PlatformConstants.Security.Claims.PermissionClaimType) .Select(c => c.Value) .Intersect(limitedPermissions, StringComparer.OrdinalIgnoreCase) .ToArray(); } if (limitedPermissions.Any()) { // Set limited permissions and authenticate user with combined mode Cookies + Bearer. // // LimitedPermissions claims that will be granted to the user by cookies when bearer token authentication is enabled. // This can help to authorize the user for direct(non - AJAX) GET requests to the VC platform API and / or to use some 3rd - party web applications for the VC platform(like Hangfire dashboard). // // If the user identity has claim named "limited_permissions", this attribute should authorize only permissions listed in that claim. Any permissions that are required by this attribute but // not listed in the claim should cause this method to return false. However, if permission limits of user identity are not defined ("limited_permissions" claim is missing), // then no limitations should be applied to the permissions. ((ClaimsIdentity)claims.Identity).AddClaim(new Claim(PlatformConstants.Security.Claims.LimitedPermissionsClaimType, string.Join(PlatformConstants.Security.Claims.PermissionClaimTypeDelimiter, limitedPermissions))); await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, claims); } return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the authorization code/refresh token. var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code/refresh token. // Note: if you want to automatically invalidate the authorization code/refresh token // when the user password/roles change, use the following line instead: // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The token is no longer valid." })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } // Create a new authentication ticket, but reuse the properties stored in the // authorization code/refresh token, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsClientCredentialsGrantType()) { // Note: the client credentials are automatically validated by OpenIddict: // if client_id or client_secret are invalid, this action won't be invoked. var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted); if (application == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidClient, ErrorDescription = "The client application was not found in the database." })); } // Create a new authentication ticket. var ticket = CreateTicket(request, application); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
private async Task <AuthenticationTicket> CreateTicketAsync( OpenIdConnectRequest request, IUser user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); var identity = (ClaimsIdentity)principal.Identity; // Note: while ASP.NET Core Identity uses the legacy WS-Federation claims (exposed by the ClaimTypes class), // OpenIddict uses the newer JWT claims defined by the OpenID Connect specification. To ensure the mandatory // subject claim is correctly populated (and avoid an InvalidOperationException), it's manually added here. if (string.IsNullOrEmpty(principal.FindFirstValue(OpenIdConnectConstants.Claims.Subject))) { identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Subject, await _userManager.GetUserIdAsync(user))); } // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIdConnectServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType()) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes())); ticket.SetResources(await GetResourcesAsync(request)); } // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in ticket.Principal.Claims) { // Never include the security stamp in the access and identity tokens, as it's a secret value. if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } var destinations = new List <string> { OpenIdConnectConstants.Destinations.AccessToken }; // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. // The other claims will only be added to the access_token, which is encrypted when using the default format. if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) || (claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) || (claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles))) { destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken); } claim.SetDestinations(destinations); } return(ticket); }
public async Task <IActionResult> TokenExchange(OpenIdConnectRequest request) { // Checks if the incoming request is a OpenIdConnect password grant request if (!request.IsPasswordGrantType()) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); } // Checks if the user exists in the database var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username or password is invalid." })); } // Ensure the user is allowed to sign in if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in." })); } // Ensure the user is not already locked out if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username or password is invalid." })); } // Ensure the password is valid if (!await _userManager.CheckPasswordAsync(user, request.Password)) { if (_userManager.SupportsUserLockout) { await _userManager.AccessFailedAsync(user); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username or password is invalid." })); } // Reset the lockout count if (_userManager.SupportsUserLockout) { await _userManager.ResetAccessFailedCountAsync(user); } // Look up the user's roles (if any) var roles = new string[0]; if (_userManager.SupportsUserRole) { roles = (await _userManager.GetRolesAsync(user)).ToArray(); } // Create a new authentication ticket w/ the user identity var ticket = await CreateTicketAsync(request, user, roles); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); }
/// <summary> /// Sends a generic OpenID Connect request to the given endpoint and /// converts the returned response to an OpenID Connect response. /// </summary> /// <param name="method">The HTTP method used to send the OpenID Connect request.</param> /// <param name="uri">The endpoint to which the request is sent.</param> /// <param name="request">The OpenID Connect request to send.</param> /// <returns>The OpenID Connect response returned by the server.</returns> public virtual async Task <OpenIdConnectResponse> SendAsync( [NotNull] HttpMethod method, [NotNull] Uri uri, [NotNull] OpenIdConnectRequest request) { if (method == null) { throw new ArgumentNullException(nameof(method)); } if (uri == null) { throw new ArgumentNullException(nameof(uri)); } if (request == null) { throw new ArgumentNullException(nameof(request)); } if (HttpClient.BaseAddress == null && !uri.IsAbsoluteUri) { throw new ArgumentException("The address cannot be a relative URI when no base address " + "is associated with the HTTP client.", nameof(uri)); } var parameters = new Dictionary <string, string>(); foreach (var parameter in request.GetParameters()) { var value = (string)parameter.Value; if (string.IsNullOrEmpty(value)) { continue; } parameters.Add(parameter.Key, value); } if (method == HttpMethod.Get && parameters.Count != 0) { var builder = new StringBuilder(); foreach (var parameter in parameters) { if (builder.Length != 0) { builder.Append('&'); } builder.Append(UrlEncoder.Default.Encode(parameter.Key)); builder.Append('='); builder.Append(UrlEncoder.Default.Encode(parameter.Value)); } if (!uri.IsAbsoluteUri) { uri = new Uri(HttpClient.BaseAddress, uri); } uri = new UriBuilder(uri) { Query = builder.ToString() }.Uri; } var message = new HttpRequestMessage(method, uri); if (method != HttpMethod.Get) { message.Content = new FormUrlEncodedContent(parameters); } var response = await HttpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead); if (response.Headers.Location != null) { var payload = response.Headers.Location.Fragment; if (string.IsNullOrEmpty(payload)) { payload = response.Headers.Location.Query; } if (string.IsNullOrEmpty(payload)) { return(new OpenIdConnectResponse()); } var result = new OpenIdConnectResponse(); using (var tokenizer = new StringTokenizer(payload, OpenIdConnectConstants.Separators.Ampersand).GetEnumerator()) { while (tokenizer.MoveNext()) { var parameter = tokenizer.Current; if (parameter.Length == 0) { continue; } // Always skip the first char (# or ?). if (parameter.Offset == 0) { parameter = parameter.Subsegment(1, parameter.Length - 1); } var index = parameter.IndexOf('='); if (index == -1) { continue; } var name = parameter.Substring(0, index); if (string.IsNullOrEmpty(name)) { continue; } var value = parameter.Substring(index + 1, parameter.Length - (index + 1)); if (string.IsNullOrEmpty(value)) { continue; } result.AddParameter( Uri.UnescapeDataString(name.Replace('+', ' ')), Uri.UnescapeDataString(value.Replace('+', ' '))); } } return(result); } else if (string.Equals(response.Content?.Headers?.ContentType?.MediaType, "application/json", StringComparison.OrdinalIgnoreCase)) { using (var stream = await response.Content.ReadAsStreamAsync()) using (var reader = new JsonTextReader(new StreamReader(stream))) { var serializer = JsonSerializer.CreateDefault(); return(serializer.Deserialize <OpenIdConnectResponse>(reader)); } } else if (string.Equals(response.Content?.Headers?.ContentType?.MediaType, "text/html", StringComparison.OrdinalIgnoreCase)) { using (var stream = await response.Content.ReadAsStreamAsync()) { var result = new OpenIdConnectResponse(); var document = await new HtmlParser().ParseAsync(stream); foreach (var element in document.Body.GetElementsByTagName("input")) { var name = element.GetAttribute("name"); if (string.IsNullOrEmpty(name)) { continue; } var value = element.GetAttribute("value"); if (string.IsNullOrEmpty(value)) { continue; } result.AddParameter(name, value); } return(result); } } else if (string.Equals(response.Content?.Headers?.ContentType?.MediaType, "text/plain", StringComparison.OrdinalIgnoreCase)) { using (var stream = await response.Content.ReadAsStreamAsync()) using (var reader = new StreamReader(stream)) { var result = new OpenIdConnectResponse(); for (var line = await reader.ReadLineAsync(); line != null; line = await reader.ReadLineAsync()) { var index = line.IndexOf(':'); if (index == -1) { continue; } result.AddParameter(line.Substring(0, index), line.Substring(index + 1)); } return(result); } } throw new InvalidOperationException("The server returned an unexpected response."); }
/// <summary> /// Sends a generic OpenID Connect request to the given endpoint using POST /// and converts the returned response to an OpenID Connect response. /// </summary> /// <param name="uri">The endpoint to which the request is sent.</param> /// <param name="request">The OpenID Connect request to send.</param> /// <returns>The OpenID Connect response returned by the server.</returns> public Task <OpenIdConnectResponse> PostAsync( [NotNull] Uri uri, [NotNull] OpenIdConnectRequest request) { return(SendAsync(HttpMethod.Post, uri, request)); }
private async Task <bool> InvokeTokenEndpointAsync() { if (!string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The token request was rejected because an invalid " + "HTTP method was specified: {Method}.", Request.Method); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified HTTP method is not valid." })); } // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrEmpty(Request.ContentType)) { Logger.LogError("The token request was rejected because the " + "mandatory 'Content-Type' header was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'Content-Type' header must be specified." })); } // May have media/type; charset=utf-8, allow partial match. if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The token request was rejected because an invalid 'Content-Type' " + "header was specified: {ContentType}.", Request.ContentType); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'Content-Type' header is not valid." })); } var request = new OpenIdConnectRequest(await Request.ReadFormAsync()); // Note: set the message type before invoking the ExtractTokenRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.TokenRequest); // Store the token request in the OWIN context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractTokenRequestContext(Context, Options, request); await Options.Provider.ExtractTokenRequest(@event); if (@event.HandledResponse) { Logger.LogDebug("The token request was handled in user code."); return(true); } else if (@event.Skipped) { Logger.LogDebug("The default token request handling was skipped from user code."); return(false); } else if (@event.IsRejected) { Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } Logger.LogInformation("The token request was successfully extracted " + "from the HTTP request: {Request}.", request); // Reject token requests missing the mandatory grant_type parameter. if (string.IsNullOrEmpty(request.GrantType)) { Logger.LogError("The token request was rejected because the grant type was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'grant_type' parameter is missing.", })); } // Reject grant_type=authorization_code requests if the authorization endpoint is disabled. else if (request.IsAuthorizationCodeGrantType() && !Options.AuthorizationEndpointPath.HasValue) { Logger.LogError("The token request was rejected because the authorization code grant was disabled."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The authorization code grant is not allowed by this authorization server." })); } // Reject grant_type=authorization_code requests missing the authorization code. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 else if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.Code)) { Logger.LogError("The token request was rejected because the authorization code was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'code' parameter is missing." })); } // Reject grant_type=refresh_token requests missing the refresh token. // See https://tools.ietf.org/html/rfc6749#section-6 else if (request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(request.RefreshToken)) { Logger.LogError("The token request was rejected because the refresh token was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'refresh_token' parameter is missing." })); } // Reject grant_type=password requests missing username or password. // See https://tools.ietf.org/html/rfc6749#section-4.3.2 else if (request.IsPasswordGrantType() && (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password))) { Logger.LogError("The token request was rejected because the resource owner credentials were missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'username' and/or 'password' parameters are missing." })); } // Try to resolve the client credentials specified in the 'Authorization' header. // If they cannot be extracted, fallback to the client_id/client_secret parameters. var credentials = Request.Headers.GetClientCredentials(); if (credentials != null) { // Reject requests that use multiple client authentication methods. // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information. if (!string.IsNullOrEmpty(request.ClientSecret)) { Logger.LogError("The token request was rejected because multiple client credentials were specified."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "Multiple client credentials cannot be specified." })); } request.ClientId = credentials?.Key; request.ClientSecret = credentials?.Value; } var context = new ValidateTokenRequestContext(Context, Options, request); await Options.Provider.ValidateTokenRequest(context); // If the validation context was set as fully validated, // mark the OpenID Connect request as confidential. if (context.IsValidated) { request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel, OpenIdConnectConstants.ConfidentialityLevels.Private); } if (context.HandledResponse) { Logger.LogDebug("The token request was handled in user code."); return(true); } else if (context.Skipped) { Logger.LogDebug("The default token request handling was skipped from user code."); return(false); } else if (context.IsRejected) { Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } // Reject grant_type=client_credentials requests if validation was skipped. else if (context.IsSkipped && request.IsClientCredentialsGrantType()) { Logger.LogError("The token request must be fully validated to use the client_credentials grant type."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Client authentication is required when using the client credentials grant." })); } // At this stage, client_id cannot be null for grant_type=authorization_code requests, // as it must either be set in the ValidateTokenRequest notification // by the developer or manually flowed by non-confidential client applications. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(context.ClientId)) { Logger.LogError("The token request was rejected because the mandatory 'client_id' was missing."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'client_id' parameter is missing." })); } // Store the validated client_id as a request property. request.SetProperty(OpenIdConnectConstants.Properties.ValidatedClientId, context.ClientId); Logger.LogInformation("The token request was successfully validated."); AuthenticationTicket ticket = null; // See http://tools.ietf.org/html/rfc6749#section-4.1 // and http://tools.ietf.org/html/rfc6749#section-4.1.3 (authorization code grant). // See http://tools.ietf.org/html/rfc6749#section-6 (refresh token grant). if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { ticket = request.IsAuthorizationCodeGrantType() ? await DeserializeAuthorizationCodeAsync(request.Code, request) : await DeserializeRefreshTokenAsync(request.RefreshToken, request); if (ticket == null) { Logger.LogError("The token request was rejected because the " + "authorization code or the refresh token was invalid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = request.IsAuthorizationCodeGrantType() ? "The specified authorization code is invalid." : "The specified refresh token is invalid." })); } // If the client was fully authenticated when retrieving its refresh token, // the current request must be rejected if client authentication was not enforced. if (request.IsRefreshTokenGrantType() && !context.IsValidated && ticket.IsConfidential()) { Logger.LogError("The token request was rejected because client authentication " + "was required to use the confidential refresh token."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "Client authentication is required to use the specified refresh token." })); } if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Logger.LogError("The token request was rejected because the " + "authorization code or the refresh token was expired."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = request.IsAuthorizationCodeGrantType() ? "The specified authorization code is no longer valid." : "The specified refresh token is no longer valid." })); } // Note: presenters may be empty during a grant_type=refresh_token request if the refresh token // was issued to a public client but cannot be null for an authorization code grant request. var presenters = ticket.GetPresenters(); if (request.IsAuthorizationCodeGrantType() && !presenters.Any()) { throw new InvalidOperationException("The presenters list cannot be extracted from the authorization code."); } // Ensure the authorization code/refresh token was issued to the client application making the token request. // Note: when using the refresh token grant, client_id is optional but must validated if present. // As a consequence, this check doesn't depend on the actual status of client authentication. // See https://tools.ietf.org/html/rfc6749#section-6 // and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken if (!string.IsNullOrEmpty(context.ClientId) && presenters.Any() && !presenters.Contains(context.ClientId, StringComparer.Ordinal)) { Logger.LogError("The token request was rejected because the authorization " + "code was issued to a different client application."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = request.IsAuthorizationCodeGrantType() ? "The specified authorization code cannot be used by this client application." : "The specified refresh token cannot be used by this client application." })); } // Validate the redirect_uri flowed by the client application during this token request. // Note: for pure OAuth2 requests, redirect_uri is only mandatory if the authorization request // contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri // but the specifications allow proceeding the token request without returning an error // if the authorization request didn't contain an explicit redirect_uri. // See https://tools.ietf.org/html/rfc6749#section-4.1.3 // and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation var address = ticket.GetProperty(OpenIdConnectConstants.Properties.OriginalRedirectUri); if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(address)) { if (string.IsNullOrEmpty(request.RedirectUri)) { Logger.LogError("The token request was rejected because the mandatory 'redirect_uri' " + "parameter was missing from the grant_type=authorization_code request."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'redirect_uri' parameter is missing." })); } else if (!string.Equals(address, request.RedirectUri, StringComparison.Ordinal)) { Logger.LogError("The token request was rejected because the 'redirect_uri' " + "parameter didn't correspond to the expected value."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified 'redirect_uri' parameter doesn't match the client " + "redirection endpoint the authorization code was initially sent to." })); } } // If a code challenge was initially sent in the authorization request and associated with the // code, validate the code verifier to ensure the token request is sent by a legit caller. var challenge = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallenge); if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(challenge)) { // Get the code verifier from the token request. // If it cannot be found, return an invalid_grant error. var verifier = request.CodeVerifier; if (string.IsNullOrEmpty(verifier)) { Logger.LogError("The token request was rejected because the required 'code_verifier' " + "parameter was missing from the grant_type=authorization_code request."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'code_verifier' parameter is missing." })); } // Note: the code challenge method is always validated when receiving the authorization request. var method = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod); Debug.Assert(string.IsNullOrEmpty(method) || string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Plain, StringComparison.Ordinal) || string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal), "The specified code challenge method should be supported."); // If the S256 challenge method was used, compute the hash corresponding to the code verifier. if (string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal)) { using (var algorithm = SHA256.Create()) { // Compute the SHA-256 hash of the code verifier and encode it using base64-url. // See https://tools.ietf.org/html/rfc7636#section-4.6 for more information. var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(request.CodeVerifier)); verifier = Base64UrlEncoder.Encode(hash); } } // Compare the verifier and the code challenge: if the two don't match, return an error. // Note: to prevent timing attacks, a time-constant comparer is always used. if (!OpenIdConnectServerHelpers.AreEqual(verifier, challenge)) { Logger.LogError("The token request was rejected because the 'code_verifier' was invalid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified 'code_verifier' parameter is invalid." })); } } if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Scope)) { // When an explicit scope parameter has been included in the token request // but was missing from the initial request, the request MUST be rejected. // See http://tools.ietf.org/html/rfc6749#section-6 var scopes = ticket.GetScopes(); if (!scopes.Any()) { Logger.LogError("The token request was rejected because the 'scope' parameter was not allowed."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The 'scope' parameter is not valid in this context." })); } // When an explicit scope parameter has been included in the token request, // the authorization server MUST ensure that it doesn't contain scopes // that were not allowed during the initial authorization/token request. // See https://tools.ietf.org/html/rfc6749#section-6 else if (!new HashSet <string>(scopes).IsSupersetOf(request.GetScopes())) { Logger.LogError("The token request was rejected because the 'scope' parameter was not valid."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified 'scope' parameter is invalid." })); } } } var notification = new HandleTokenRequestContext(Context, Options, request, ticket); await Options.Provider.HandleTokenRequest(notification); if (notification.HandledResponse) { Logger.LogDebug("The token request was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default token request handling was skipped from user code."); return(false); } else if (notification.IsRejected) { Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant, /* Description: */ notification.ErrorDescription); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } // Flow the changes made to the ticket. ticket = notification.Ticket; // Ensure an authentication ticket has been provided or return // an error code indicating that the request was rejected. if (ticket == null) { Logger.LogError("The token request was rejected because it was not handled by the user code."); return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The token request was rejected by the authorization server." })); } return(await HandleSignInAsync(ticket)); }
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 cryptography request was rejected because an invalid " + "HTTP method was specified: {Method}.", Request.Method); return(await SendCryptographyResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified HTTP method is not valid." })); } var request = new OpenIdConnectRequest(Request.Query); // Note: set the message type before invoking the ExtractCryptographyRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.CryptographyRequest); // Store the cryptography request in the ASP.NET context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractCryptographyRequestContext(Context, Options, request); await Options.Provider.ExtractCryptographyRequest(@event); if (@event.HandledResponse) { Logger.LogDebug("The cryptography request was handled in user code."); return(true); } else if (@event.Skipped) { Logger.LogDebug("The default cryptography request handling was skipped from user code."); return(false); } else if (@event.IsRejected) { Logger.LogError("The cryptography request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendCryptographyResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } Logger.LogInformation("The cryptography request was successfully extracted " + "from the HTTP request: {Request}.", request); var context = new ValidateCryptographyRequestContext(Context, Options, request); await Options.Provider.ValidateCryptographyRequest(context); if (context.HandledResponse) { Logger.LogDebug("The cryptography request was handled in user code."); return(true); } else if (context.Skipped) { Logger.LogDebug("The default cryptography request handling was skipped from user code."); return(false); } else if (context.IsRejected) { Logger.LogError("The cryptography request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendCryptographyResponseAsync(new OpenIdConnectResponse { 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) { // If the signing key is not an asymmetric key, ignore it. if (!(credentials.Key is AsymmetricSecurityKey)) { Logger.LogDebug("A non-asymmetric signing key of type '{Type}' was excluded " + "from the key set.", credentials.Key.GetType().FullName); continue; } #if SUPPORTS_ECDSA if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256Signature) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256Signature) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384Signature) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512Signature)) { Logger.LogInformation("An unsupported signing key of type '{Type}' was ignored and excluded " + "from the key set. Only RSA and ECDSA asymmetric security keys can be " + "exposed via the JWKS endpoint.", credentials.Key.GetType().Name); continue; } #else if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256Signature)) { Logger.LogInformation("An unsupported signing key of type '{Type}' was ignored and excluded " + "from the key set. Only RSA asymmetric security keys can be exposed " + "via the JWKS endpoint.", credentials.Key.GetType().Name); continue; } #endif var key = new JsonWebKey { Use = JsonWebKeyUseNames.Sig, // 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, }; if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256Signature)) { RSA algorithm = null; // Note: IdentityModel 5 doesn't expose a method allowing to retrieve the underlying algorithm // from a generic asymmetric security key. To work around this limitation, try to cast // the security key to the built-in IdentityModel types to extract the required RSA instance. // See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/395 if (credentials.Key is X509SecurityKey x509SecurityKey) { algorithm = x509SecurityKey.PublicKey as RSA; } else if (credentials.Key is RsaSecurityKey rsaSecurityKey) { algorithm = rsaSecurityKey.Rsa; // If no RSA instance can be found, create one using // the RSA parameters attached to the security key. if (algorithm == null) { var rsa = RSA.Create(); rsa.ImportParameters(rsaSecurityKey.Parameters); algorithm = rsa; } } // Skip the key if an algorithm instance cannot be extracted. if (algorithm == null) { Logger.LogWarning("A signing key was ignored because it was unable " + "to provide the requested algorithm instance."); continue; } // Export the RSA public key to create a new JSON Web Key // exposing the exponent and the modulus parameters. var parameters = algorithm.ExportParameters(includePrivateParameters: false); Debug.Assert(parameters.Exponent != null && parameters.Modulus != null, "RSA.ExportParameters() shouldn't return null parameters."); key.Kty = JsonWebAlgorithmsKeyTypes.RSA; // Note: both E and N must be base64url-encoded. // See https://tools.ietf.org/html/rfc7518#section-6.3.1.1 key.E = Base64UrlEncoder.Encode(parameters.Exponent); key.N = Base64UrlEncoder.Encode(parameters.Modulus); } #if SUPPORTS_ECDSA else if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256Signature) || credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384Signature) || credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512Signature)) { ECDsa algorithm = null; if (credentials.Key is X509SecurityKey x509SecurityKey) { algorithm = x509SecurityKey.PublicKey as ECDsa; } else if (credentials.Key is ECDsaSecurityKey ecdsaSecurityKey) { algorithm = ecdsaSecurityKey.ECDsa; } // Skip the key if an algorithm instance cannot be extracted. if (algorithm == null) { Logger.LogWarning("A signing key was ignored because it was unable " + "to provide the requested algorithm instance."); continue; } // Export the ECDsa public key to create a new JSON Web Key // exposing the coordinates of the point on the curve. var parameters = algorithm.ExportParameters(includePrivateParameters: false); Debug.Assert(parameters.Q.X != null && parameters.Q.Y != null, "ECDsa.ExportParameters() shouldn't return null coordinates."); key.Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve; key.Crv = OpenIdConnectServerHelpers.GetJwtAlgorithmCurve(parameters.Curve); // Note: both X and Y must be base64url-encoded. // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2 key.X = Base64UrlEncoder.Encode(parameters.Q.X); key.Y = Base64UrlEncoder.Encode(parameters.Q.Y); } #endif // If the signing key is embedded in a X.509 certificate, set // the x5t and x5c parameters using the certificate details. var certificate = (credentials.Key as X509SecurityKey)?.Certificate; if (certificate != null) { // x5t must be base64url-encoded. // See https://tools.ietf.org/html/rfc7517#section-4.8 key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash()); // Unlike E or N, the certificates contained in x5c // must be base64-encoded and not base64url-encoded. // See https://tools.ietf.org/html/rfc7517#section-4.7 key.X5c.Add(Convert.ToBase64String(certificate.RawData)); } notification.Keys.Add(key); } await Options.Provider.HandleCryptographyRequest(notification); if (notification.HandledResponse) { Logger.LogDebug("The cryptography request was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default cryptography request handling was skipped from user code."); return(false); } else if (notification.IsRejected) { Logger.LogError("The cryptography request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); return(await SendCryptographyResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } var keys = new JArray(); foreach (var key in notification.Keys) { var item = new JObject(); // Ensure a key type has been provided. // See https://tools.ietf.org/html/rfc7517#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.Crv] = key.Crv, [JsonWebKeyParameterNames.E] = key.E, [JsonWebKeyParameterNames.N] = key.N, [JsonWebKeyParameterNames.X] = key.X, [JsonWebKeyParameterNames.Y] = key.Y, [JsonWebKeyParameterNames.X5t] = key.X5t, [JsonWebKeyParameterNames.X5u] = key.X5u }; foreach (var parameter in parameters) { if (!string.IsNullOrEmpty(parameter.Value)) { item.Add(parameter.Key, parameter.Value); } } if (key.KeyOps.Count != 0) { item.Add(JsonWebKeyParameterNames.KeyOps, new JArray(key.KeyOps)); } if (key.X5c.Count != 0) { item.Add(JsonWebKeyParameterNames.X5c, new JArray(key.X5c)); } keys.Add(item); } // Note: AddParameter() is used here to ensure the mandatory "keys" node // is returned to the caller, even if the key set doesn't expose any key. // See https://tools.ietf.org/html/rfc7517#section-5 for more information. var response = new OpenIdConnectResponse(); response.AddParameter(OpenIdConnectConstants.Parameters.Keys, keys); return(await SendCryptographyResponseAsync(response)); }
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 configuration request was rejected because an invalid " + "HTTP method was specified: {Method}.", Request.Method); return(await SendConfigurationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified HTTP method is not valid." })); } var request = new OpenIdConnectRequest(Request.Query); // Note: set the message type before invoking the ExtractConfigurationRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.ConfigurationRequest); // Store the configuration request in the ASP.NET context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractConfigurationRequestContext(Context, Options, request); await Options.Provider.ExtractConfigurationRequest(@event); if (@event.HandledResponse) { Logger.LogDebug("The configuration request was handled in user code."); return(true); } else if (@event.Skipped) { Logger.LogDebug("The default configuration request handling was skipped from user code."); return(false); } else if (@event.IsRejected) { Logger.LogError("The configuration request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendConfigurationResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } Logger.LogInformation("The configuration request was successfully extracted " + "from the HTTP request: {Request}.", request); var context = new ValidateConfigurationRequestContext(Context, Options, request); await Options.Provider.ValidateConfigurationRequest(context); if (context.HandledResponse) { Logger.LogDebug("The configuration request was handled in user code."); return(true); } else if (context.Skipped) { Logger.LogDebug("The default configuration request handling was skipped from user code."); return(false); } else if (context.IsRejected) { Logger.LogError("The configuration request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendConfigurationResponseAsync(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } Logger.LogInformation("The configuration request was successfully validated."); var notification = new HandleConfigurationRequestContext(Context, Options, request) { 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.IntrospectionEndpointPath.HasValue) { notification.IntrospectionEndpoint = notification.Issuer.AddPath(Options.IntrospectionEndpointPath); notification.IntrospectionEndpointAuthenticationMethods.Add( OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretBasic); notification.IntrospectionEndpointAuthenticationMethods.Add( OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretPost); } if (Options.LogoutEndpointPath.HasValue) { notification.LogoutEndpoint = notification.Issuer.AddPath(Options.LogoutEndpointPath); } if (Options.RevocationEndpointPath.HasValue) { notification.RevocationEndpoint = notification.Issuer.AddPath(Options.RevocationEndpointPath); notification.RevocationEndpointAuthenticationMethods.Add( OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretBasic); notification.RevocationEndpointAuthenticationMethods.Add( OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretPost); } if (Options.TokenEndpointPath.HasValue) { notification.TokenEndpoint = notification.Issuer.AddPath(Options.TokenEndpointPath); notification.TokenEndpointAuthenticationMethods.Add( OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretBasic); notification.TokenEndpointAuthenticationMethods.Add( OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretPost); } if (Options.UserinfoEndpointPath.HasValue) { notification.UserinfoEndpoint = notification.Issuer.AddPath(Options.UserinfoEndpointPath); } if (Options.AuthorizationEndpointPath.HasValue) { notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit); if (Options.TokenEndpointPath.HasValue) { // Only expose the code grant type and the code challenge methods // if both the authorization and the token endpoints are enabled. notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode); // Note: supporting S256 is mandatory for authorization servers that implement PKCE. // See https://tools.ietf.org/html/rfc7636#section-4.2 for more information. notification.CodeChallengeMethods.Add(OpenIdConnectConstants.CodeChallengeMethods.Plain); notification.CodeChallengeMethods.Add(OpenIdConnectConstants.CodeChallengeMethods.Sha256); } } if (Options.TokenEndpointPath.HasValue) { notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken); 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); // 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); } // Only expose the response types containing id_token if an asymmetric signing key is available. if (Options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey)) { 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 + ' ' + 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); foreach (var credentials in Options.SigningCredentials) { // If the signing key is not an asymmetric key, ignore it. if (!(credentials.Key is AsymmetricSecurityKey)) { continue; } // Try to resolve the JWA algorithm short name. If a null value is returned, ignore it. var algorithm = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm); if (string.IsNullOrEmpty(algorithm)) { continue; } notification.IdTokenSigningAlgorithms.Add(algorithm); } await Options.Provider.HandleConfigurationRequest(notification); if (notification.HandledResponse) { Logger.LogDebug("The configuration request was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default configuration request handling was skipped from user code."); return(false); } else if (notification.IsRejected) { Logger.LogError("The configuration request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); return(await SendConfigurationResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } var response = new OpenIdConnectResponse { [OpenIdConnectConstants.Metadata.Issuer] = notification.Issuer, [OpenIdConnectConstants.Metadata.AuthorizationEndpoint] = notification.AuthorizationEndpoint, [OpenIdConnectConstants.Metadata.TokenEndpoint] = notification.TokenEndpoint, [OpenIdConnectConstants.Metadata.IntrospectionEndpoint] = notification.IntrospectionEndpoint, [OpenIdConnectConstants.Metadata.EndSessionEndpoint] = notification.LogoutEndpoint, [OpenIdConnectConstants.Metadata.RevocationEndpoint] = notification.RevocationEndpoint, [OpenIdConnectConstants.Metadata.UserinfoEndpoint] = notification.UserinfoEndpoint, [OpenIdConnectConstants.Metadata.JwksUri] = notification.CryptographyEndpoint, [OpenIdConnectConstants.Metadata.GrantTypesSupported] = new JArray(notification.GrantTypes), [OpenIdConnectConstants.Metadata.ResponseTypesSupported] = new JArray(notification.ResponseTypes), [OpenIdConnectConstants.Metadata.ResponseModesSupported] = new JArray(notification.ResponseModes), [OpenIdConnectConstants.Metadata.ScopesSupported] = new JArray(notification.Scopes), [OpenIdConnectConstants.Metadata.IdTokenSigningAlgValuesSupported] = new JArray(notification.IdTokenSigningAlgorithms), [OpenIdConnectConstants.Metadata.CodeChallengeMethodsSupported] = new JArray(notification.CodeChallengeMethods), [OpenIdConnectConstants.Metadata.SubjectTypesSupported] = new JArray(notification.SubjectTypes), [OpenIdConnectConstants.Metadata.TokenEndpointAuthMethodsSupported] = new JArray(notification.TokenEndpointAuthenticationMethods), [OpenIdConnectConstants.Metadata.IntrospectionEndpointAuthMethodsSupported] = new JArray(notification.IntrospectionEndpointAuthenticationMethods), [OpenIdConnectConstants.Metadata.RevocationEndpointAuthMethodsSupported] = new JArray(notification.RevocationEndpointAuthenticationMethods) }; foreach (var metadata in notification.Metadata) { response.SetParameter(metadata.Key, metadata.Value); } return(await SendConfigurationResponseAsync(response)); }
public IActionResult Logout(OpenIdConnectRequest request) { // Flow the request_id to allow OpenIddict to restore // the original logout request from the distributed cache. return View(); /*return View(new LogoutViewModel { RequestId = request.RequestId, });*/ }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { Debug.Assert(request.IsTokenRequest(), "The OpenIddict binder for ASP.NET Core MVC is not registered. " + "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); if (request.IsPasswordGrantType()) { var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIddictConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Validate the username/password parameters and ensure the account is not locked out. var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure : true); if (!result.Succeeded) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIddictConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the authorization code/refresh token. var info = await HttpContext.AuthenticateAsync(OpenIddictServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code/refresh token. // Note: if you want to automatically invalidate the authorization code/refresh token // when the user password/roles change, use the following line instead: // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIddictConstants.Errors.InvalidGrant, ErrorDescription = "The token is no longer valid." })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIddictConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } // Create a new authentication ticket, but reuse the properties stored in the // authorization code/refresh token, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIddictConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { Debug.Assert(request.IsTokenRequest(), "The OpenIddict binder for ASP.NET Core MVC is not registered. " + "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); if (request.IsPasswordGrantType()) { var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Ensure the user is allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in." })); } // Reject the token request if two-factor authentication has been enabled by the user. if (_userManager.SupportsUserTwoFactor && await _userManager.GetTwoFactorEnabledAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in." })); } // Ensure the user is not already locked out. if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Ensure the password is valid. if (!await _userManager.CheckPasswordAsync(user, request.Password)) { if (_userManager.SupportsUserLockout) { await _userManager.AccessFailedAsync(user); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } if (_userManager.SupportsUserLockout) { await _userManager.ResetAccessFailedCountAsync(user); } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } else if (request.IsRefreshTokenGrantType()) { // Retrieve the claims principal stored in the refresh token. var info = await HttpContext.Authentication.GetAuthenticateInfoAsync( OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the refresh token. var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The refresh token is no longer valid." })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } // Create a new authentication ticket, but reuse the properties stored // in the refresh token, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, User user) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. var scopes = new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles };// .Intersect(request.GetScopes()); // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in principal.Claims) { // Always include the user identifier in the // access token and the identity token. if (claim.Type == ClaimTypes.NameIdentifier) { claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); } // Include the name claim, but only if the "profile" scope was requested. else if (claim.Type == ClaimTypes.Name && scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) { claim.SetDestinations(OpenIdConnectConstants.Destinations.IdentityToken); } // Include the role claims, but only if the "roles" scope was requested. else if (claim.Type == ClaimTypes.Role && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); } // The other claims won't be added to the access // and identity tokens and will be kept private. } List<Claim> roles = principal.Claims.Where(c => c.Type == ClaimTypes.Role).ToList(); // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket( principal, new AuthenticationProperties() { AllowRefresh = true, ExpiresUtc = DateTimeOffset.Now.AddDays(14), IsPersistent = true, Items = { new KeyValuePair<string, string>(".roles", string.Join(", ", roles.Select(r => r.Value))) } }, OpenIdConnectServerDefaults.AuthenticationScheme); // ticket.SetResources(request.GetResources()); ticket.SetScopes(scopes); return ticket; }
//TODO: this is an example private async Task <AuthenticationTicket> CreateTicketAsync( OpenIdConnectRequest request, AuthenticateResult authResult = null) { AuthenticationProperties properties = authResult.Properties; var userId = authResult.Principal.Claims.FirstOrDefault(_ => _.Type == OpenIdConnectConstants.Claims.Subject)?.Value; if (userId == null) { return(null); } // string userPrincipalName = _sessionStateService.Get<string>("UserPrincipalName"); SimpleClaim simpleClaim = new SimpleClaim() { ObjectIdentifier = userId, UserPrincipalName = "SomeUPN", GivenName = "Joe", Surname = "Citizen" }; // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. //var principal = await _signInManager.CreateUserPrincipalAsync(user); var identity = new ClaimsIdentity( OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, OpenIdConnectConstants.Claims.Role); // Add a "sub" claim containing the user identifier, and attach // the "access_token" destination to allow OpenIddict to store it // in the access token, so it can be retrieved from your controllers. identity.AddClaim(OpenIdConnectConstants.Claims.Subject, simpleClaim.ObjectIdentifier, OpenIdConnectConstants.Destinations.IdentityToken); identity.AddClaim(OpenIdConnectConstants.Claims.Name, simpleClaim.GivenName, OpenIdConnectConstants.Destinations.IdentityToken); identity.AddClaim(OpenIdConnectConstants.Claims.GivenName, simpleClaim.GivenName, OpenIdConnectConstants.Destinations.IdentityToken); identity.AddClaim(OpenIdConnectConstants.Claims.FamilyName, simpleClaim.Surname, OpenIdConnectConstants.Destinations.IdentityToken); identity.AddClaim(ClaimTypes.Upn, simpleClaim.UserPrincipalName, OpenIdConnectConstants.Destinations.IdentityToken); // ... add other claims, if necessary. var principal = new ClaimsPrincipal(identity); // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties, OpenIdConnectServerDefaults.AuthenticationScheme); if (!request.IsAuthorizationCodeGrantType()) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes())); } ticket.SetResources("resource_server"); // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in ticket.Principal.Claims) { // Never include the security stamp in the access and identity tokens, as it's a secret value. if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } var destinations = new List <string> { OpenIdConnectConstants.Destinations.AccessToken }; // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. // The other claims will only be added to the access_token, which is encrypted when using the default format. if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) || (claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) || (claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles))) { destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken); } claim.SetDestinations(destinations); } return(ticket); }
public async Task <IActionResult> Exchange([ModelBinder(BinderType = typeof(OpenIddictMvcBinder))] OpenIdConnectRequest request) { if (request.IsPasswordGrantType()) { var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Ensure the user is allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in." })); } // Reject the token request if two-factor authentication has been enabled by the user. if (_userManager.SupportsUserTwoFactor && await _userManager.GetTwoFactorEnabledAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified user is not allowed to sign in." })); } // Ensure the user is not already locked out. if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } // Ensure the password is valid. if (!await _userManager.CheckPasswordAsync(user, request.Password)) { if (_userManager.SupportsUserLockout) { await _userManager.AccessFailedAsync(user); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } if (_userManager.SupportsUserLockout) { await _userManager.ResetAccessFailedCountAsync(user); } // Create a new authentication ticket. var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
protected override bool IsValid(OpenIdConnectRequest request) { return request.IsRefreshTokenGrantType(); }
private async Task <IActionResult> ExchangeAuthorizationCodeOrRefreshTokenGrantType(OpenIdConnectRequest request) { // Retrieve the claims principal stored in the authorization code/refresh token. //var info = await HttpContext.Authentication.GetAuthenticateInfoAsync( var info = await HttpContext.AuthenticateAsync( OpenIdConnectServerDefaults.AuthenticationScheme); // Retrieve the user profile corresponding to the authorization code/refresh token. // Note: if you want to automatically invalidate the authorization code/refresh token // when the user password/roles change, use the following line instead: // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = T["The token is no longer valid."] })); } // Ensure the user is still allowed to sign in. if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = T["The user is no longer allowed to sign in."] })); } // Create a new authentication ticket, but reuse the properties stored in the // authorization code/refresh token, including the scopes originally granted. var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); }
private async Task <bool> InvokeAuthorizationEndpointAsync() { OpenIdConnectRequest request; if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { request = new OpenIdConnectRequest(Request.Query); } 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)) { Logger.LogError("The authorization request was rejected because " + "the mandatory 'Content-Type' header was missing."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'Content-Type' header must be specified." })); } // May have media/type; charset=utf-8, allow partial match. if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The authorization request was rejected because an invalid 'Content-Type' " + "header was specified: {ContentType}.", Request.ContentType); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'Content-Type' header is not valid." })); } request = new OpenIdConnectRequest(await Request.ReadFormAsync(Context.RequestAborted)); } else { Logger.LogError("The authorization request was rejected because an invalid " + "HTTP method was specified: {Method}.", Request.Method); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified HTTP method is not valid." })); } // Note: set the message type before invoking the ExtractAuthorizationRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.AuthorizationRequest); // Store the authorization request in the ASP.NET context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractAuthorizationRequestContext(Context, Scheme, Options, request); await Provider.ExtractAuthorizationRequest(@event); if (@event.Result != null) { if (@event.Result.Handled) { Logger.LogDebug("The authorization request was handled in user code."); return(true); } else if (@event.Result.Skipped) { Logger.LogDebug("The default authorization request handling was skipped from user code."); return(false); } } else if (@event.IsRejected) { Logger.LogError("The authorization request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } // Store the original redirect_uri sent by the client application for later comparison. request.SetProperty(OpenIdConnectConstants.Properties.OriginalRedirectUri, request.RedirectUri); Logger.LogInformation("The authorization request was successfully extracted " + "from the HTTP request: {Request}.", 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)) { Logger.LogError("The authorization request was rejected because " + "the mandatory 'client_id' parameter was missing."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'client_id' parameter is 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)) { Logger.LogError("The authorization request was rejected because " + "the mandatory 'redirect_uri' parameter was missing."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'redirect_uri' parameter is missing." })); } 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 // // Note: on Linux/macOS, "/path" URLs are treated as valid absolute file URLs. // To ensure relative redirect_uris are correctly rejected on these platforms, // an additional check using IsWellFormedOriginalString() is made here. // See https://github.com/dotnet/corefx/issues/22098 for more information. if (!Uri.TryCreate(request.RedirectUri, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString()) { 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(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The 'redirect_uri' parameter must be a valid absolute URL." })); } // 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 if (!string.IsNullOrEmpty(uri.Fragment)) { Logger.LogError("The authorization request was rejected because the 'redirect_uri' " + "contained a URL fragment: {RedirectUri}.", request.RedirectUri); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The 'redirect_uri' parameter must not include a fragment." })); } } // Reject requests missing the mandatory response_type parameter. if (string.IsNullOrEmpty(request.ResponseType)) { Logger.LogError("The authorization request was rejected because " + "the mandatory 'response_type' parameter was missing."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'response_type' parameter is missing." })); } // 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 (request.IsQueryResponseMode() && (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) || request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token))) { Logger.LogError("The authorization request was rejected because the 'response_type'/'response_mode' combination " + "was invalid: {ResponseType} ; {ResponseMode}.", request.ResponseType, request.ResponseMode); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'response_type'/'response_mode' combination is invalid." })); } // 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. if (string.IsNullOrEmpty(request.Nonce) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && (request.IsImplicitFlow() || request.IsHybridFlow())) { Logger.LogError("The authorization request was rejected because the mandatory 'nonce' parameter was missing."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'nonce' parameter is missing." })); } // Reject requests containing the id_token response_type if no openid scope has been received. if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) && !request.HasScope(OpenIdConnectConstants.Scopes.OpenId)) { Logger.LogError("The authorization request was rejected because the 'openid' scope was missing."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'openid' scope is missing." })); } // Reject requests containing the id_token response_type if no asymmetric signing key has been registered. if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) && !Options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey)) { Logger.LogError("The authorization request was rejected because the 'id_token' response type could not be honored. " + "To fix this error, consider registering a X.509 signing certificate or an ephemeral signing key."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedResponseType, ErrorDescription = "The specified 'response_type' is not supported by this server." })); } // Reject requests containing the code response_type if the token endpoint has been disabled. if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code) && !Options.TokenEndpointPath.HasValue) { Logger.LogError("The authorization request was rejected because the authorization code flow was disabled."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedResponseType, ErrorDescription = "The specified 'response_type' is not supported by this server." })); } // Reject requests specifying prompt=none with consent/login or select_account. if (request.HasPrompt(OpenIdConnectConstants.Prompts.None) && (request.HasPrompt(OpenIdConnectConstants.Prompts.Consent) || request.HasPrompt(OpenIdConnectConstants.Prompts.Login) || request.HasPrompt(OpenIdConnectConstants.Prompts.SelectAccount))) { Logger.LogError("The authorization request was rejected because an invalid prompt parameter was specified."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'prompt' parameter is invalid." })); } if (!string.IsNullOrEmpty(request.CodeChallenge) || !string.IsNullOrEmpty(request.CodeChallengeMethod)) { // When code_challenge or code_challenge_method is specified, ensure the response_type includes "code". if (!request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code)) { Logger.LogError("The authorization request was rejected because the response type " + "was not compatible with 'code_challenge'/'code_challenge_method'."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The 'code_challenge' and 'code_challenge_method' parameters " + "can only be used with a response type containing 'code'." })); } if (!string.IsNullOrEmpty(request.CodeChallengeMethod)) { // Ensure a code_challenge was specified if a code_challenge_method was used. if (string.IsNullOrEmpty(request.CodeChallenge)) { Logger.LogError("The authorization request was rejected because the code_challenge was missing."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The 'code_challenge_method' parameter " + "cannot be used without 'code_challenge'." })); } // If a code_challenge_method was specified, ensure the algorithm is supported. if (request.CodeChallengeMethod != OpenIdConnectConstants.CodeChallengeMethods.Plain && request.CodeChallengeMethod != OpenIdConnectConstants.CodeChallengeMethods.Sha256) { Logger.LogError("The authorization request was rejected because " + "the specified code challenge was not supported."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified code_challenge_method is not supported." })); } } } var context = new ValidateAuthorizationRequestContext(Context, Scheme, Options, request); await Provider.ValidateAuthorizationRequest(context); if (context.Result != null) { if (context.Result.Handled) { Logger.LogDebug("The authorization request was handled in user code."); return(true); } else if (context.Result.Skipped) { Logger.LogDebug("The default authorization request handling was skipped from user code."); return(false); } } else if (context.IsRejected) { Logger.LogError("The authorization request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } // Store the validated client_id/redirect_uri as request properties. request.SetProperty(OpenIdConnectConstants.Properties.ValidatedClientId, context.ClientId) .SetProperty(OpenIdConnectConstants.Properties.ValidatedRedirectUri, context.RedirectUri); Logger.LogInformation("The authorization request was successfully validated."); var notification = new HandleAuthorizationRequestContext(Context, Scheme, Options, request); await Provider.HandleAuthorizationRequest(notification); if (notification.Result != null) { if (notification.Result.Handled) { Logger.LogDebug("The authorization request was handled in user code."); return(true); } else if (notification.Result.Skipped) { Logger.LogDebug("The default authorization request handling was skipped from user code."); return(false); } } else if (notification.IsRejected) { Logger.LogError("The authorization request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } // If an authentication ticket was provided, stop processing // the request and return an authorization response. var ticket = notification.Ticket; if (ticket == null) { return(false); } return(await SignInAsync(ticket)); }
private async Task <bool> InvokeIntrospectionEndpointAsync() { OpenIdConnectRequest 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 OpenIdConnectRequest(Request.Query); } 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)) { Logger.LogError("The introspection request was rejected because " + "the mandatory 'Content-Type' header was missing."); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'Content-Type' header must be specified." })); } // May have media/type; charset=utf-8, allow partial match. if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The introspection request was rejected because an invalid 'Content-Type' " + "header was specified: {ContentType}.", Request.ContentType); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'Content-Type' header is not valid." })); } request = new OpenIdConnectRequest(await Request.ReadFormAsync(Context.RequestAborted)); } else { Logger.LogError("The introspection request was rejected because an invalid " + "HTTP method was specified: {Method}.", Request.Method); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified HTTP method is not valid." })); } // Note: set the message type before invoking the ExtractIntrospectionRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.IntrospectionRequest); // Store the introspection request in the ASP.NET context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractIntrospectionRequestContext(Context, Scheme, Options, request); await Provider.ExtractIntrospectionRequest(@event); if (@event.Result != null) { if (@event.Result.Handled) { Logger.LogDebug("The introspection request was handled in user code."); return(true); } else if (@event.Result.Skipped) { Logger.LogDebug("The default introspection request handling was skipped from user code."); return(false); } } else if (@event.IsRejected) { Logger.LogError("The introspection request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } Logger.LogInformation("The introspection request was successfully extracted " + "from the HTTP request: {Request}.", request); if (string.IsNullOrEmpty(request.Token)) { return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'token' parameter is missing." })); } // Try to resolve the client credentials specified in the 'Authorization' header. // If they cannot be extracted, fallback to the client_id/client_secret parameters. var credentials = Request.Headers.GetClientCredentials(); if (credentials != null) { // Reject requests that use multiple client authentication methods. // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information. if (!string.IsNullOrEmpty(request.ClientSecret)) { Logger.LogError("The introspection request was rejected because " + "multiple client credentials were specified."); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "Multiple client credentials cannot be specified." })); } request.ClientId = credentials?.Key; request.ClientSecret = credentials?.Value; } var context = new ValidateIntrospectionRequestContext(Context, Scheme, Options, request); await Provider.ValidateIntrospectionRequest(context); // If the validation context was set as fully validated, // mark the OpenID Connect request as confidential. if (context.IsValidated) { request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel, OpenIdConnectConstants.ConfidentialityLevels.Private); } if (context.Result != null) { if (context.Result.Handled) { Logger.LogDebug("The introspection request was handled in user code."); return(true); } else if (context.Result.Skipped) { Logger.LogDebug("The default introspection request handling was skipped from user code."); return(false); } } else if (context.IsRejected) { Logger.LogError("The introspection request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } // Store the validated client_id as a request property. request.SetProperty(OpenIdConnectConstants.Properties.ValidatedClientId, context.ClientId); Logger.LogInformation("The introspection request was successfully validated."); 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.TokenTypeHint) { case OpenIdConnectConstants.TokenTypeHints.AccessToken: ticket = await DeserializeAccessTokenAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.AuthorizationCode: ticket = await DeserializeAuthorizationCodeAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.IdToken: ticket = await DeserializeIdentityTokenAsync(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/rfc7662#section-2.1 if (ticket == null) { // To avoid calling the same deserialization methods twice, // an additional check is made to exclude the corresponding // method when an explicit token_type_hint was specified. switch (request.TokenTypeHint) { case OpenIdConnectConstants.TokenTypeHints.AccessToken: ticket = await DeserializeAuthorizationCodeAsync(request.Token, request) ?? await DeserializeIdentityTokenAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.AuthorizationCode: ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeIdentityTokenAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.IdToken: ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeAuthorizationCodeAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.RefreshToken: ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeAuthorizationCodeAsync(request.Token, request) ?? await DeserializeIdentityTokenAsync(request.Token, request); break; default: ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeAuthorizationCodeAsync(request.Token, request) ?? await DeserializeIdentityTokenAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); break; } } if (ticket == null) { Logger.LogInformation("The introspection request was rejected because the token was invalid."); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { [OpenIdConnectConstants.Parameters.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 (context.IsSkipped && ticket.IsConfidential()) { Logger.LogError("The introspection request was rejected because the caller was not authenticated."); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { [OpenIdConnectConstants.Parameters.Active] = false })); } // If the ticket is already expired, directly return active=false. if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Logger.LogInformation("The introspection request was rejected because the token was expired."); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { [OpenIdConnectConstants.Parameters.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(context.ClientId)) { if (ticket.IsAuthorizationCode() && ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId)) { Logger.LogError("The introspection request was rejected because the " + "authorization code was issued to a different client."); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { [OpenIdConnectConstants.Parameters.Active] = false })); } // Ensure the caller is listed as a valid audience or authorized presenter. else if (ticket.IsAccessToken() && ticket.HasAudience() && !ticket.HasAudience(context.ClientId) && ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId)) { Logger.LogError("The introspection request was rejected because the access token " + "was issued to a different client or for another resource server."); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { [OpenIdConnectConstants.Parameters.Active] = false })); } // Reject the request if the caller is not listed as a valid audience. else if (ticket.IsIdentityToken() && ticket.HasAudience() && !ticket.HasAudience(context.ClientId)) { Logger.LogError("The introspection request was rejected because the " + "identity token was issued to a different client."); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { [OpenIdConnectConstants.Parameters.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(context.ClientId)) { Logger.LogError("The introspection request was rejected because the " + "refresh token was issued to a different client."); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { [OpenIdConnectConstants.Parameters.Active] = false })); } } var notification = new HandleIntrospectionRequestContext(Context, Scheme, Options, request, ticket) { Active = true, Issuer = Context.GetIssuer(Options), TokenId = ticket.GetTokenId(), TokenUsage = ticket.GetProperty(OpenIdConnectConstants.Properties.TokenUsage), Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject) }; // 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.IssuedAt = ticket.Properties.IssuedUtc; notification.NotBefore = ticket.Properties.IssuedUtc; notification.ExpiresAt = ticket.Properties.ExpiresUtc; // Infer the audiences/client_id claims from the properties stored in the authentication ticket. // Note: the client_id claim must be a unique string so multiple presenters cannot be returned. // To work around this limitation, only the first one is returned if multiple values are listed. notification.Audiences.UnionWith(ticket.GetAudiences()); notification.ClientId = ticket.GetPresenters().FirstOrDefault(); // Note: non-metadata claims are only added if the caller's client_id is known // AND is in the specified audiences, unless there's no explicit audience. if (!ticket.HasAudience() || (!string.IsNullOrEmpty(context.ClientId) && ticket.HasAudience(context.ClientId))) { notification.Username = ticket.Principal.Identity?.Name; notification.Scopes.UnionWith(ticket.GetScopes()); // Potentially sensitive claims are only exposed if the client was authenticated // and if the authentication ticket corresponds to an identity or access token. if (context.IsValidated && (ticket.IsAccessToken() || ticket.IsIdentityToken())) { foreach (var grouping in ticket.Principal.Claims.GroupBy(claim => claim.Type)) { // 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. var type = grouping.Key; switch (type) { case OpenIdConnectConstants.Claims.Audience: case OpenIdConnectConstants.Claims.ExpiresAt: case OpenIdConnectConstants.Claims.IssuedAt: case OpenIdConnectConstants.Claims.Issuer: case OpenIdConnectConstants.Claims.NotBefore: case OpenIdConnectConstants.Claims.Scope: case OpenIdConnectConstants.Claims.Subject: case OpenIdConnectConstants.Claims.TokenType: case OpenIdConnectConstants.Claims.TokenUsage: continue; } var claims = grouping.ToArray(); switch (claims.Length) { case 0: continue; // When there's only one claim with the same type, directly // convert the claim as an OpenIdConnectParameter instance, // whose token type is determined from the claim value type. case 1: { notification.Claims[type] = claims[0].AsParameter(); continue; } // When multiple claims share the same type, convert all the claims // to OpenIdConnectParameter instances, retrieve the underlying // JSON values and add everything to a new JSON array. default: { notification.Claims[type] = new JArray(claims.Select(claim => claim.AsParameter().Value)); continue; } } } } } await Provider.HandleIntrospectionRequest(notification); if (notification.Result != null) { if (notification.Result.Handled) { Logger.LogDebug("The introspection request was handled in user code."); return(true); } else if (notification.Result.Skipped) { Logger.LogDebug("The default introspection request handling was skipped from user code."); return(false); } } else if (notification.IsRejected) { Logger.LogError("The introspection request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } var response = new OpenIdConnectResponse { [OpenIdConnectConstants.Claims.Active] = notification.Active }; // Only add the other properties if // the token is considered as active. if (notification.Active) { response[OpenIdConnectConstants.Claims.Issuer] = notification.Issuer; response[OpenIdConnectConstants.Claims.Username] = notification.Username; response[OpenIdConnectConstants.Claims.Subject] = notification.Subject; response[OpenIdConnectConstants.Claims.Scope] = string.Join(" ", notification.Scopes); response[OpenIdConnectConstants.Claims.JwtId] = notification.TokenId; response[OpenIdConnectConstants.Claims.TokenType] = notification.TokenType; response[OpenIdConnectConstants.Claims.TokenUsage] = notification.TokenUsage; response[OpenIdConnectConstants.Claims.ClientId] = notification.ClientId; if (notification.IssuedAt != null) { response[OpenIdConnectConstants.Claims.IssuedAt] = EpochTime.GetIntDate(notification.IssuedAt.Value.UtcDateTime); } if (notification.NotBefore != null) { response[OpenIdConnectConstants.Claims.NotBefore] = EpochTime.GetIntDate(notification.NotBefore.Value.UtcDateTime); } if (notification.ExpiresAt != null) { response[OpenIdConnectConstants.Claims.ExpiresAt] = EpochTime.GetIntDate(notification.ExpiresAt.Value.UtcDateTime); } switch (notification.Audiences.Count) { case 0: break; case 1: response[OpenIdConnectConstants.Claims.Audience] = notification.Audiences.ElementAt(0); break; default: response[OpenIdConnectConstants.Claims.Audience] = new JArray(notification.Audiences); break; } foreach (var claim in notification.Claims) { response.SetParameter(claim.Key, claim.Value); } } return(await SendIntrospectionResponseAsync(response)); }
private async Task <AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetResources(request.GetResources()); //if (!request.IsRefreshTokenGrantType()) //{ // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes())); //} // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in ticket.Principal.Claims) { // Never include the security stamp in the access and identity tokens, as it's a secret value. if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); } var identity = principal.Identity as ClaimsIdentity; if (!string.IsNullOrWhiteSpace(user.FullName)) { identity.AddClaim(CustomClaimTypes.FullName, user.FullName, OpenIdConnectConstants.Destinations.IdentityToken); } if (!string.IsNullOrWhiteSpace(user.JobTitle)) { identity.AddClaim(CustomClaimTypes.JobTitle, user.JobTitle, OpenIdConnectConstants.Destinations.IdentityToken); } if (!string.IsNullOrWhiteSpace(user.Configuration)) { identity.AddClaim(CustomClaimTypes.Configuration, user.Configuration, OpenIdConnectConstants.Destinations.IdentityToken); } return(ticket); }
private async Task <bool> InvokeLogoutEndpointAsync() { OpenIdConnectRequest 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 OpenIdConnectRequest(Request.Query); } 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)) { Logger.LogError("The logout request was rejected because " + "the mandatory 'Content-Type' header was missing."); return(await SendLogoutResponseAsync(new OpenIdConnectResponse { 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)) { Logger.LogError("The logout request was rejected because an invalid 'Content-Type' " + "header was received: {ContentType}.", Request.ContentType); return(await SendLogoutResponseAsync(new OpenIdConnectResponse { 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 OpenIdConnectRequest(await Request.ReadFormAsync()); } else { Logger.LogError("The logout request was rejected because an invalid " + "HTTP method was received: {Method}.", Request.Method); return(await SendLogoutResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed logout request has been received: " + "make sure to use either GET or POST." })); } // Note: set the message type before invoking the ExtractLogoutRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.LogoutRequest); // Store the logout request in the OWIN context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractLogoutRequestContext(Context, Options, request); await Options.Provider.ExtractLogoutRequest(@event); if (@event.HandledResponse) { Logger.LogDebug("The logout request was handled in user code."); return(true); } else if (@event.Skipped) { Logger.LogDebug("The default logout request handling was skipped from user code."); return(false); } else if (@event.IsRejected) { Logger.LogError("The logout request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendLogoutResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } Logger.LogInformation("The logout request was successfully extracted " + "from the HTTP request: {Request}", request); var context = new ValidateLogoutRequestContext(Context, Options, request); await Options.Provider.ValidateLogoutRequest(context); if (context.HandledResponse) { Logger.LogDebug("The logout request was handled in user code."); return(true); } else if (context.Skipped) { Logger.LogDebug("The default logout request handling was skipped from user code."); return(false); } else if (context.IsRejected) { 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(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } // Store the validated post_logout_redirect_uri as a request property. request.SetProperty(OpenIdConnectConstants.Properties.PostLogoutRedirectUri, context.PostLogoutRedirectUri); Logger.LogInformation("The logout request was successfully validated."); var notification = new HandleLogoutRequestContext(Context, Options, request); await Options.Provider.HandleLogoutRequest(notification); if (notification.HandledResponse) { Logger.LogDebug("The logout request was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default logout request handling was skipped from user code."); return(false); } else if (notification.IsRejected) { Logger.LogError("The logout request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); return(await SendLogoutResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } return(false); }
public async Task <IActionResult> Exchange(OpenIdConnectRequest request) { if (request.IsPasswordGrantType()) { var user = await _userManager.FindByEmailAsync(request.Username); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure : true); if (!result.Succeeded) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The username/password couple is invalid." })); } var ticket = await CreateTicketAsync(request, user); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } if (request.IsRefreshTokenGrantType()) { var info = await _authService.AuthenticateAsync(HttpContext, OpenIdConnectServerDefaults.AuthenticationScheme); var user = await _userManager.GetUserAsync(info.Principal); if (user == null) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The token is no longer valid." })); } if (!await _signInManager.CanSignInAsync(user)) { return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The user is no longer allowed to sign in." })); } var ticket = await CreateTicketAsync(request, user, info.Properties); return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme)); } return(BadRequest(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, ErrorDescription = "The specified grant type is not supported." })); }
public static void SetOpenIdConnectRequest([NotNull] this HttpContext context, OpenIdConnectRequest request) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var feature = context.Features.Get <OpenIdConnectServerFeature>(); if (feature == null) { feature = new OpenIdConnectServerFeature(); context.Features.Set(feature); } feature.Request = request; }
public async Task ApplyAuthorizationResponse_RequestIsRemovedFromDistributedCache() { // Arrange var request = new OpenIdConnectRequest { ClientId = "Fabrikam", RedirectUri = "http://www.fabrikam.com/path", ResponseType = OpenIddictConstants.ResponseTypes.Token }; var stream = new MemoryStream(); using (var writer = new BsonDataWriter(stream)) { writer.CloseOutput = false; var serializer = JsonSerializer.CreateDefault(); serializer.Serialize(writer, request); } var cache = new Mock <IDistributedCache>(); cache.Setup(mock => mock.GetAsync( OpenIddictConstants.Environment.AuthorizationRequest + "b2ee7815-5579-4ff7-86b0-ba671b939d96", It.IsAny <CancellationToken>())) .ReturnsAsync(stream.ToArray()); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { var application = new OpenIddictApplication(); instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny <CancellationToken>())) .ReturnsAsync(application); instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny <CancellationToken>())) .ReturnsAsync(true); instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny <CancellationToken>())) .Returns(new ValueTask <string>(OpenIddictConstants.ClientTypes.Public)); })); builder.Services.AddSingleton(cache.Object); builder.EnableRequestCaching(); }); var client = new OpenIdConnectClient(server.CreateClient()); // Act var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest { RequestId = "b2ee7815-5579-4ff7-86b0-ba671b939d96" }); // Assert Assert.NotNull(response.AccessToken); cache.Verify(mock => mock.RemoveAsync( OpenIddictConstants.Environment.AuthorizationRequest + "b2ee7815-5579-4ff7-86b0-ba671b939d96", It.IsAny <CancellationToken>()), Times.Once()); }
public async Task <IActionResult> ExternalLoginCallbackEmbedded(OpenIdConnectRequest request, string remoteError = null) { return(await this.ExternalLoginCallback(request.RedirectUri, remoteError, JwtBearerDefaults.AuthenticationScheme)); }
private async Task <bool> InvokeAuthorizationEndpointAsync() { OpenIdConnectRequest request; if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { request = new OpenIdConnectRequest(Request.Query); } 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(new OpenIdConnectResponse { 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(new OpenIdConnectResponse { 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'." })); } request = new OpenIdConnectRequest(await Request.ReadFormAsync()); } else { Options.Logger.LogError("The authorization request was rejected because an invalid " + "HTTP method was received: {Method}.", Request.Method); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "A malformed authorization request has been received: " + "make sure to use either GET or POST." })); } // Note: set the message type before invoking the ExtractAuthorizationRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.Authorization); // Store the authorization request in the OWIN context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractAuthorizationRequestContext(Context, Options, request); await Options.Provider.ExtractAuthorizationRequest(@event); if (@event.HandledResponse) { return(true); } else if (@event.Skipped) { return(false); } else if (@event.IsRejected) { Options.Logger.LogError("The authorization request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } // 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(new OpenIdConnectResponse { 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(new OpenIdConnectResponse { 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(new OpenIdConnectResponse { 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 fragment: {RedirectUri}.", request.RedirectUri); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "redirect_uri must not include a fragment" })); } } // Reject requests missing the mandatory response_type parameter. if (string.IsNullOrEmpty(request.ResponseType)) { Options.Logger.LogError("The authorization request was rejected because " + "the mandatory 'response_type' parameter was missing."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "response_type parameter missing" })); } // 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(new OpenIdConnectResponse { 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(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "nonce parameter missing" })); } // Reject requests containing the id_token response_type 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(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "openid scope missing" })); } // Reject requests containing the code response_type 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(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedResponseType, ErrorDescription = "response_type=code is not supported by this server" })); } if (!string.IsNullOrEmpty(request.CodeChallenge) || !string.IsNullOrEmpty(request.CodeChallengeMethod)) { // When code_challenge or code_challenge_method is specified, ensure the response_type includes "code". if (!request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code)) { Options.Logger.LogError("The authorization request was rejected because the response type " + "was not compatible with 'code_challenge'/'code_challenge_method'."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The 'code_challenge' and 'code_challenge_method' parameters " + "can only be used with a response type containing 'code'." })); } if (!string.IsNullOrEmpty(request.CodeChallengeMethod)) { // Ensure a code_challenge was specified if a code_challenge_method was used. if (string.IsNullOrEmpty(request.CodeChallenge)) { Options.Logger.LogError("The authorization request was rejected " + "because the code_challenge was missing."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The 'code_challenge_method' parameter " + "cannot be used without 'code_challenge'." })); } // If a code_challenge_method was specified, ensure the algorithm is supported. if (request.CodeChallengeMethod != OpenIdConnectConstants.CodeChallengeMethods.Plain && request.CodeChallengeMethod != OpenIdConnectConstants.CodeChallengeMethods.Sha256) { Options.Logger.LogError("The authorization request was rejected because " + "the specified code challenge was not supported."); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified code_challenge_method is not supported." })); } } } var context = new ValidateAuthorizationRequestContext(Context, Options, request); await Options.Provider.ValidateAuthorizationRequest(context); if (context.HandledResponse) { return(true); } else if (context.Skipped) { return(false); } else if (!context.IsValidated) { Options.Logger.LogError("The authorization request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } var notification = new HandleAuthorizationRequestContext(Context, Options, request); await Options.Provider.HandleAuthorizationRequest(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } else if (notification.IsRejected) { Options.Logger.LogError("The authorization request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } // If an authentication ticket was provided, stop processing // the request and return an authorization response. var ticket = notification.Ticket; if (ticket == null) { return(false); } return(await HandleSignInAsync(ticket)); }
private async Task <bool> InvokeRevocationEndpointAsync() { if (!HttpMethods.IsPost(Request.Method)) { Logger.LogError("The revocation request was rejected because an invalid " + "HTTP method was specified: {Method}.", Request.Method); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified HTTP method is not valid." })); } // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization if (string.IsNullOrEmpty(Request.ContentType)) { Logger.LogError("The revocation request was rejected because " + "the mandatory 'Content-Type' header was missing."); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'Content-Type' header must be specified." })); } // May have media/type; charset=utf-8, allow partial match. if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The revocation request was rejected because an invalid 'Content-Type' " + "header was specified: {ContentType}.", Request.ContentType); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'Content-Type' header is not valid." })); } var request = new OpenIdConnectRequest(await Request.ReadFormAsync(Context.RequestAborted)); // Note: set the message type before invoking the ExtractRevocationRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.RevocationRequest); // Insert the revocation request in the ASP.NET context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractRevocationRequestContext(Context, Scheme, Options, request); await Provider.ExtractRevocationRequest(@event); if (@event.Result != null) { if (@event.Result.Handled) { Logger.LogDebug("The revocation request was handled in user code."); return(true); } else if (@event.Result.Skipped) { Logger.LogDebug("The default revocation request handling was skipped from user code."); return(false); } } else if (@event.IsRejected) { Logger.LogError("The revocation request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } Logger.LogInformation("The revocation request was successfully extracted " + "from the HTTP request: {Request}.", request); if (string.IsNullOrEmpty(request.Token)) { return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'token' parameter is missing." })); } // Try to resolve the client credentials specified in the 'Authorization' header. // If they cannot be extracted, fallback to the client_id/client_secret parameters. var credentials = Request.Headers.GetClientCredentials(); if (credentials != null) { // Reject requests that use multiple client authentication methods. // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information. if (!string.IsNullOrEmpty(request.ClientSecret)) { Logger.LogError("The revocation request was rejected because " + "multiple client credentials were specified."); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "Multiple client credentials cannot be specified." })); } request.ClientId = credentials?.Key; request.ClientSecret = credentials?.Value; } var context = new ValidateRevocationRequestContext(Context, Scheme, Options, request); await Provider.ValidateRevocationRequest(context); // If the validation context was set as fully validated, // mark the OpenID Connect request as confidential. if (context.IsValidated) { request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel, OpenIdConnectConstants.ConfidentialityLevels.Private); } if (context.Result != null) { if (context.Result.Handled) { Logger.LogDebug("The revocation request was handled in user code."); return(true); } else if (context.Result.Skipped) { Logger.LogDebug("The default revocation request handling was skipped from user code."); return(false); } } else if (context.IsRejected) { 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(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } // Store the validated client_id as a request property. request.SetProperty(OpenIdConnectConstants.Properties.ValidatedClientId, context.ClientId); Logger.LogInformation("The revocation request was successfully validated."); 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.TokenTypeHint) { case OpenIdConnectConstants.TokenTypeHints.AccessToken: ticket = await DeserializeAccessTokenAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.AuthorizationCode: ticket = await DeserializeAuthorizationCodeAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.IdToken: ticket = await DeserializeIdentityTokenAsync(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) { // To avoid calling the same deserialization methods twice, // an additional check is made to exclude the corresponding // method when an explicit token_type_hint was specified. switch (request.TokenTypeHint) { case OpenIdConnectConstants.TokenTypeHints.AccessToken: ticket = await DeserializeAuthorizationCodeAsync(request.Token, request) ?? await DeserializeIdentityTokenAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.AuthorizationCode: ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeIdentityTokenAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.IdToken: ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeAuthorizationCodeAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); break; case OpenIdConnectConstants.TokenTypeHints.RefreshToken: ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeAuthorizationCodeAsync(request.Token, request) ?? await DeserializeIdentityTokenAsync(request.Token, request); break; default: ticket = await DeserializeAccessTokenAsync(request.Token, request) ?? await DeserializeAuthorizationCodeAsync(request.Token, request) ?? await DeserializeIdentityTokenAsync(request.Token, request) ?? await DeserializeRefreshTokenAsync(request.Token, request); break; } } if (ticket == null) { Logger.LogInformation("The revocation request was ignored because the token was invalid."); return(await SendRevocationResponseAsync(new OpenIdConnectResponse())); } // If the ticket is already expired, directly return a 200 response. else if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Logger.LogInformation("The revocation request was ignored because the token was already expired."); return(await SendRevocationResponseAsync(new OpenIdConnectResponse())); } // 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()) { Logger.LogError("The revocation request was rejected because the caller was not authenticated."); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest })); } // When a client_id can be inferred from the introspection request, // ensure that the client application is a valid audience/presenter. if (!string.IsNullOrEmpty(context.ClientId)) { if (ticket.IsAuthorizationCode() && ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId)) { Logger.LogError("The revocation request was rejected because the " + "authorization code was issued to a different client."); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest })); } // Ensure the caller is listed as a valid audience or authorized presenter. else if (ticket.IsAccessToken() && ticket.HasAudience() && !ticket.HasAudience(context.ClientId) && ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId)) { Logger.LogError("The revocation request was rejected because the access token " + "was issued to a different client or for another resource server."); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest })); } // Reject the request if the caller is not listed as a valid audience. else if (ticket.IsIdentityToken() && ticket.HasAudience() && !ticket.HasAudience(context.ClientId)) { Logger.LogError("The revocation request was rejected because the " + "identity token was issued to a different client."); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest })); } // 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(context.ClientId)) { Logger.LogError("The revocation request was rejected because the " + "refresh token was issued to a different client."); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest })); } } var notification = new HandleRevocationRequestContext(Context, Scheme, Options, request, ticket); await Provider.HandleRevocationRequest(notification); if (notification.Result != null) { if (notification.Result.Handled) { Logger.LogDebug("The revocation request was handled in user code."); return(true); } else if (notification.Result.Skipped) { Logger.LogDebug("The default revocation request handling was skipped from user code."); return(false); } } else if (notification.IsRejected) { Logger.LogError("The revocation request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } if (!notification.Revoked) { return(await SendRevocationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.UnsupportedTokenType, ErrorDescription = "The specified token cannot be revoked." })); } return(await SendRevocationResponseAsync(new OpenIdConnectResponse())); }
private async Task <bool> SendAuthorizationResponseAsync(OpenIdConnectResponse response, AuthenticationTicket ticket = null) { var request = Context.GetOpenIdConnectRequest(); if (request == null) { request = new OpenIdConnectRequest(); } Context.SetOpenIdConnectResponse(response); 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)) { // 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) { // Return false to allow the rest of // the pipeline to handle the request. return(false); } return(await SendNativePageAsync(response)); } } // Create a new parameters dictionary holding the name/value pairs. var parameters = new Dictionary <string, string>(); foreach (var parameter in response.GetParameters()) { // Don't include redirect_uri in the parameters dictionary. if (string.Equals(parameter.Key, OpenIdConnectConstants.Parameters.RedirectUri, StringComparison.Ordinal)) { continue; } var value = parameter.Value as JValue; if (value == null) { Options.Logger.LogWarning("A parameter whose type was incompatible was ignored and excluded " + "from the authorization response: '{Parameter}'.", parameter.Key); continue; } parameters.Add(parameter.Key, (string)value); } // 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 OpenIdConnectServerProvider.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 parameters) { 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 parameters) { appender.Append(parameter.Key, parameter.Value); } Response.Redirect(appender.ToString()); return(true); } else if (request.IsQueryResponseMode()) { var location = WebUtilities.AddQueryString(response.RedirectUri, parameters); Response.Redirect(location); return(true); } Options.Logger.LogError("The authorization request was rejected because the 'response_mode' " + "parameter was invalid: {ResponseMode}.", request.ResponseMode); return(await SendNativePageAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "response_mode unsupported" })); }
private async Task <AuthenticationTicket> CreateTicketAsync( OpenIdConnectRequest request, UserInfo user, AuthenticationProperties properties = null) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket(principal, properties ?? new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); if (!request.IsRefreshTokenGrantType()) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Roles });//.Intersect(request.GetScopes())); } ticket.SetResources("resource-server"); // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in ticket.Principal.Claims) { // Never include the security stamp in the access and identity tokens, as it's a secret value. if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) { continue; } var destinations = new List <string> { OpenIdConnectConstants.Destinations.AccessToken }; // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. // The other claims will only be added to the access_token, which is encrypted when using the default format. if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) || (claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) || (claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles))) { destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken); } claim.SetDestinations(destinations); } return(ticket); }
private async Task <bool> InvokeUserinfoEndpointAsync() { OpenIdConnectRequest request; if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { request = new OpenIdConnectRequest(Request.Query); } else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) { // Note: if no Content-Type header was specified, assume the userinfo request // doesn't contain any parameter and create an empty OpenIdConnectRequest. if (string.IsNullOrEmpty(Request.ContentType)) { request = new OpenIdConnectRequest(); } else { // 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 specified: {ContentType}.", Request.ContentType); return(await SendUserinfoResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'Content-Type' header is not valid." })); } request = new OpenIdConnectRequest(await Request.ReadFormAsync()); } } else { Logger.LogError("The userinfo request was rejected because an invalid " + "HTTP method was specified: {Method}.", Request.Method); return(await SendUserinfoResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified HTTP method is not valid." })); } // Note: set the message type before invoking the ExtractUserinfoRequest event. request.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.UserinfoRequest); // Insert the userinfo request in the ASP.NET context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractUserinfoRequestContext(Context, Options, request); await Options.Provider.ExtractUserinfoRequest(@event); if (@event.HandledResponse) { Logger.LogDebug("The userinfo request was handled in user code."); return(true); } else if (@event.Skipped) { Logger.LogDebug("The default userinfo request handling was skipped from user code."); return(false); } else if (@event.IsRejected) { Logger.LogError("The userinfo request was rejected with the following error: {Error} ; {Description}", /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ @event.ErrorDescription); return(await SendUserinfoResponseAsync(new OpenIdConnectResponse { Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = @event.ErrorDescription, ErrorUri = @event.ErrorUri })); } Logger.LogInformation("The userinfo request was successfully extracted " + "from the HTTP request: {Request}.", request); string token = null; if (!string.IsNullOrEmpty(request.AccessToken)) { token = request.AccessToken; } else { string header = Request.Headers[HeaderNames.Authorization]; if (!string.IsNullOrEmpty(header)) { if (!header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("The userinfo request was rejected because the " + "'Authorization' header was invalid: {Header}.", header); return(await SendUserinfoResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'Authorization' header is invalid." })); } token = header.Substring("Bearer ".Length); } } if (string.IsNullOrEmpty(token)) { Logger.LogError("The userinfo request was rejected because the access token was missing."); return(await SendUserinfoResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The mandatory 'access_token' parameter is missing." })); } var context = new ValidateUserinfoRequestContext(Context, Options, request); await Options.Provider.ValidateUserinfoRequest(context); if (context.HandledResponse) { Logger.LogDebug("The userinfo request was handled in user code."); return(true); } else if (context.Skipped) { Logger.LogDebug("The default userinfo request handling was skipped from user code."); return(false); } else if (context.IsRejected) { Logger.LogError("The userinfo request was rejected with the following error: {Error} ; {Description}", /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ context.ErrorDescription); return(await SendUserinfoResponseAsync(new OpenIdConnectResponse { Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = context.ErrorDescription, ErrorUri = context.ErrorUri })); } Logger.LogInformation("The userinfo request was successfully validated."); var ticket = await DeserializeAccessTokenAsync(token, request); if (ticket == null) { Logger.LogError("The userinfo request was rejected because the 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 SendUserinfoResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified access token is not valid." })); } if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow) { Logger.LogError("The userinfo request was rejected because the 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 SendUserinfoResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The specified access token is no longer valid." })); } var notification = new HandleUserinfoRequestContext(Context, Options, request, ticket) { Issuer = Context.GetIssuer(Options), Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject) }; // 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 notification.Audiences.UnionWith(ticket.GetPresenters()); // 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(OpenIdConnectConstants.Claims.FamilyName); notification.GivenName = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.GivenName); notification.BirthDate = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Birthdate); } if (ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) { notification.Email = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Email); } if (ticket.HasScope(OpenIdConnectConstants.Scopes.Phone)) { notification.PhoneNumber = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.PhoneNumber); } await Options.Provider.HandleUserinfoRequest(notification); if (notification.HandledResponse) { Logger.LogDebug("The userinfo request was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default userinfo request handling was skipped from user code."); return(false); } else if (notification.IsRejected) { Logger.LogError("The userinfo request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); return(await SendUserinfoResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } // Ensure the "sub" claim has been correctly populated. if (string.IsNullOrEmpty(notification.Subject)) { throw new InvalidOperationException("The subject claim cannot be null or empty."); } var response = new OpenIdConnectResponse { [OpenIdConnectConstants.Claims.Subject] = notification.Subject, [OpenIdConnectConstants.Claims.Address] = notification.Address, [OpenIdConnectConstants.Claims.Birthdate] = notification.BirthDate, [OpenIdConnectConstants.Claims.Email] = notification.Email, [OpenIdConnectConstants.Claims.EmailVerified] = notification.EmailVerified, [OpenIdConnectConstants.Claims.FamilyName] = notification.FamilyName, [OpenIdConnectConstants.Claims.GivenName] = notification.GivenName, [OpenIdConnectConstants.Claims.Issuer] = notification.Issuer, [OpenIdConnectConstants.Claims.PhoneNumber] = notification.PhoneNumber, [OpenIdConnectConstants.Claims.PhoneNumberVerified] = notification.PhoneNumberVerified, [OpenIdConnectConstants.Claims.PreferredUsername] = notification.PreferredUsername, [OpenIdConnectConstants.Claims.Profile] = notification.Profile, [OpenIdConnectConstants.Claims.Website] = notification.Website }; switch (notification.Audiences.Count) { case 0: break; case 1: response[OpenIdConnectConstants.Claims.Audience] = notification.Audiences.ElementAt(0); break; default: response[OpenIdConnectConstants.Claims.Audience] = new JArray(notification.Audiences); break; } foreach (var claim in notification.Claims) { response.SetParameter(claim.Key, claim.Value); } return(await SendUserinfoResponseAsync(response)); }
private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, User user) { // Set the list of scopes granted to the client application. // Note: the offline_access scope must be granted // to allow OpenIddict to return a refresh token. var scopes = new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.OfflineAccess, OpenIdConnectConstants.Scopes.Profile, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes()); // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); principal.AddIdentity(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, user.UserName) })); // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in principal.Claims) { // Always include the user identifier in the // access token and the identity token. if (claim.Type == ClaimTypes.NameIdentifier) { claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); } // Include the name claim, but only if the "profile" scope was requested. else if (claim.Type == ClaimTypes.Name && scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) { claim.SetDestinations(OpenIdConnectConstants.Destinations.IdentityToken); } // Include the role claims, but only if the "roles" scope was requested. else if (claim.Type == ClaimTypes.Role && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); } // The other claims won't be added to the access // and identity tokens and will be kept private. } // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket( principal, new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetScopes(scopes); return ticket; }
private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user) { // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await _signInManager.CreateUserPrincipalAsync(user); // Note: by default, claims are NOT automatically included in the access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. foreach (var claim in principal.Claims) { // In this sample, every claim is serialized in both the access and the identity tokens. // In a real world application, you'd probably want to exclude confidential claims // or apply a claims policy based on the scopes requested by the client application. claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); } // Create a new authentication ticket holding the user identity. var ticket = new AuthenticationTicket( principal, new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); // Set the list of scopes granted to the client application. ticket.SetScopes(new[] { OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, OpenIddictConstants.Scopes.Roles }.Intersect(request.GetScopes())); return ticket; }