Esempio n. 1
0
        /// <summary>
        /// Validates the token request.
        /// </summary>
        /// <param name="context">Class describing the extension grant validation context</param>
        public async Task ValidateAsync(ExtensionGrantValidationContext context)
        {
            var userToken    = context.Request.Raw.Get("token");
            var code         = context.Request.Raw.Get("code");
            var providerName = context.Request.Raw.Get("provider");
            var provider     = default(TotpProviderType?);
            var reason       = context.Request.Raw.Get("reason");

            if (string.IsNullOrEmpty(userToken))
            {
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Access token 'token' parameter missing from payload.");
                return;
            }
            if (string.IsNullOrEmpty(code))
            {
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, "Payload must provide a valid totp 'code'.");
                return;
            }
            var validationResult = await _validator.ValidateAccessTokenAsync(userToken);

            if (validationResult.IsError)
            {
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Access token validation failed");
                return;
            }
            if (!string.IsNullOrEmpty(providerName))
            {
                if (Enum.TryParse <TotpProviderType>(providerName, true, out var pv))
                {
                    provider = pv;
                }
                else
                {
                    context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, "Unsupported 'provider'.");
                    return;
                }
            }
            // Get user's identity.
            var sub        = validationResult.Claims.FirstOrDefault(x => x.Type == "sub").Value;
            var user       = new ClaimsPrincipal(new ClaimsIdentity(validationResult.Claims));
            var totpResult = await _totpService.Verify(user, code, provider, reason);

            if (!totpResult.Success)
            {
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
                return;
            }
            context.Result = new GrantValidationResult(sub, GrantType);
            return;
        }
Esempio n. 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);
        }
Esempio n. 3
0
 /// <summary>
 /// Verify the code received for the given user id.
 /// </summary>
 /// <param name="service">The service to use.</param>
 /// <param name="userId">The user id.</param>
 /// <param name="code">The TOTP code.</param>
 /// <param name="provider">Optionaly pass the provider to use to verify. Defaults to DefaultPhoneProvider</param>
 /// <param name="reason">Optionaly pass the reason used to generate the TOTP.</param>
 public static Task <TotpResult> Verify(this ITotpService service, string userId, string code, TotpProviderType?provider = null, string reason = null) =>
 service.Verify(new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(BasicClaimTypes.Subject, userId) })), code, provider, reason);