Ignore expired tokens on certain routes - c#

Using JWT tokens, I was working on authentication for my .NET Core API. I was wondering if I can tell my API to ignore the expiry of my token when the user requests a certain route, like "/refresh" for example, to refresh expired tokens. I understand it can be worked around by supplying the tokens through a POST request, but I want the ability to ignore the expiry of a token when I only want to identify the calling user.
My code is done as below. Currently it works with a POST request but I want the ability to supply expired tokens through the header.
[AllowAnonymous]
[HttpPost("refresh")]
public async Task<IActionResult> Refresh(RefreshRequest refreshRequest)
{
// Validate refresh token
if (!_tokenService.RefreshTokenValid(refreshRequest.Refresh_Token)) {
return BadRequest("The request token is not a valid token.");
}
var tokenHandler = new JwtSecurityTokenHandler();
// Validate actual token
if (!tokenHandler.CanReadToken(refreshRequest.Token)) {
return BadRequest("The JWT token is not a valid token.");
}
// Get the user from the JWT token
var userId = _tokenService.GetUserIdFromtoken(refreshRequest.Token);
var repoUser = await _repo.GetUserById(userId);
// Map the user, generate a token and send response
var newToken = _tokenService.GenerateRefreshedJwtToken(repoUser);
var tokenUser = _mapper.Map(repoUser, new AuthRefreshedTokenUser(newToken));
return Ok(tokenUser);
}

A simple way is to create another authentication schema and set not to validate token's lifetime :
.AddJwtBearer(options =>
{
...
options.TokenValidationParameters.ValidateLifetime = false;
....
});
And apply that authentication scheme using [Authorize(AuthenticationSchemes = "schemeName")] on specific controllers/actions.

Related

Asp.Net web api antiforgery token fails after user signin & token refresh issues

I'm trying to make a short-lived JWT token with refresh token using Asp.Net Web API and Angular.
Though not required for the JWT and refresh token, I'm also trying to add anti forgery tokens.
The anti-forgery tokens also work until I log in.
I am refreshing the token after I've successfully signed in the user but I'm getting the error:
The provided anti-forgery token was meant for a different claims-based user than the current user
If I don't log in I can do the following with the anti-forgery token:
GET / -> GET /fetch-data -> POST* /weatherforecasts
^ 200 OK 200 OK
Anonymous token set here
* The Angular HttpClient does not send the token for GET methods so I changed the
endpoint to accept POST for testing
The flow is almost the same when I try signing in.
GET / -> GET /fetch-data -> POST /user/login -> POST /weatheforcasts
^ 200 OK 200 OK - JWT data 400
Anonymous token set here refresh anti-forgery token
Login Method inside UserController:
[HttpPost]
[ValidateAntiForgeryToken]
[Route("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] LoginModel login)
{
if (ModelState.IsValid)
{
var emailMatch = await UserManager.FindByEmailAsync(login.Email);
var result = await SignInManager.PasswordSignInAsync(emailMatch.UserName, login.Password, false, false);
if (result.Succeeded)
{
var newRefreshToken = await RefreshTokenStore.MakeToken(emailMatch.Id);
HttpContext.Response.Cookies.Append("refresh_token", newRefreshToken.Token, new CookieOptions()
{
HttpOnly = true
});
HttpContext.User = await SignInManager.CreateUserPrincipalAsync(emailMatch); // Found from another stack overflow issue but dosen't fix the issue
var tokens = Antiforgery.GetAndStoreTokens(HttpContext);
HttpContext.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions()
{
HttpOnly = false
});
string token = await CreateToken(emailMatch);
return Json(new
{
token,
expire = TimeSpan.FromSeconds(120)
});
}
}
return Unauthorized();
}
Inside Startup Configure I have this to set initial anonymous tokens
app.Use(async (context, next) =>
{
string path = context.Request.Path.Value;
if (string.Equals(path, "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase)
)
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions()
{
HttpOnly = false
});
}
await next.Invoke();
});
The only time I got it "working" was when I removed the if statement in the app.use function and created a new token for every request but it still fails on the first request after login.

ASP .NET Identity - How To Validate Auth Token In Custom Filter Attribute

