Why GrantRefreshToken method is not called - Oauth2 ASP.NET Web API - asp.net-web-api

I send request to oauth2 server with request body :
grant_type=refresh_token&refresh_token=abc
I save refresh_token in database.
ReceiveAsync method :
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("clientAllowedOrigin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
string hashTokenName = Helper.GetHash(context.Token);
var refreshToken = await repo.FindRefreshToken(hashTokenName);
if (refreshToken != null)
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
var result = await repo.RemoveRefreshToken(hashTokenName);
}
}
GrantRefreshToken method :
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originClient = context.Ticket.Properties.Dictionary["client_id"];
var currenClient = context.ClientId;
if (originClient != currenClient)
{
context.SetError("Error");
return Task.FromResult<object>(null);
}
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
newIdentity.AddClaim(new Claim(ClaimTypes.Name, context.ClientId));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
Why GrantRefreshToken is not called when ReceiveAsync finished ?

I had been facing the same problem for two days.
the issue causes by ReceiveAsync method does not set OAuthGrantRefreshTokenContext context.ticket
properly.
to diagnose that, use postman to send the refresh_token request. an invalid_grant error will be prompted.
the minimum requirement for generate a ticket is to set IssuedUtc & ExpiresUtc and leave other properties to default.
the code below can simply replicate the issue.
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket = null;
var identity = new ClaimsIdentity();
var props = new AuthenticationProperties();
ticket = new AuthenticationTicket(identity, props);
context.SetTicket(ticket);
//context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
//context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow;
}
once IssuedUtc & ExpiresUtc are set, GrantRefreshToken will be
invoked.
solution: put a breakpoint by the end of ReceiveAsync method, check
whether context.ticket is set properly.
IssuedUtc & ExpiresUtc these two properties can not be null.

ReceiveAsync signature should be: public Task ReceiveAsync(AuthenticationTokenReceiveContext context)
And you should return a Task in that method.

I've faced the exact same problem, and the root cause in my case was the machine key, the engine use the machine key in serializing the ticket, so if you don't configure the machine key in web.config then one key could be used in the serialization process and another one in the deserialization.
So try to configure the machine key in the web.cofig. Hope this helps

Related

invalid_grant of OAuthAuthorizationServerProvider

I'm working on writing fully customized ASP.NET Identity for my WebAPi.
I have rewritten my own derived OAuthAuthorizationServerProvider in this way:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Check User availability ...
//ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
// if i couldn't found user in my DataBase ...
//if (user == null)
//{
//context.SetError("invalid_grant", "The user name or password is incorrect.");
// return;
//}
context.Validated();
}
}
GrantResourceOwnerCredentials just returns an invalid_grant error for each calls. i want to handle it but, i don't know how.
ValidateClientAuthentication is where you would do your authentication checks and this is where you throw errors if anything doesn't match.
move your code there and do the checks before you call context.Validated(). You only call the Validate method once you make sure everything is validated correctly.
here is an example of such an implementation I did a while back:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
//first try to get the client details from the Authorization Basic header
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
//no details in the Authorization Header so try to find matching post values
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("client_not_authorized", "invalid client details");
return Task.FromResult<object>(null);
}
var dataLayer = new RepoManager(new DataLayerDapper()).DataLayer;
var audienceDto = dataLayer.GetAudience(clientId);
if (audienceDto == null || !clientSecret.Equals(audienceDto.Secret))
{
context.SetError("unauthorized_client", "unauthorized client");
return Task.FromResult<object>(null);
}
context.Validated();
return Task.FromResult<object>(null);
}
Notice how the checks happen in order and certain errors are raised with some appropriate errors.
This code takes a client id and client secret from an authorization header but you can easily drop all that and replace it with your own checks and database calls.
The important part is that this is where you deal with stuff like this and this is where you set the errors so your clients know what's going on.
GrantResourceOwnerCredentials this is where you get once the call is properly authenticated, at which point you can start creating tokens, adding claims and creating the authentication ticket. This method does not get hit if the previous one fails to authenticate the request.
Here is a working example:
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim("clientID", context.ClientId));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"audience", context.ClientId
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
return Task.FromResult<object>(null);
}
Now, if you get an invalid grant error that usually happens because you either didn't set up the grant_type in your initial call or you set up the wrong value.
in my case I had to setup this:
"grant_type", "password"

