Return more info to the client using OAuth Bearer Tokens Generation and Owin in WebApi

49,311

Solution 1

You can add as many claims as you want.
You can add the standard set of claims from System.Security.Claims or create your own.
Claims will be encrypted in your token so they will only be accessed from the resource server.

If you want your client to be able to read extended properties of your token you have another option: AuthenticationProperties.

Let's say you want to add something so that your client can have access to. That's the way to go:

var props = new AuthenticationProperties(new Dictionary<string, string>
{
    { 
        "surname", "Smith"
    },
    { 
        "age", "20"
    },
    { 
    "gender", "Male"
    }
});

Now you can create a ticket with the properties you've added above:

var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);

That's the result your client will fetch:

.expires: "Tue, 14 Oct 2014 20:42:52 GMT"
.issued: "Tue, 14 Oct 2014 20:12:52 GMT"
access_token: "blahblahblah"
expires_in: 1799
age: "20"
gender: "Male"
surname: "Smith"
token_type: "bearer"

On the other hand if you add claims you will be able to read them in your resource server in your API controller:

public IHttpActionResult Get()
{
    ClaimsPrincipal principal = Request.GetRequestContext().Principal as ClaimsPrincipal;

    return Ok();
}

Your ClaimsPrincipal will contain your new claim's guid which you've added here:

identity.AddClaim(new Claim("guid", user.UserGuid.ToString()));

If you want to know more about owin, bearer tokens and web api there's a really good tutorial here and this article will help you to grasp all the concepts behind Authorization Server and Resource Server.

UPDATE:

You can find a working example here. This is a Web Api + Owin self-hosted.
There's no database involved here. The client is a console application (there's a html + JavaScript sample as well) which call a Web Api passing credentials.

As Taiseer suggested, you need to override TokenEndpoint:

public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
    foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
    {
        context.AdditionalResponseParameters.Add(property.Key, property.Value);
    }

    return Task.FromResult<object>(null);
}

Enable 'Multiple Startup Projects' from Solution -> Properties and you can run it straight away.

Solution 2

My recommendation is not to add extra claims to the token if not needed, because will increase the size of the token and you will keep sending it with each request. As LeftyX advised add them as properties but make sure you override TokenEndPoint method to get those properties as a response when you obtain the token successfully, without this end point the properties will not return in the response.

 public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    {
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
        {
            context.AdditionalResponseParameters.Add(property.Key, property.Value);
        }

        return Task.FromResult<object>(null);
    }

You can check my repo here for complete example. Hope it will help.

Share:
49,311
Ivan Stoyanov
Author by

Ivan Stoyanov

I am interested in software development.

Updated on November 14, 2021