I have a question about verifying an Auth Token. Currently, I'm generating my Auth Token at /token and sending it back to the client side where I store it in session storage. To make the process as secure as possible I'm looking to store the token in a HttpOnly and Secure cookie to protect against XSS.
I'm sending the cookie along with a XSRF token where on my client side (Angular) I set the X-XSRF-TOKEN header to the XSRF token value. The browser then automatically send the Auth Cookie back to me where I can read the token.
My main question is how do I extract the token and validate it? Before I was using Authentication Bear auth_token with the [Authorize] attribute but that doesn't work now since the token is in a cookie. I made my own Attribute (see below, but I'm stuck on the line where I actually verify the token. I'm not sure what function to call and pass the token to, to validate it.
public class ValidateToken : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var headers = actionContext.Request.Headers;
CookieHeaderValue authToken = headers.GetCookies().FirstOrDefault();
CookieState authCookie = authToken.Cookies.Where(p => p.Name == "AUTH-TOKEN").FirstOrDefault();
if (authCookie.Value is valid -> need help here) {
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
}
}
}
Edit: I've tried the following
actionContext.Request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authCookie.Value);
bool isAuth = base.IsAuthorized(actionContext);
But the RequestContext is already set once OnAuthorization is called and it doesn't update to use the new Header I added.

Do not receive refresh token with OpenIddict

I have a web api project based on .net core 2.0.
I followed pretty much the very good example on http://kevinchalet.com/2017/01/30/implementing-simple-token-authentication-in-aspnet-core-with-openiddict/.
The code that returns the SignIn() result for the auth. method looks like so:
if (request.IsPasswordGrantType())
{
// (...)
if (useraccount != null && useraccount.Failcount <= AppConstants.AuthMaxAllowedFailedLogin)
{
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, OpenIdConnectConstants.Claims.Role);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, AppConstants.AuthSubjectClaim, OpenIdConnectConstants.Destinations.AccessToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Name, useraccount.Username, OpenIdConnectConstants.Destinations.AccessToken);
return SignIn(new ClaimsPrincipal(identity), OpenIdConnectServerDefaults.AuthenticationScheme);
}
// (...)
}
My startup code looks like so:
services.AddDbContext<DbContext>(options =>
{
options.UseInMemoryDatabase(nameof(DbContext));
options.UseOpenIddict();
});
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<DbContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint(DcpConstants.ApiTokenRoute);
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
options.SetAccessTokenLifetime(TimeSpan.FromHours(1));
options.SetRefreshTokenLifetime(TimeSpan.FromDays(1));
options.DisableHttpsRequirement();
});
services.AddAuthentication(options =>
{
options.DefaultScheme = OAuthValidationDefaults.AuthenticationScheme;
}).AddOAuthValidation();
Now, when I send the post request with the following params:
username: foo#bar.com
password: myPassword
grant_type: password
scope: openid profile offline_access
I only receive scope, token_type, access_token, expires_in and id_token and no refresh_token.
What am I missing?
Returning a refresh token with the password is definitely allowed by the OAuth2 specification and thus, fully supported by OpenIddict.
For a refresh token to be returned by OpenIddict, you have to grant the special offline_access scope when calling SignIn. E.g:
if (request.IsPasswordGrantType())
{
// (...)
if (useraccount != null && useraccount.Failcount <= AppConstants.AuthMaxAllowedFailedLogin)
{
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, OpenIdConnectConstants.Claims.Role);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, AppConstants.AuthSubjectClaim, OpenIdConnectConstants.Destinations.AccessToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Name, useraccount.Username, OpenIdConnectConstants.Destinations.AccessToken);
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
// You have to grant the 'offline_access' scope to allow
// OpenIddict to return a refresh token to the caller.
ticket.SetScopes(OpenIdConnectConstants.Scopes.OfflineAccess);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
// (...)
}
Note that you'll also have to handle the grant_type=refresh_token requests in your controller. Here's an example using Identity: https://github.com/openiddict/openiddict-samples/blob/dev/samples/RefreshFlow/AuthorizationServer/Controllers/AuthorizationController.cs#L75-L109
options.AllowPasswordFlow();
Refresh Token cannot be used with Password flow, as the user is never redirected to login at Auth Server in this flow and so can’t directly authorize the application:
If the application uses the username-password OAuth authentication flow, no refresh token is issued, as the user cannot authorize the application in this flow. If the access token expires, the application using username-password OAuth flow must re-authenticate the user.