Generate Access Token In Web Api action method using OWIN and IIS host

I'm trying to generate a token inside Web Api action method based on the code below:
private JObject GeneratePaymentTokenResponse(string email, bool rememberMe)
{
//var tokenExpiration = rememberMe ? TimeSpan.FromDays(14) : TimeSpan.FromMinutes(30);
var tokenExpiration = rememberMe ? TimeSpan.FromMinutes(30) : TimeSpan.FromMinutes(5);
ClaimsIdentity identity = new ClaimsIdentity("CustomType", ClaimTypes.Email, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.Email, email));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration)
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("email", email),
new JProperty("customToken", accessToken),
new JProperty("expiresIn", tokenExpiration.TotalSeconds),
new JProperty("issuedUtc", ticket.Properties.IssuedUtc),
new JProperty("expiresUtc", ticket.Properties.ExpiresUtc)
);
return tokenResponse;
}
The OAuthBeaerOptions object is coming from the Startup class as the below:
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
// Token Consumption (Resource Server)
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
Now when I try to pass a valid access token but has been expired and call AccessTokenFormat.Unprotect as the code below
Microsoft.Owin.Security.AuthenticationTicket ticket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(paymentToken);
if ((ticket == null) || (!ticket.Identity.IsAuthenticated))
{
actionContext.Response = CreateForbiddenResponse(actionContext);
return Task.FromResult<object>(null);
}
I'm receiving a valid ticket and the value of ticket.Identity.IsAuthenticated is true even that token is expired.
Currently I'm using the latest version (3.0.1) of Microsoft.Owin.Security assembly
I would appreciate any clue on how to set the expiry date for this token?
I'm receiving a valid ticket and the value of ticket.Identity.IsAuthenticated is true even that token is expired.
That's totally intended: Unprotect will return a ticket with a valid ClaimsIdentity even if it is expired. Since ClaimsIdentity.IsAuthenticated only ensures the ClaimsIdentity.AuthenticationType property is not null, it's not a reliable way to ensure the ticket is not expired.
Actually, it's up to you to determine whether the ticket is still valid and return an error if necessary (which is exactly what the bearer middleware does internally when receiving an access token: https://github.com/jchannon/katanaproject/blob/master/src/Microsoft.Owin.Security.OAuth/OAuthBearerAuthenticationHandler.cs#L68-L73)
if (ticket.Properties.ExpiresUtc.HasValue &&
ticket.Properties.ExpiresUtc.Value < DateTimeOffset.Now)
{
return Task.FromResult<object>(null);
}

How to get user context during Web Api calls?