Comments

  • Ivan Stoyanov
    Ivan Stoyanov over 2 years

    I have created a WebApi and a Cordova application. I am using HTTP requests to communicate between the Cordova application and the WebAPI. In the WebAPI, I've implemented OAuth Bearer Token Generation.

    public void ConfigureOAuth(IAppBuilder app)
        {
            var oAuthServerOptions = new OAuthAuthorizationServerOptions
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = new SimpleAuthorizationServerProvider(new UserService(new Repository<User>(new RabbitApiObjectContext()), new EncryptionService()))
            };
    
            // Token Generation
            app.UseOAuthAuthorizationServer(oAuthServerOptions);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    
        }
    

    and this is inside the SimpleAuthorizationServerProvider implementation

     public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
           context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
    
            // A little hack. context.UserName contains the email
            var user = await _userService.GetUserByEmailAndPassword(context.UserName, context.Password);
    
            if (user == null)
            {
                context.SetError("invalid_grant", "Wrong email or password.");
                return;
            }
    
            var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            identity.AddClaim(new Claim("sub", context.UserName));
            identity.AddClaim(new Claim("role", "user"));
    
            context.Validated(identity);
        }
    

    after a successful login request to the API from the Cordova app, I receive the following JSON

    {"access_token":"some token","token_type":"bearer","expires_in":86399}
    

    The problem is, that I require more information about the user. For example, I have a UserGuid field in the database and I want to send it to the Cordova app when the login is successful and to use it later in other requests. Can I include other information to return to the client, other than "access_token", "token_type" and "expires_in"? If not, how can I get the user in the API based on the access_token?


    EDIT:

    I think that I found a workaround. I added the following code inside GrantResourceOwnerCredentials

    identity.AddClaim(new Claim(ClaimTypes.Name, user.UserGuid.ToString()));
    

    and after that, I access the GUID inside my controller like this: User.Identity.Name

    I also can add the the guid with a custom name identity.AddClaim(new Claim("guid", user.UserGuid.ToString()));

    I'm still interested to know if there is a way to return more data to the client with the bearer token JSON.

  • Ivan Stoyanov
    Ivan Stoyanov over 9 years
    Thank you for the reply. When I wrote the API, I red the exact same article, I even downloaded the code and went trough it. So I tried using AuthenticationProperties, but the result was the same. On the client side I still receive only access_token, token_type and expires_in properties.
  • LeftyX
    LeftyX over 9 years
    I've tested my solution in that project and it works. Are you using a sniffer like fiddler to see what's going on?
  • Ivan Stoyanov
    Ivan Stoyanov over 9 years
    Yes. I am using Fiddler and I am also debugging the API, but the result is still the same.
  • Ers
    Ers about 9 years
    this will not work unless you override public override Task TokenEndpoint(OAuthTokenEndpointContext context) from Taiseer's answer
  • LeftyX
    LeftyX about 9 years
    @Ers: yes, you're right. forgot to mention that ... but it was in my repo. thanks for the down-vote.
  • pomber
    pomber almost 9 years
    @TaiseerJoudeh do you know if I can put an array of strings as one of the properties? I need to send a list of permissions
  • superjos
    superjos almost 8 years
    as of today, with ASOS for Asp.Net Core, things seems to have changed a little bit. context.Properties.Dictionary is context.Ticket.Properties.Items, but I cannot find context.AdditionalResponseParameters equivalent. Does anyone know about it?
  • superjos
    superjos almost 8 years
    Same comment as below: as of today, with ASOS for Asp.Net Core, things seems to have changed a little bit. context.Properties.Dictionary is context.Ticket.Properties.Items, but I cannot find context.AdditionalResponseParameters equivalent. Does anyone know about it?
  • superjos
    superjos almost 8 years
    Nevermind, got linked to this by ASOS author
  • superjos
    superjos almost 8 years
    Nevermind, got linked to this by ASOS author
  • nav
    nav almost 8 years
    For some reason my JSON that is returned doesn't include the ".expires" and ".issued" properties. Is there additional configuration that needs to be done? Thanks
  • LeftyX
    LeftyX almost 8 years
    @nav: It's hard to tell. It should work. Did you have the chance to run the code in my github repository? Cheers.
  • Jose Miguel Vega Lopez
    Jose Miguel Vega Lopez over 7 years
    @TaiseerJoudeh it is possible to include other user model atributes like user.birthday? The only user attribute that I see in context is context.username. ¿How can I include other user atributes?
  • AminM
    AminM about 7 years
    @TaiseerJoudeh how can i get name and family or other field of IdentityUser class?
  • Taiseer Joudeh
    Taiseer Joudeh about 7 years
    @AminM you can add them manually as a claim if you want identity.AddClaim(new Claim("Family", "Family name value"));
  • AminM
    AminM about 7 years
    @TaiseerJoudeh i solve it with: var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>()‌​; Member user= userManager.FindByEmail(context.Identity.Name); context.AdditionalResponseParameters.Add("NameOfUser", user.Name);
  • Yoan Pumar
    Yoan Pumar almost 6 years
    I've been sending information to the client in the way suggested by @LeftyX without any problem, but I'm now trying to add the profile picture to the AuthenticationProperties object as a base64 string and my application stopped working. It seems that the token generated is too large or something. This is the error I'm getting in my console after I login (angular.js:11048 GET localhost:3000/app/home/home.html net::ERR_CONNECTION_RESET). Have you guys had this issue before? Any help will be appreciated. Thanks :)
  • LeftyX
    LeftyX almost 6 years
    @YoanPumar: Never experienced anything like that but I would suggest to return a url with a path to the profile picture. You would take advantage of browser caching etc etc. Cheers.