Return JWT Token generated by OAuthAuthorizatioServer from controller in Web API

Following #Taiseer Joudeh I was able to create simple POC of Web API. I'm able to create new account, then log-in and call secure Web API when I add JWT token to header.
I'd like to modify method that is responsible for creating accounts.
Right now I'm returning Create (201) code with new user object, but instead I'd like to return access token.
I've found similar question but it requires creating HttpClient and doing request to OAuthAuthorizatioServer TokenEndpointPath.
Second question I found requires generating temporary token that is returned to front-end, but then front-end must do additional request to server to get "real" token.
What I'd like to do is to return login response (access_token, token_type and expires_in) when I create user account.
I want user to be authenticated when his account is created.
I'm using just Web API and JWT without any cookies.
EDIT: My temporary solution:
after creating user I'm doing this:
var validTime = new TimeSpan(0, 0, 0, 10);
var identity = await UserManager.CreateIdentityAsync(user, "JWT");
var jwtFormat = new CustomJwtFormat(ApplicationConfiguration.Issuer);
var authenticationProperties = new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow, ExpiresUtc = DateTimeOffset.UtcNow.Add(validTime) };
var authenticationTicket = new AuthenticationTicket(identity, authenticationProperties);
var token = jwtFormat.Protect(authenticationTicket);
var response = new
{
access_token = token,
token_type = "bearer",
expires_in = validTime.TotalSeconds.ToInt()
};
return Ok(response);
where CustomJwtFormat comes from this awesome article.
Below is some code similar to what I'm doing in my application, which is using Asp.Net Core 1.0. Your signin and user registration will differ if you're not using Core 1.0.
public async Task<string> CreateUser(string username, string password)
{
string jwt = String.Empty;
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
var user = await _userManager.FindByNameAsync(username);
if (user == null) // user doesn't exist, create user
{
var newUser = await _userManager.CreateAsync(new ApplicationUser() { UserName = username }, password);
if (newUser.Succeeded) //user was successfully created, sign in user
{
user = await _userManager.FindByNameAsync(username);
var signInResult = await _signInManager.PasswordSignInAsync(user, password, false, true);
if (signInResult.Succeeded) //user signed in, create a JWT
{
var tokenHandler = new JwtSecurityTokenHandler();
List<Claim> userClaims = new List<Claim>();
//add any claims to the userClaims collection that you want to be part of the JWT
//...
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user.UserName, "TokenAuth"), userClaims);
DateTime expires = DateTime.Now.AddMinutes(30); //or whatever
var securityToken = tokenHandler.CreateToken(
issuer: _tokenOptions.Issuer, //_tokenAuthOptions is a class that holds the issuer, audience, and RSA security key
audience: _tokenOptions.Audience,
subject: identity,
notBefore: DateTime.Now,
expires: expires,
signingCredentials: _tokenOptions.SigningCredentials
);
jwt = tokenHandler.WriteToken(securityToken);
Response.StatusCode = (int)HttpStatusCode.Created;
await _signInManager.SignOutAsync(); //sign the user out, which deletes the cookie that gets added if you are using Identity. It's not needed as security is based on the JWT
}
}
//handle other cases...
}
}
Basically, the user is created and then signed in automatically. I then build a JWT (add in any claims you want) and return it in the response body. On the client side (MVC and Angular JS) I get the JWT out of the response body and store it. It is then passed back to the server in the Authorization header of each subsequent request. Authorization policies for all server actions are based on the set of claims supplied by the JWT. No cookies, no state on the server.
EDIT: I suppose you don't even need to call the signIn method in this process as you can just create the user, create a JWT, and return the JWT. When a user logs in on a future request you would use the Sign-In, create token, Sign-Out approach.
The idea of sending a response with access_token, token_type and expires_in, when the user is created is a great idea. But, I would stick to calling the "/token" end point with HttpClient to achieve this task. There are quite a few security checks that needs to be performed before token is generated. Since this is security I would not take any risk. I feel comfortable using the libraries/ code provided by industry experts.
That said, I tried to come up with a class you can call to create the token once the user is created. This code has been taken from the Microsoft's implementation of OAuth Authorization Server in their Katana project. You can access the source here. As you can see there is quite a lot happening when creating the token.
Here is the modified version of that middleware class for generating the token. You have to provide the proper OAuthAuthorizationServerOptions, Context, username, password, Scopes and clientid to get an access token. Please note that this is a sample implementation to guide you in the right direction. Please test it thoroughly if you want to use this.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AspNetIdentity.WebApi.Providers;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Microsoft.Owin.Security.OAuth;
namespace WebApi.AccessToken
{
public class TokenGenerator
{
public string ClientId { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public IList<string> Scope { get; set; }
private OAuthAuthorizationServerOptions Options { get; } =
new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost:59822")
};
public async Task<IList<KeyValuePair<string, string>>> InvokeTokenEndpointAsync(IOwinContext owinContext)
{
var result = new List<KeyValuePair<string, string>>();
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
// remove milliseconds in case they don't round-trip
currentUtc = currentUtc.Subtract(TimeSpan.FromMilliseconds(currentUtc.Millisecond));
AuthenticationTicket ticket = await InvokeTokenEndpointResourceOwnerPasswordCredentialsGrantAsync(owinContext, Options, currentUtc);
if (ticket == null)
{
result.Add(new KeyValuePair<string, string>("ERROR", "Failed to create acess_token"));
return result;
}
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(Options.AccessTokenExpireTimeSpan);
ticket = new AuthenticationTicket(ticket.Identity, ticket.Properties);
var accessTokenContext = new AuthenticationTokenCreateContext(
owinContext,
Options.AccessTokenFormat,
ticket);
await Options.AccessTokenProvider.CreateAsync(accessTokenContext);
string accessToken = accessTokenContext.Token;
if (string.IsNullOrEmpty(accessToken))
{
accessToken = accessTokenContext.SerializeTicket();
}
DateTimeOffset? accessTokenExpiresUtc = ticket.Properties.ExpiresUtc;
result.Add(new KeyValuePair<string, string>("access_token", accessToken));
result.Add(new KeyValuePair<string, string>("token_type", "bearer"));
TimeSpan? expiresTimeSpan = accessTokenExpiresUtc - currentUtc;
var expiresIn = (long)expiresTimeSpan.Value.TotalSeconds;
if (expiresIn > 0)
{
result.Add(new KeyValuePair<string, string>("expires_in", "bearer"));
}
return result;
}
private async Task<AuthenticationTicket> InvokeTokenEndpointResourceOwnerPasswordCredentialsGrantAsync(IOwinContext owinContext, OAuthAuthorizationServerOptions options, DateTimeOffset currentUtc)
{
var grantContext = new OAuthGrantResourceOwnerCredentialsContext(
owinContext,
options,
ClientId,
UserName,
Password,
Scope);
await options.Provider.GrantResourceOwnerCredentials(grantContext);
return grantContext.Ticket;
}
}
}
Please let me know if you have any questions.
Thank you,
Soma.
I assume you are referring to the following article: http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/
The general approach to authenticating a user in this case is
Make an HTTP call to the token endpoint
User Enters the credentials at the UI that is rendered
IdP verifies the credentials and issues a token
User makes another call to an authorized endpoint and OWIN will validate this (JWT) token (as configured in the ConfigureOAuthTokenConsumption method) and if successfull will set a user session with expiry same as the token expiry. The session is set using session cookies.
Now try to understand that in general, this whole process of authentication is required because your server does not trust the user logging in to be the user the person is claiming to be. However, in your case, you (or your server code) just created a user and you know for sure that the person accessing your website is the user you have just created. In this case you don't need to validate a token to create a session for this user. Just create a session with an expiry that suits your use-case.
The next time the user will have to log-in and prove him/herself to the server using a token but this time the user does not need to prove him/her self.
Note: If you absolutely are adamant about requiring a token to log in a user who you yourself have just created using their credentials here are a couple of issues.
You are taking on the responsibility of storing (having access to) the user credentials, which might not be with you over the lifetime of the application (in most cases you might want to act as a relying party rather than an IdP).
Even if you want to then doing it is not trivial. You will have to make the calls to the token end point in code (server or client side) on behalf of the user, enter their credentials for them, retrieve the token, call an authenticated endpoint on your site and retrieve the session cookie all while hiding all this from the user, which probably is something you will either do if you hate yourself :) but also isn't very secure way of doing things especially when you are taking all the trouble to implement OAuth in the first place.
Also, take a look at Windows Server 2016 (technical preview 5 at this time) which supports implicit grants and might take writing all this custom code off your plate if you can wait a bit for RTM.
In an OAuth solution you as a developer are are not required to handle the cookie setting yourself. The cookie handling is done for you automatically by the framework.
Also the only way to set a session is a. Using session cookies or b. Use cookie-less (in the url) methods. Look at http://www.cloudidentity.com/blog/2015/02/19/introducing-adal-js-v1/ for more details on token validation and session establishment (also search the term cookie and you will know what all it's used for).
If you start thinking about not using cookies at all not only will you have to figure out how to maintain session and do it securely without cookies but also have to re-write the token refresh code that detects and refreshes the token for you based on the presence of a session cookie. (i.e. not a smart idea)
I am using the exact technology stack and recently implemented token based authorization successfully. The link I took reference from had very neatly defined the token-based auth in Web APIs. A must bookmark page I must say. Here is the link: TOKEN BASED AUTHENTICATION IN WEB APIs.