I have an web front end calling an ASP Web Api 2 backend. Authentication is managed with ASP Identity. For some of the controllers I'm creating I need to know the user making the call. I don't want to have to create some weird model to pass in including the user's identity (which I don't even store in the client).
All calls to the API are authorized using a bearer token, my thought is the controller should be able to determine the user context based on this but I do not know how to implement. I have searched but I don't know what I'm searching for exactly and haven't found anything relevant. I'm going for something like...
public async Task<IHttpActionResult> Post(ApplicationIdentity identity, WalkthroughModel data)
Update
I found the below which looked very promising... but the value is always null! My controller inherits from ApiController and has an Authorize header.
var userid = User.Identity.GetUserId();
Update 2
I have also tried all of the solutions in Get the current user, within an ApiController action, without passing the userID as a parameter but none work. No matter what I am getting an Identity that is valid and auth'd, but has a null UserID
Update 3
Here's where I'm at now.
[Authorize]
[Route("Email")]
public async Task<IHttpActionResult> Get()
{
var testa = User.Identity.GetType();
var testb = User.Identity.GetUserId();
var testc = User.Identity.AuthenticationType;
var testd = User.Identity.IsAuthenticated;
return Ok();
}
testa = Name: ClaimsIdentity,
testb = null,
testc = Bearer,
testd = true
The user is obviously authenticated but I am unable to retrieve their userID.
Update 4
I found an answer, but I'm really unhappy with it...
ClaimsIdentity identity = (ClaimsIdentity)User.Identity;
string username = identity.Claims.First().Value;
That gets me the username without any db calls but it seems very janky and a pain to support in the future. Would love if anyone had a better answer.
What if I need to change what claims are issued down the road? Plus any time I actually need the user's id I have to make a db call to convert username to ID
A common approach is to create a base class for your ApiControllers and take advantage of the ApplicationUserManager to retrieve the information you need. With this approach, you can keep the logic for accessing the user's information in one location and reuse it across your controllers.
public class BaseApiController : ApiController
{
private ApplicationUser _member;
public ApplicationUserManager UserManager
{
get { return HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
}
public string UserIdentityId
{
get
{
var user = UserManager.FindByName(User.Identity.Name);
return user.Id;
}
}
public ApplicationUser UserRecord
{
get
{
if (_member != null)
{
return _member ;
}
_member = UserManager.FindByEmail(Thread.CurrentPrincipal.Identity.Name);
return _member ;
}
set { _member = value; }
}
}
I use a custom user authentication (I dont use AspIdentity because my existing user table fields was far different from IdentityUser properties) and create ClaimsIdentity passing my table UserID and UserName to validate my bearer token on API calls.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
User user;
try
{
var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
_service = scope.Resolve<IUserService>();
user = await _service.FindUserAsync(context.UserName);
if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
catch (Exception ex)
{
context.SetError("invalid_grant", ex.Message);
return;
}
var properties = new Dictionary<string, string>()
{
{ ClaimTypes.NameIdentifier, user.UserID.ToString() },
{ ClaimTypes.Name, context.UserName }
};
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
properties.ToList().ForEach(c => identity.AddClaim(new Claim(c.Key, c.Value)));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties(properties));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(identity);
}
And how I use the ClaimsIdentity to retrieve my User table details on User ApiController Details call.
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("Details")]
public async Task<IHttpActionResult> Details()
{
var user = await _service.GetAsync(RequestContext.Principal.Identity.GetUserId<int>());
var basicDetails = Mapper.Map<User, BasicUserModel>(user);
return Ok(basicDetails);
}
Notice the
ClaimTypes.NameIdentifier = GetUserId() and ClaimTypes.Name = GetUserName()

Update: Passing HttpContext and IPrincipal to Controler in ReSharper unit test

