示例#1
0
        /// <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));
        }
示例#2
0
        /// <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);
        }
示例#3
0
 /// <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);