question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Validation] How can I perform custom validation, and if fails, then introspection?

See original GitHub issue

Confirm you’ve already contributed to this project or that you sponsor it

  • I confirm I’m a sponsor or a contributor

Version

3.x

Question

I have such a legacy OWIN WebAPI 2 code

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ManualAuthorizeAttribute
    : ActionFilterAttribute, IAuthorizationFilter
{

    #region Public properties

    /// <summary>
    /// Comma separated list of roles this method should allow access for
    /// </summary>
    public string Roles { get; set; }

    #endregion

    #region IAuthorizationFilter implementation

    /// <summary>
    /// Authenticates the call by looking for a Bearer token or a token passed in the query string
    /// </summary>
    /// <remarks>
    /// The query string parameter may be named "token" or "bearer"
    /// </remarks>
    /// <param name="actionContext">Contains the necessary HTTP context</param>
    /// <param name="cancellationToken">Not used</param>
    /// <param name="continuation">Handler to execute after authentication</param>
    /// <returns>Returns a Forbidden status if authentication failes, otherwise the result of continuation()</returns>
    public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
    {
        // Obtain token from header or query string
        string token = actionContext.Request.Headers.Authorization?.Parameter;

        if (string.IsNullOrWhiteSpace(token))
        {
            token = actionContext.Request.RequestUri.ParseQueryString()["token"];

            if (string.IsNullOrWhiteSpace(token))
                token = actionContext.Request.RequestUri.ParseQueryString()["bearer"];

            if (string.IsNullOrWhiteSpace(token))
                return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden));
        }

        try
        {
            // Actual authentication
            byte[] audienceSecret = Convert.FromBase64String(AppSetting.AuthAudienceSecret);
            var tokenHandler = new JwtSecurityTokenHandler();
            SecurityToken validatedToken;

            var validationParameters = new TokenValidationParameters
            {
                IssuerSigningKeys = new SymmetricKeyIssuerSecurityKeyProvider(AppSetting.AuthIssuer, audienceSecret).SecurityKeys,
                ValidIssuer = AppSetting.AuthIssuer,
                ValidAudience = AppSetting.AuthAudienceId,
                ValidateIssuerSigningKey = true,
                ValidateIssuer = true,
                ValidateAudience = true,
            };

            ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out validatedToken);

            if (string.IsNullOrWhiteSpace(Roles) == false)
            {
                // Roles defined in attribute
                string[] roles = Roles.Split(',');

                // Role claim
                string role = claimsPrincipal.Claims.Where(c => c.Type == ((ClaimsIdentity)claimsPrincipal.Identity).RoleClaimType).Select(c => c.Value).FirstOrDefault();

                if (roles.Contains(role, StringComparer.InvariantCultureIgnoreCase) == false)
                    return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden));
            }

            var appIdentity = new ClaimsIdentity(claimsPrincipal.Claims);
            var authRoles = claimsPrincipal.Claims.Where(c => c.Type == ((ClaimsIdentity)claimsPrincipal.Identity).RoleClaimType).Select(c => c.Value).ToArray();
            HttpContext.Current.User = new GenericPrincipal(appIdentity, authRoles);
        }
        catch
        {
            // tokenHandler.ValidateToken will throw exception if token is invalid
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden));
        }
        return continuation();
    }
    #endregion
}

And I connect that API to my new auth server. So that I want both validations to work together temporarily. I’ve found such a sample https://github.com/openiddict/openiddict-samples/blob/0cc47dedec5be7cf50c31b548fda82621063af1a/samples/Zirku/Zirku.Api2/Program.cs#L26 But I suppose that will only do a key validation, doesn’t it?

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:6 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
kevinchaletcommented, Jun 26, 2022

You can use the events model for that:

public static class CustomEventHandlers
{
    public class ExtractAccessTokenFromNonStandardQueryString : IOpenIddictValidationHandler<ProcessAuthenticationContext>
    {
        public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
            = OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
                .AddFilter<RequireOwinRequest>()
                .AddFilter<RequireAccessTokenExtracted>()
                .UseSingletonHandler<ExtractAccessTokenFromNonStandardQueryString>()
                .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1)
                .SetType(OpenIddictValidationHandlerType.Custom)
                .Build();