I'm attempting to run a couple basic unit tests on an ASP MVC controller, however at one point the controller needs to examine the IPrincipal User object like so:
ViewBag.Level = Security.GetLevel(User);
Once the unit test enters the Create controller method, however, the above line throws a NullReferenceException as the User object is null.
Any ideas on how to set an IPrincipal for a unit test session?
Here's the test as I have it written right now. I attempted to access the User object and simply set it before the test goes into the Create method, however the intellisense isn't picking it up.
[Test]
public void a06_CloneFromDatabase()
{
using (AgentResources db = new AgentResources())
{
var master = (from a in db.AgentTransmission
where a.RecordStatus.Equals("C")
select a).FirstOrDefault();
var result = _controlAgtTran.Create(master.ID, null, string.Empty, string.Empty, string.Empty, string.Empty, false) as ViewResult;
var model = result.Model as AgentTransmission;
Assert.AreEqual(model.ReferenceType, master.ReferenceType);
}
}
EDIT
Following the post mentioned in the comments below I found a method to create a HttpContext session and apply and IPrincipal to that session. This works fine until the unit test moves into the controller where the HttpContext and IPrincipal User objects are all, once again, null.
Since it seems the instance of the controller I'm using has it's HttpContext property as read only (and the IPrincipal User property as well) does anyone know of a way to pass the HttpContext being used in the unit test inside the controller being tested? Also, if this is not possible, what is a usable method for testing RouteValues using ReSharper's unit tests?
[SetUp]
public void SetUp()
{
_controlAgtTran = new AgentTransmissionController();
/****Set up Current HTTP Context to pass Security.cs checks*****/
//Set up the HTTP Request
var httpRequest = new HttpRequest("", "http://localhost:2574/", "");
//Set up the HTTP Response
var httpResponse = new HttpResponse(new StringWriter());
//Set up the HTTP Context
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer("NEAROD",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
100,
true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc,
false);
httpContext.Items["AspSession"] =
typeof (HttpSessionState)
.GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
CallingConventions.Standard,
new[] {typeof (HttpSessionStateContainer)},
null)
.Invoke(new object[] {sessionContainer});
//Assign the context
HttpContext.Current = httpContext;
}
[Test]
public void a01_IncompleteRecordGoesToEdit()
{
AgentTransmission agtTran = new AgentTransmission();
agtTran.ReferenceNumber = 95820518787;
agtTran.ReferenceType = "S";
agtTran.EffectiveDate = DateTime.Now;
agtTran.RelationshipEffDate = DateTime.Now;
agtTran.RecordStatus = "N";
agtTran.CreatedDate = DateTime.Now;
agtTran.CreatedOperator = "xTest1";
agtTran.FirstName = "Unit";
agtTran.LastName = "Test";
agtTran.ExtRepType = "EXTREPID";
agtTran.JIT = true;
agtTran.SendToDRM = true;
agtTran.SendToNMF = true;
agtTran.WelcomeLetter = true;
agtTran.OverrideRegionInd = false;
//set IPrincipal
string[] roles = {"LCO"};
IPrincipal principal = new GenericPrincipal(new GenericIdentity("SYMETRA\\NEAROD"), roles);
HttpContext.Current.User = principal;
IPrincipal user = HttpContext.Current.User;
Assert.AreEqual(user, principal); //This passes
Assert.AreEqual(principal, _controlAgtTran.User); //this fails
var result = (RedirectToRouteResult)_controlAgtTran.Create(agtTran); //this crashes
//Tests aren't run
Assert.IsNotNull(result);
Assert.AreEqual(3, result.RouteValues.Count);
Assert.AreEqual("AgentTransmission", result.RouteValues["controller"]);
Assert.AreEqual("Edit", result.RouteValues["action"]);
}
Following a similar solution mentioned in this post I added the following to the end of the SetUp() method.
var controllerCtx = new ControllerContext();
controllerCtx.HttpContext = new HttpContextWrapper(HttpContext.Current);
_controlAgtTran.ControllerContext = controllerCtx;
Wrapping the current HttpContext inside an HttpContextBase property (the inappropriately named controllerCtx.HttpContext) the test now has access to the User and HttpContext properties of the controller. These properties were previously read-only when using just the HttpContext.Current session and therefore always null.
FYI - this is my first time unit testing with these objects so that explanation may be less than 100% correct. Please feel free to comment below and I'll make any necessary changes.

How to create roles and add users to roles in ASP.NET MVC Web API

I have a .NET Web API project that users the individual accounts. I can register users fine using the standard template AccountController. However, I now want to set up roles and add users to roles depending on the type of user.
There are no roles automatically set up in the DB. How do I set up the roles and how do I add users to the roles?
The only information I can find on this is based on the old ASP.NET Membership, so it fails on the fact that the stored procedures are not set up for it.
Have scoured forums and tutorials on MSDN and can't seem to find an example for Web API.
You can add roles using the RoleManager...
using (var context = new ApplicationDbContext())
{
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
await roleManager.CreateAsync(new IdentityRole { Name = "Administrator" });
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
var user = new ApplicationUser { UserName = "admin" };
await userManager.CreateAsync(user);
await userManager.AddToRoleAsync(user.Id, "Administrator");
}
You're right that documentation is a bit light right now. But I find that once you've worked with the RoleManager and the UserManager a bit, the API's are pretty discoverable (but perhaps not always intuitive and sometimes you have to run queries directly against the store or even the db context).
It took me awhile to figure out but I finally got it. Anthony please excuse me but going to repost a lot of your code so that dumb developers like me can understand.
In the latest WebAPI2 (Visual Studio 2013 Update 2) the registration method will look like so:
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
What you want to do is replace it with this:
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result;
using (var context = new ApplicationDbContext())
{
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
await roleManager.CreateAsync(new IdentityRole() { Name = "Admin" });
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
result = await UserManager.CreateAsync(user, model.Password);
await userManager.AddToRoleAsync(user.Id, "Admin");
}
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
Now when you post it should correctly work, but you may run into a further problem. After I did this my response complained about the DB.
The model backing the <Database> context has changed since the database was created
To fix this error I had to go into the Package Manager Console and enable Migrations.
Enable-Migrations –EnableAutomaticMigrations
Then:
Add Migration
Finally:
Update-Database
A good post on enabling migrations here:
http://msdn.microsoft.com/en-us/data/jj554735.aspx

Resources