mngeek206
mngeek206

Reputation: 5157

C# ASP.NET: How to automatically decode my bearer token for each method?

I have a Web API that uses bearer tokens for authentication and authorization. The presence of a token signifies that the user is authenticated; the claims within that token specify the authorizations the user has.

I wrote an OAuth class that queries the database upon user login to determine the user's permissions. All of this works fine.

Now, for each method in each controller, I need to extract information from the bearer token's claims. What I have done now is to define a class that contains the entities I need from the token and write a static method to take a User object from within a controller method and produce an instance of my class containing the token data:

// Bearer token class
class TokenData 
{
    public string UserId { get; set; }
    public int GroupId { get; set; }
    public string UserTag { get; set; }
    // ... more properties as needed
}

// Method to get bearer token data from user object
internal static TokenData getTokenDataFromUserPrincipal(System.Security.Principal.IPrincipal p)
{
    var identity = (ClaimsIdentity)p.Identity;
    IEnumerable<Claim> claims = identity.Claims;

    var enumerable = claims as Claim[] ?? claims.ToArray();
    var claimTypes = from x in enumerable select x.Type;

    var types = claimTypes as string[] ?? claimTypes.ToArray();
    if (!types.Contains("userId")) return null; // if a token was given but it has no UserID it's not valid.

    var claimsByType = enumerable.ToDictionary(x => x.Type, x => x.Value);

    TokenData td = new TokenData
    {
        UserId = claimsByType["userId"],
        GroupId = types.Contains("groupId") ? int.Parse(claimsByType["groupId"]) : 0,
        UserTag = types.Contains("userTag") ? claimsByType["userTag"]) : null,
        // more properies as needed
    };
    return td;
}

// A controller method that uses the claims
public HttpResponseMessage DoSomething() 
{
    TokenData td = getTokenDataFromUserPrincipal(User);
    // Now I can access the token data in the td variable.
    return Request.CreateResponse(td.UserId);
}

Ok, so this all works perfectly. The thing I'm looking for though is if there's a way to automate pulling the claims from the User object whenever a controller method is invoked.

The Web API architecture already basically does this with the User object itself - it contains any information that is part of the request related to the user. (Similar to the Request object, which contains all of the HTTP request data.)

What I'd like to be able to do is, instead of calling getTokenDataFromUserPrincipal at the start of each controller, to instead just have a static variable available similar to how Web API does it - e.g. TokenData. For example:

// A controller method that uses the claims and doesn't have to explicitly retrieve them.
public HttpResponseMessage DoSomething() 
{
    return Request.CreateResponse(TokenData.UserId);
}

I do know how to use attributes - I actually wrote custom attributes which pull the same token data and use it to determine if a user can access a certain class of function - so for example I can just include [MinimumUserLevel(2)] in front of my controller methods. But having to add an attribute to each method simply moves the problem outside of the method.

To sum: Is it possible to have another static variable, scoped at the request level, that user code can populate per request without having to copy code to the beginning of each controller method?

Ideally, there would be a way to insert a function in the pipeline, so that prior to the controller method being run, I can run the code to get the token data from the principal, so it will be ready when the controller method runs. (Note that the method to pull token data simply returns null if the data doesn't exist - this is the expected behavior for this static variable in the instance of a call with no token.)

Upvotes: 1

Views: 2884

Answers (2)

mngeek206
mngeek206

Reputation: 5157

The solution to this turned out to be simpler than I was making it: define a subclass of ApiController.

So I simply wrote a class that looks like this:

public class MyApiController : ApiController 
{
    // Makes token data available to any method on a class deriving from MyApiController
    public TokenData Token { get; set; }

    // Override constructor - parse token whenever instance is created
    public MyApiController() 
    {
        // Extract the token data from the User variable (User is pulled in from the base ApiController)
        Token = getTokenDataFromUserPrincipal(User);
    }

    private static TokenData getTokenDataFromUserPrincipal(System.Security.Principal.IPrincipal p)
    {
        ...
    }
}

Now I have all of my controllers derive from MyApiController rather than from ApiController. From that point, I'm able to access the Token variable in any controller's logic, which will contain the parsed token data.

Upvotes: 0

Stephan
Stephan

Reputation: 2580

You can either manually verify the jwt token. Or add the JwtBearerAuthentication to the Owin stack.

Here is one of the found samples. This would parse the token for each request and set the User properties of each request.

You would have to set it up for the secret you were using when you created the jwt token.

The provided sample validates and converts the JWT token to a ClaimsIdentity which is set on each request and is accessible through the User.Identity property within each controller. No custom code is necessary for that.

And since this package is provided by Microsoft, it might be better then some self made JWT parser.

(User.Identity as ClaimsIdentity).FindFirst("userId")?.Value to access your user ID.

You could also extend the IIdentity with the following code.

public static class IIdentityExtensions
{
    public static string UserId(this IIdentity identity)
    {
        return identity.GetClaimValue("userId");
    }

    public static string GetClaimValue(this IIdentity identity, string claimType)
    {
        return identity
            .AsClaimsIdentity()
            .Claims.FirstOrDefault(c => c.Type == claimType)?.Value;
    }

    private static ClaimsIdentity AsClaimsIdentity(this IIdentity identity)
    {
        if (!(identity?.IsAuthenticated ?? false))
            throw new UnauthorizedAccessException("User not logged-in");

        return identity as ClaimsIdentity;
    }
}

And then accessing the user id like User.Identity.UserId()

Upvotes: 0

Related Questions