// Based on https://stackoverflow.com/a/44322425/1249506 public async Task <IdentityServerToken> GetIdentityServerTokenForUserAsync(User user) { var request = new TokenCreationRequest(); var identityPricipal = await PrincipalFactory.CreateAsync(user); var identityUser = new IdentityServerUser(user.Id.ToString()) { AdditionalClaims = identityPricipal.Claims.ToArray(), DisplayName = user.UserName, AuthenticationTime = DateTime.UtcNow, IdentityProvider = IdentityServerConstants.LocalIdentityProvider }; request.Subject = identityUser.CreatePrincipal(); request.IncludeAllIdentityClaims = true; request.ValidatedRequest = new ValidatedRequest { Subject = request.Subject, }; var client = await ClientStore.FindClientByIdAsync(ClientId); request.ValidatedRequest.SetClient(client); request.Resources = new Resources(await ResourceStore.FindEnabledIdentityResourcesByScopeAsync(client.AllowedScopes), await ResourceStore.FindApiResourcesByScopeAsync(client.AllowedScopes)) { OfflineAccess = client.AllowOfflineAccess }; request.ValidatedRequest.Options = Options; request.ValidatedRequest.ClientClaims = identityUser.AdditionalClaims; var token = await TokenService.CreateAccessTokenAsync(request); var accessToken = await TokenService.CreateSecurityTokenAsync(token); var refreshToken = await RefreshTokenService.CreateRefreshTokenAsync(request.Subject, token, client); return(new IdentityServerToken(token, accessToken, refreshToken)); }
public async Task <IEndpointResult> ProcessAsync(HttpContext httpContext) { Logger.LogInformation($"[{nameof(InitRegistrationEndpoint)}] Started processing trusted device registration initiation endpoint."); var isPostRequest = HttpMethods.IsPost(httpContext.Request.Method); var isApplicationFormContentType = httpContext.Request.HasApplicationFormContentType(); // Validate HTTP request type and method. if (!isPostRequest || !isApplicationFormContentType) { return(Error(OidcConstants.TokenErrors.InvalidRequest, "Request must be of type 'POST' and have a Content-Type equal to 'application/x-www-form-urlencoded'.")); } // Ensure that a valid 'Authorization' header exists. var tokenUsageResult = await Token.Validate(httpContext); if (!tokenUsageResult.TokenFound) { return(Error(OidcConstants.ProtectedResourceErrors.InvalidToken, "No access token is present in the request.")); } // Validate request data and access token. var parameters = (await httpContext.Request.ReadFormAsync()).AsNameValueCollection(); var requestValidationResult = await Request.Validate(parameters, tokenUsageResult.Token); if (requestValidationResult.IsError) { return(Error(requestValidationResult.Error, requestValidationResult.ErrorDescription)); } // Ensure device is not already registered or belongs to any other user. var existingDevice = await UserDeviceStore.GetByDeviceId(requestValidationResult.DeviceId); var isNewDeviceOrOwnedByUser = existingDevice == null || existingDevice.UserId.Equals(requestValidationResult.UserId, StringComparison.OrdinalIgnoreCase); if (!isNewDeviceOrOwnedByUser) { return(Error(OidcConstants.ProtectedResourceErrors.InvalidToken, "Device does not belong to the this user.")); } // Ensure that the principal has declared a phone number which is also confirmed. // We will get these 2 claims by retrieving the identity resources from the store (using the requested scopes existing in the access token) and then calling the profile service. // This will help us make sure that the 'phone' scope was requested and finally allowed in the token endpoint. var identityResources = await ResourceStore.FindEnabledIdentityResourcesByScopeAsync(requestValidationResult.RequestedScopes); var resources = new Resources(identityResources, Enumerable.Empty <ApiResource>(), Enumerable.Empty <ApiScope>()); var validatedResources = new ResourceValidationResult(resources); if (!validatedResources.Succeeded) { return(Error(OidcConstants.ProtectedResourceErrors.InvalidToken, "Identity resources could be validated.")); } var requestedClaimTypes = resources.IdentityResources.SelectMany(x => x.UserClaims).Distinct(); var profileDataRequestContext = new ProfileDataRequestContext(requestValidationResult.Principal, requestValidationResult.Client, IdentityServerConstants.ProfileDataCallers.UserInfoEndpoint, requestedClaimTypes) { RequestedResources = validatedResources }; await ProfileService.GetProfileDataAsync(profileDataRequestContext); var profileClaims = profileDataRequestContext.IssuedClaims; var phoneNumberClaim = profileClaims.FirstOrDefault(x => x.Type == JwtClaimTypes.PhoneNumber); var phoneNumberVerifiedClaim = profileClaims.FirstOrDefault(x => x.Type == JwtClaimTypes.PhoneNumberVerified); if (string.IsNullOrWhiteSpace(phoneNumberClaim?.Value) || phoneNumberVerifiedClaim == null || (bool.TryParse(phoneNumberVerifiedClaim.Value, out var phoneNumberVerified) && !phoneNumberVerified)) { return(Error(OidcConstants.ProtectedResourceErrors.InvalidToken, "User does not have a phone number or the phone number is not verified.")); } var otpAuthenticatedValue = profileClaims.FirstOrDefault(x => x.Type == BasicClaimTypes.OtpAuthenticated)?.Value; var otpAuthenticated = !string.IsNullOrWhiteSpace(otpAuthenticatedValue) && bool.Parse(otpAuthenticatedValue); if (!otpAuthenticated) { // Send OTP code. void messageBuilder(TotpMessageBuilder message) { var builder = message.UsePrincipal(requestValidationResult.Principal).WithMessage(IdentityMessageDescriber.DeviceRegistrationCodeMessage(existingDevice?.Name, requestValidationResult.InteractionMode)); if (requestValidationResult.DeliveryChannel == TotpDeliveryChannel.Sms) { builder.UsingSms(); } else { builder.UsingViber(); } builder.WithPurpose(Constants.TrustedDeviceOtpPurpose(requestValidationResult.UserId, requestValidationResult.DeviceId)); } var totpResult = await TotpService.Send(messageBuilder); if (!totpResult.Success) { return(Error(totpResult.Error)); } } // Create endpoint response. var response = await Response.Generate(requestValidationResult); Logger.LogInformation($"[{nameof(InitRegistrationEndpoint)}] Trusted device registration initiation endpoint success."); return(new InitRegistrationResult(response)); }