        /// <inheritdoc/>
        public ValueTask HandleAsync(ProcessAuthenticationContext context)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            // If a token was already resolved, don't overwrite it.
            if (!string.IsNullOrEmpty(context.AccessToken))
            {
                return default;
            }

            var request = context.Transaction.GetOwinRequest() ?? throw new InvalidOperationException();

            // Resolve the access token from non-standard query parameters.
            context.AccessToken = request.Query["token"] ?? request.Query["bearer"];

            return default;
        }
    }

    public class ValidateCustomJsonWebToken : IOpenIddictValidationHandler<ValidateTokenContext>
    {
        public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
            = OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
                .AddFilter<RequireOwinRequest>()
                .UseSingletonHandler<ValidateCustomJsonWebToken>()
                .SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500)
                .SetType(OpenIddictValidationHandlerType.Custom)
                .Build();

        /// <inheritdoc/>
        public ValueTask HandleAsync(ValidateTokenContext context)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            // If a principal was already attached, don't overwrite it.
            if (context.Principal is not null)
            {
                return default;
            }

            // Actual authentication
            byte[] audienceSecret = Convert.FromBase64String(AppSetting.AuthAudienceSecret);
            var tokenHandler = new JsonWebTokenHandler();

            var validationParameters = new TokenValidationParameters
            {
                IssuerSigningKeys = new SymmetricKeyIssuerSecurityKeyProvider(AppSetting.AuthIssuer, audienceSecret).SecurityKeys,
                ValidIssuer = AppSetting.AuthIssuer,
                ValidAudience = AppSetting.AuthAudienceId,
                ValidateIssuerSigningKey = true,
                ValidateIssuer = true,
                ValidateAudience = true,
            };

            var result = tokenHandler.ValidateToken(context.Token, validationParameters);
            if (result is not { IsValid: true })
            {
                // Ignore the token validation errors to give the other handlers a chance to validate the token.

                return default;
            }

            // Attach the principal and let OpenIddict know it's an access token.
            context.Principal = new ClaimsPrincipal(result.ClaimsIdentity).SetTokenType(TokenTypeHints.AccessToken);

            return default;
        }
    }
}
services.AddOpenIddict()
    .AddValidation(options =>
    {
        // Note: the validation handler uses OpenID Connect discovery
        // to retrieve the address of the introspection endpoint.
        options.SetIssuer("https://localhost:44395/"); // AuthenticationServerUrl
        options.AddAudiences("API", AppSetting.AuthAudienceId);

        // Configure the validation handler to use introspection and register the client
        // credentials used when communicating with the remote introspection endpoint.
        options.UseIntrospection()
            .SetClientId("API") // AuthenticationServerClientId
            .SetClientSecret("[your secret]"); // AuthenticationServerClientSecret

        // Register the System.Net.Http integration.
        options.UseSystemNetHttp();

        // Register the Owin host.
        options.UseOwin();

        options.AddEventHandler(CustomEventHandlers.ExtractAccessTokenFromNonStandardQueryString.Descriptor);
        options.AddEventHandler(CustomEventHandlers.ValidateCustomJsonWebToken.Descriptor);
    });
0reactions
xperiandricommented, Jul 1, 2022

Yahoo!!! 🎉🎉🎉 Works!

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to trigger javax validations manually via reflection or ...
Yes, it is possible to invoke validation programmatically and in case of validation failures you will receive all failure messages in a set....
Read more >
Input validation in GoLang
Having used GoLang for 2+ years now, I am constantly being asked how to do input validation in Go so I decided to...
Read more >
Validation with Hibernate Validator
This guide covers how to use Hibernate Validator/Bean Validation for: ... If a validation error is triggered, a violation report is generated and...
Read more >
Flash message after $this->validate()
So you could use a custom validator, and call the validate() method after flashing your message as intended. For example: Copy public function...
Read more >
Validation and Error Handling
The error handling support works end-to-end where all errors get auto-serialized into your Response DTO and re-hydrated into a C# Exception on ServiceStack's ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found