Zapnologica
Zapnologica

Reputation: 22556

Authorize attribute is ignoring [AllowAnonymous]

I have set-up a customer Authorization Attribute in my asp.net web api

global file:

  FilterConfig.RegisterHttpFilters(GlobalConfiguration.Configuration.Filters);

FilterConfig.cs

public static void RegisterHttpFilters(System.Web.Http.Filters.HttpFilterCollection filters)
    {
        filters.Add(new TokenAuthentication(""));
    }

Authentication Class:

  public class TokenAuthentication : Attribute, IAuthenticationFilter
{
    private readonly string realm;

    public bool AllowMultiple { get { return false; } }

    public TokenAuthentication(string realm)
    {
        this.realm = "realm=" + realm;
    }      

    public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        var request = context.Request;

        // Receive token from the client. Here is the example when token is in header:
        string token = null;

        if (request.Headers.Contains("Token"))
        {
            token = request.Headers.GetValues("Token").FirstOrDefault();
        }

        if (token != null && token != "")
        {
            // Get your secret key from the configuration
            var secretKey = ConfigurationManager.AppSettings["JWTSecurityKey"];

            try
            {
                //Get the Payload from the token
                string jsonPayload = JWT.JsonWebToken.Decode(token, secretKey);

                int separatorIndex = jsonPayload.IndexOf(';');
                string userId = "";
                DateTime timeIssued = DateTime.MinValue;
                if (separatorIndex >= 0)
                {
                    //userId = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(jsonPayload.Substring(0, separatorIndex)));
                    userId = jsonPayload.Substring(1, separatorIndex - 1);
                    string tmpTime = jsonPayload.Substring(separatorIndex + 1, (jsonPayload.Length - separatorIndex) - 2);
                    timeIssued = DateTime.Parse(tmpTime);
                }

                short TokenTTL = 10;                //minuets
                //Int16.TryParse(ConfigurationManager.AppSettings["TokenTTL"],TokenTTL);               

                // if ((DateTime.Now.Subtract(timeIssued).TotalMinutes >= TokenTTL))
                if ((DateTime.Now.Subtract(timeIssued).TotalMinutes < 0))
                {
                    context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
                }
                else
                {
                    //Save user in context                
                    var claims = new List<Claim>()
                    {
                        new Claim(ClaimTypes.Name, userId)
                    };
                    var id = new ClaimsIdentity(claims, "Basic");
                    var principal = new ClaimsPrincipal(new[] { id });

                    // Set the user name to the user id in the httpcontext which is passed to the controller
                    context.Principal = principal;
                }

            }
            catch (JWT.SignatureVerificationException)
            {
                context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
            }
        }
        else
        {
            return Task.FromResult(0);
        }
        return Task.FromResult(0);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="context"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
    {
        context.Result = new ResultWithChallenge(context.Result, realm);
        return Task.FromResult(0);
    }
}

/// <summary>
/// 
/// </summary>
public class ResultWithChallenge : IHttpActionResult
{
    private readonly IHttpActionResult next;
    private readonly string realm;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="next"></param>
    /// <param name="realm"></param>
    public ResultWithChallenge(IHttpActionResult next, string realm)
    {
        this.next = next;
        this.realm = realm;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var res = await next.ExecuteAsync(cancellationToken);
        if (res.StatusCode == HttpStatusCode.Unauthorized)
        {
            res.Headers.WwwAuthenticate.Add(
               new AuthenticationHeaderValue("Basic", this.realm));
        }
        return res;
    }
}

Now my issue is this class is being called even when the [AllowAnonymous] attribute is applied to my Controllers Action.

This is giving me trouble specifically with the login action as the user does not have a token yet.

How can I rectify this?

Upvotes: 3

Views: 4078

Answers (1)

AllowAnonymous will work only with authorization filter. What you have here is an authentication filter. Use OverrideAuthentication from System.Web.Http instead.

UPDATE

Where in my original answer did I say you must implement authentication? All I have said is, if you do not want the AuthenticateAsync method of the TokenAuthentication to be called, apply OverrideAuthentication, instead of AllowAnonymous. AllowAnonymous will work only with authroization filters and not authentication filters. So, in your Login action, you will need to have OverrideAuthentication applied like this.

public class SomeController : ApiController
{
    [OverrideAuthentication]
    public HttpResponeMessage Login(Dto dto)
    { }
}

Upvotes: 5

Related Questions