Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#/OWIN/ASP.NET: can I *manually* generate and get a valid bearer token string in my API code?

I am using the OWIN OAuthAuthorizationServer library in an OWIN ASP.NET C# web API to generate and process bearer tokens.

Right now, I have a single endpoint (which you set in the OAuthAuthorizationServerOptions struct) that accepts the grant_type, username and password fields from the frontend. I created a provider class that performs the validation, and then calls context.Validated() or context.SetError() accordingly. The middleware then handles generating the token and returning it to the user, and also "takes over" the login endpoint, doing all the work internally.

Now, I am adding a new feature to my API where the user can change their "role" (e.g. an admin can set themselves as a regular user to view the results of their work, a user can select among multiple roles, etc.) Since I already handle this through the bearer token (I store the user's role there and all my endpoints use the bearer token to determine the current role), I now have a reason to update the contents of the bearer token from the API backend.

What I'm looking to do is to allow the frontend to call an endpoint (e.g. api/set_role) that will accept a parameter. The user requests a certain role, and their current bearer token would accompany the request. The server then would check if the user in question is allowed to use that specific role and, if so, would generate a new token and return it to the user in the response body. The frontend would then update its token in local storage. Or, of course, if the user is not permitted to switch to that role, the backend would return an appropriate error and the frontend would react accordingly.

To do this I basically want to be able to manually generate a token. Similar to how I use identity.AddClaim() in my login provider, I'd like to be able to do that at any arbitrary position within the API's code. The method would take responsibility for transferring over any necessary existing information (e.g. the user's username) into the new token, since it already has the existing one.

Pseudocode for what I want:

if (!userCanUseRole(requestedRoleId)) return Request.CreateErrorResponse(...);
// we have a struct containing parsed information for the current token in the variable cToken
bearerToken newToken = new bearerToken();
newToken.AddClaim(new Claim("user", cToken.user));
newToken.AddClaim(new Claim("role", requestedRoleId));
string tokenToReturnToFrontend = newToken.getTokenString(); // string suitable for using in Authorization Bearer header
return Request.CreateResponse(new StringContent(tokenToReturnToFrontend));

I am not too familiar with "refresh" tokens, but the only way I am using them right now is extending token expiration. To that end the frontend explicitly requests a refresh token and provides its own, which the backend simply copies to a new token and edits the expiry time. The problem with this is that there's a single method for getting a refresh token, and since I have now at least one other reason to refresh a token (and possibly, future developments could add even more reasons to change token contents at various times), I'd then have to deal with storing transient data somewhere (E.g. "when requesting a refresh token, what is the thing the user wanted to do? has it been too long since they requested to do that? etc.) It'd be much easier if I could simply generate a bearer token on demand in the same way that the OAuthAuthorizationServer itself does. (I know it uses the MachineKey to do this, but I don't know exactly how it does it, nor how I would go about doing what I'm trying to do.)

Of note: In another project I provided internal access to the OAuthBearerAuthenticationOptions class that is passed to the authorization server instance, and was able to use that to decode a bearer token inside of a test. I haven't seen anything obvious thought that would let me encode a bearer token this way.


EDIT: I explored the (extremely tersely, almost uselessly documented) OWIN namespace and found the AccessTokenFormat class which appears that it should do what I want. I wrote this code:

Microsoft.Owin.Security.AuthenticationTicket at = new Microsoft.Owin.Security.AuthenticationTicket(new ClaimsIdentity
{
    Label="claims"
}
, new Microsoft.Owin.Security.AuthenticationProperties
{
    AllowRefresh=true,
    IsPersistent=true,
    IssuedUtc=DateTime.UtcNow,
    ExpiresUtc=DateTime.UtcNow.AddMinutes(5),
});

at.Identity.AddClaim(new Claim("hello", "world"));
string token = Startup.oabao.AccessTokenFormat.Protect(at);

return Request.CreateResponse(HttpStatusCode.OK, new StringContent(token, System.Text.Encoding.ASCII, "text/plain"));

which seems like it should work. (I again allow access to the OAuthBearerAuthenticationOptions class passed to the OAuthAuthorizationServer instance.) However, this code throws an ArgumentNull exception. The stacktrace indicates that it is writing to a BinaryWriter but the OWIN code is passing a null value to the Write method on the BinaryWriter.

Still have no solution.

like image 801
fdmillion Avatar asked Mar 09 '23 16:03

fdmillion


1 Answers

I did figure out the code to make this work. One could argue I'm "not using OAuth right", but strictly, this code WILL accomplish what I want - to generate a token in code at any arbitrary point and get the string.

First, as I said, I have to provide access to the OAuthBearerAuthenticationOptions class instance. When the OAuth server initializes I'm guessing it populates this class with all of the various objects used for tokens. The key is that we do have access to Protect and Unprotect which can both encode and decode bearer tokens directly.

This code will generate a token assuming that oabao is the OAuthBearerAuthenticationOptions class that has been passed to the OAuthAuthorizationServer instance:

Microsoft.Owin.Security.AuthenticationTicket at = new Microsoft.Owin.Security.AuthenticationTicket(new ClaimsIdentity("Bearer", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"),
    new Microsoft.Owin.Security.AuthenticationProperties
{
    AllowRefresh = true,
    IsPersistent = true,
    IssuedUtc = DateTime.UtcNow,
    ExpiresUtc = DateTime.UtcNow.AddDays(1) // whenever you want your new token's expiration to happen
});

// add any claims you want here like this:
at.Identity.AddClaim(new Claim("userRole", role));
// and so on

string token = oabao.AccessTokenFormat.Protect(at);

// You now have the token string in the token variable.
like image 106
fdmillion Avatar answered Apr 06 '23 00:04

fdmillion