/// <summary> /// Sends a new code via the selected channel. /// </summary> /// <param name="service">The service to use.</param> /// <param name="configureMessage">The delegate that will be used to build the message.</param> /// <exception cref="TotpServiceException">used to pass errors between service and the caller.</exception> public static Task <TotpResult> Send(this ITotpService service, Action <TotpMessageBuilder> configureMessage) { if (configureMessage == null) { throw new ArgumentNullException(nameof(configureMessage)); } var messageBuilder = new TotpMessageBuilder(); configureMessage(messageBuilder); var totpMessage = messageBuilder.Build(); return(service.Send(totpMessage.ClaimsPrincipal, totpMessage.Message, totpMessage.DeliveryChannel, totpMessage.Purpose, totpMessage.SecurityToken, totpMessage.PhoneNumberOrEmail)); }
/// <inheritdoc /> public async Task ValidateAsync(ExtensionGrantValidationContext context) { var rawRequest = context.Request.Raw; var accessToken = rawRequest.Get("token"); /* 1. Check if an access token exists in the request. */ if (string.IsNullOrWhiteSpace(accessToken)) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Access token not present in the request."); return; } /* 2. Validate given access token. */ var tokenValidationResult = await _tokenValidator.ValidateAccessTokenAsync(accessToken); if (tokenValidationResult.IsError) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Access token is not valid."); return; } /* 3. Check if given access token contains a subject. */ var subject = tokenValidationResult.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject).Value; if (string.IsNullOrWhiteSpace(subject)) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Claim 'sub' was not found."); return; } /* 4. Query the store for a user with an id equal to the one found in the token. */ var user = await _userManager.FindByIdAsync(subject); if (user == null || !user.PhoneNumberConfirmed || user.Blocked) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); return; } /* 5. Check if an OTP is provided in the request. */ var purpose = $"{TotpConstants.TokenGenerationPurpose.SessionOtp}:{user.Id}"; var otp = rawRequest.Get("otp"); var principal = Principal.Create("OtpAuthenticatedUser", new List <Claim> { new Claim(JwtClaimTypes.Subject, subject) } .ToArray()); /* 5.1 If an OTP is not provided, then we must send one to the user's confirmed phone number. */ if (string.IsNullOrWhiteSpace(otp)) { /* 5.1.1 In order to send the OTP we have to decide the delivery channel. Delivery channel can optionally be sent in the request. */ var providedChannel = rawRequest.Get("channel"); var channel = TotpDeliveryChannel.Sms; if (!string.IsNullOrWhiteSpace(providedChannel)) { if (Enum.TryParse <TotpDeliveryChannel>(providedChannel, ignoreCase: true, out var parsedChannel)) { channel = parsedChannel; } else { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid delivery channel."); return; } } await _totpService.Send(builder => builder.UsePrincipal(principal) .WithMessage(_identityMessageDescriber.OtpSecuredValidatorOtpBody()) .ToPhoneNumber(user.PhoneNumber) .UsingDeliveryChannel(channel, _identityMessageDescriber.OtpSecuredValidatorOtpSubject) .WithPurpose(purpose)); context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "An OTP code was sent to the user. Please replay the request and include the verification code.", new Dictionary <string, object> { { "otp_sent", true } }); return; } /* 5.2 If an OTP is provided, then we must verify it at first. */ var totpVerificationResult = await _totpService.Verify(principal, otp, purpose : purpose); /* If OTP verification code is not valid respond accordingly. */ if (!totpVerificationResult.Success) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "OTP verification code could not be validated."); return; } /* If OTP verification code is valid add the same claims that were present in the token and a new one to mark that OTP verification has been successfully completed. */ var claims = tokenValidationResult.Claims.ToList(); claims.Add(new Claim(BasicClaimTypes.OtpAuthenticated, "true", ClaimValueTypes.Boolean)); context.Result = new GrantValidationResult(subject, GrantType, claims); }
/// <summary> /// Sends a new code via the selected channel for the given user id. /// </summary> /// <param name="service">The service to use.</param> /// <param name="userId">The user id.</param> /// <param name="message">The message to be sent in the SMS. It's important for the message to contain the {0} placeholder in the position where the OTP should be placed.</param> /// <param name="channel">Delivery channel.</param> /// <param name="reason">Optionaly pass the reason to generate the TOTP.</param> /// <exception cref="TotpServiceException">used to pass errors between service and the caller.</exception> public static Task <TotpResult> Send(this ITotpService service, string userId, string message, TotpDeliveryChannel channel = TotpDeliveryChannel.Sms, string reason = null) => service.Send(new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(BasicClaimTypes.Subject, userId) })), message, channel, reason);