How does UseWindowsAzureActiveDirectoryBearerAuthentication work in validating the token?

I am following the below GitHub sample for implementing Authentication mechanism across WebApp and WebApi.
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet
I am using a single App registration for both WebApp and WebApi, get a access token for "https://abc.onmicrosoft.com/App" and pass it on to WebApi. I am attaching the token to the HTTPS headers with the name "Bearer". I have the below in the WebApi Owin Startup class to validate the token for the Audience and Tenant, but does not actually validate the token for these as expected.
A couple of questions:
1. What triggers the below handler to validate the token for the tenant and audience? Is it the [Authorize] attribute on the Controller class?
2. How does it where to find the token to execute the handler?
3. Setting the SaveSigninToken to true saves the token. How can I retrieve the token and also Acquire access token for Graph API from this token?
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = "abc.onmicrosoft.com",
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = "https://abc.onmicrosoft.com/App",
SaveSigninToken = true,
}
});
Please advise. Thanks in advance!
What triggers the below handler to validate the token for the tenant and audience?
The middleware runs in Active mode by default, so it will attempt to find a token in every request. If it finds one, it will attempt to validate it. If it finds that it is valid, a ClaimsPrincipal is created which is accessible in further OWIN middleware and Web API components.
It also downloads the public keys with which it checks the token signature on app startup from Azure AD. You can see this if you use a tool like Fiddler.
How does it where to find the token to execute the handler?
I'm not sure if I am understanding this question, I hope my answer above clarified the process.
Setting the SaveSigninToken to true saves the token. How can I retrieve the token and also Acquire access token for Graph API from this token?
What you are trying to do is call an API using the on-behalf-of flow. You can find an example app here: https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof. More specifically this part should be of interest to you: https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof/blob/master/TodoListService/Controllers/TodoListController.cs#L133.
ClientCredential clientCred = new ClientCredential(clientId, appKey);
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
string userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
string userAccessToken = bootstrapContext.Token;
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(authority, new DbTokenCache(userId));
// In the case of a transient error, retry once after 1 second, then abandon.
// Retrying is optional. It may be better, for your application, to return an error immediately to the user and have the user initiate the retry.
bool retry = false;
int retryCount = 0;
do
{
retry = false;
try
{
result = await authContext.AcquireTokenAsync(graphResourceId, clientCred, userAssertion);
accessToken = result.AccessToken;
}
catch (AdalException ex)
{
if (ex.ErrorCode == "temporarily_unavailable")
{
// Transient error, OK to retry.
retry = true;
retryCount++;
Thread.Sleep(1000);
}
}
} while ((retry == true) && (retryCount < 1));
The [Authorize] decoration in the controller or whichever method we specify triggers the Owin security handler to validate the token and generates the claims.

Categories