I have an ASP.NET web application that presents data retrieved from a web service.
Before getting actual data from the service a token must be retrieved by calling a specific method in that service. This token must then be providing in any subsequent data method calls. This token expires after some time and a call to a data method with an expired token leads to an error and a new token must be retrieved for further communication. The active token is kept in a global ASP.NET application variable.
This is all fine, but how do I prevent overlapping token renewals? A request from one user to the site triggers a service call, this gives an error and a new token is retrieved and put in the cache, but in the meantime another user request finds that the token is expired and also renews the token.
Is there not a problem here? I can't really wrap my head around it.
You can do so by locking (Expiration = 2 hours):
static class TokenGenerator
{
private static object tokenLock = new object();
private static DateTime tokenExp = DateTime.MinValue;
private static string token = null;
public static string GetToken()
{
if(tokenExp >= DateTime.UtcNow)
return token;
lock(tokenLock)
{
if(tokenExp >= DateTime.UtcNow)
return token;
// generate token
token = GenerateToken();
tokenExp = DateTime.UtcNow.AddHours(2);
}
}
}
public static string GenerateToken()
{
// generate and return token
}
Having a global token like this might not be best practice though.
Also this code blocks all other token requests while the token is being regenerated, depending on how long the token generation takes this might not be acceptable.
Related
I have a bit of a head-scratcher for updating a refresh tokens in a certain situation with a single page application making multiple api calls at the same time. I have an SPA which has a stack that consists of the following.
Html/JS SPA -> MVC Application -> WebAPI
I make use of the Hybrid flow, when a user logs onto the page I store the id_token the access_token and the refresh_token in the session cookie.
I use a HttpClient which has two DelegatingHandlers to talk to the web API. One of the delegating handlers simply adds the access token to the Authorization header. The other one runs before this and checks the lifetime left on the access token. If the access token has a limited amount of time left the refresh_token is used to get new credentials and save them back to my session.
Here is the code for the OidcTokenRefreshHandler.
public class OidcTokenRefreshHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly OidcTokenRefreshHandlerParams _handlerParams;
public OidcTokenRefreshHandler(IHttpContextAccessor httpContextAccessor, OidcTokenRefreshHandlerParams handlerParams)
{
_httpContextAccessor = httpContextAccessor;
_handlerParams = handlerParams;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
var handler = new JwtSecurityTokenHandler();
var accessTokenObj = handler.ReadJwtToken(accessToken);
var expiry = accessTokenObj.ValidTo;
if (expiry - TimeSpan.FromMinutes(_handlerParams.AccessTokenThresholdTimeInMinutes) < DateTime.UtcNow )
{
await RefreshTokenAsync(cancellationToken);
}
return await base.SendAsync(request, cancellationToken);
}
private async Task RefreshTokenAsync(CancellationToken cancellationToken)
{
var client = new HttpClient();
var discoveryResponse = await client.GetDiscoveryDocumentAsync(_handlerParams.OidcAuthorityUrl, cancellationToken);
if (discoveryResponse.IsError)
{
throw new Exception(discoveryResponse.Error);
}
var refreshToken = await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = discoveryResponse.TokenEndpoint,
ClientId = _handlerParams.OidcClientId,
ClientSecret = _handlerParams.OidcClientSecret,
RefreshToken = refreshToken
}, cancellationToken);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = tokenResponse.IdentityToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = tokenResponse.AccessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResponse.RefreshToken
}
};
// Sign in the user with a new refresh_token and new access_token.
var info = await _httpContextAccessor.HttpContext.AuthenticateAsync("Cookies");
info.Properties.StoreTokens(tokens);
await _httpContextAccessor.HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
}
}
The problem is that many calls hit this at roughly the same time. All of these calls will then hit the refresh endpoint at the same time. They will all retrieve new valid access tokens and the application will continue to work. However if 3 requests happen at the same time, three new refresh tokens will be created and only one of these will be valid. Due to the asynchronous nature of the application I have no guarantee that the refresh token stored in my session is actually the latest refresh token. The next time I need to refresh the refresh token may be invalid (and often is).
My thoughts on possible solutions so far.
Lock at the point of checking the access token with a Mutex or similar. However this has the potential to block when it is being used by a different user with a different session (to the best of my knowledge). It also doesn't work if my MVC app is across multiple instances.
Change so the refresh tokens remain valid after use. So it doesn't matter which one of the three gets used.
Any thoughts on which of the above is better or has anyone got a really clever alternative.
Many Thanks!
When all your requests come from the same SPA, the best should be to sync them in the browser and get rid of the problem serverside. Each time your client code requires a token, return a promise. The same promise instance to all requests, so they all get resolved with the only request to the server.
Unfortunately if you proxy all the requests through your local API and never pass your bearer to the SPA, my idea wouldn't work.
But if you keep your refresh token absolutely secure (never send it to the front), I can't see any problem to make it reusable. In that case you can switch on sliding option as excellently described here to perform less renewal requests.
I wrote a simple application in Flutter using Dart. I use JWT tokens to authenticate user. Primary token is valid only 60 seconds.
When user send a request with expired token the webapi returns 401.
Then in my Dart code I check if statuscode of response is 401
If yes, then I send a request to RefreshToken endpoint and send request one more time (this request which returned 401 earlier).
If user does many actions too fast, expired token is renewed many times.
I'd like to avoid this.
In perfect soultion when token is being refreshing, other requests should wait.
I faced similar problem and tried to solve it using below approach.
I use flutter-redux to manage the state on client side.
Get jwt token after login
Decode the jwt token on client side as responded from server.
It contains a timeout - time of expiration.
Create a redux middleware on client side lets say _createRefreshTokenMiddleware.
Every request from client should go through this middle-ware before sending to server.
In this middle-ware, with every requests to server, check the token timeout, if token is expired, hold those request, send request to server to refresh token, wait until received new token, use this new token to send those request to server.
All other requests for which token will expire will wait on a common promise, lets say refreshTokenPromise to get refreshToken get resolved first. In this way you don't have to send multiple refreshToken requests.
If the token is still valid, let the requests to go through.
Refer below example -
Your middleware :
Middleware<AppState> _createRefreshTokenMiddleware() {
return (Store store, action, NextDispatcher next) async {
AppState appState = store.state;
AuthState auth = appState.auth;
if (isTokenExpired(auth)) {
if (auth.refreshTokenPromise == null) {
refreshToken(store).then((res) => next(action));
} else {
auth.refreshTokenPromise.then((res) => next(action));
}
}
next(action);
};
}
All the requests for which token is expired will wait on refreshTokenPromise to get resolved and as soon as that is resolved all of the pending requests will have new updated token set in request header (e.g).
Checking for token expiration :
bool isTokenExpired(AuthState auth) {
int bufferSeconds = 10;
if(auth != null && auth.authTokens != null && auth.authTokens.tokenExpiryTime != null) {
var currentTime = DateTime.now();
Duration durationRemaining = auth.authTokens.tokenExpiryTime.difference(currentTime);
return (durationRemaining.inSeconds - bufferSeconds) <= 0 ? true : false;
}
return false;
}
You send the request to refresh token 10 seconds before it is actually expired.
AuthState Model:
#immutable
class AuthState {
// properties
final bool isAuthenticated;
final bool isAuthenticating;
final AuthTokens authTokens;
final String error;
final Future<dynamic> refreshTokenPromise;
// constructor with default
AuthState({
this.isAuthenticated = false,
this.isAuthenticating = false,
this.authTokens,
this.error,
this.refreshTokenPromise,
});
}
Your auth-state model can be like above.
AuthToken:
#immutable
class AuthTokens {
// properties
final String accessToken;
final String refreshToken;
final DateTime tokenExpiryTime;
// constructor with default
AuthTokens({
this.accessToken,
this.refreshToken,
this.tokenExpiryTime,
});
}
Although I have given redux based solution here but same strategy can be applied anywhere else as well. I hope it helps.
As you correctly pointed out, the problem is that the authorization server receives too many token refresh request. Each particular user should only send one refresh request and rely on the results of that request.
Flutter's async package has a handy class called AsyncMemoizer for cases like this.
From the API reference:
A class for running an asynchronous function exactly once and caching its result.
An AsyncMemoizer is used when some function may be run multiple times
in order to get its result, but it only actually needs to be run once
for its effect. To memoize the result of an async function, you can
create a memoizer outside the function (for example as an instance
field if you want to memoize the result of a method), and then wrap
the function's body in a call to runOnce.
Assuming that the component of your app that handles all token requests is a singleton, you can cache the tokenrequests like that:
class TokenDataSource {
AsyncMemoizer<TokenResponse> tokenRequestMemoizer = AsyncMemoizer();
...
#override
Future<Tokens> verifyAndRefreshTokens() async {
var tokenResponse = await tokenRequestMemoizer.runOnce(() {
// run your token request code
});
// once the request is done, reset the memoizer so that future clients don't receive the cached tokens
tokenRequestMemoizer = AsyncMemoizer();
// return results
}
}
This will make all clients of TokenDataSource wait for the same token request instead of launching a new one.
I'm trying to create a simple user authentication function but I just can't get it to work.
Here is the code I'm working on:
public class LoginController : ApiController
{
private void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
public bool Login(string token)
{
//Check token
if (.....)
{
//Authenticate user
var identity = new GenericIdentity("Test user");
SetPrincipal(new GenericPrincipal(identity, new string[]{"Test role"}));
}
}
[Authorize]
public string TestFun()
{
return "Hello " + User.Identity.Name;
}
}
So, if I try to call method TestFun() first, it returns error code 401 like it should.
However when I call method Login() it should somehow save user credentials, but this is where I get lost, I just can't get it to work.
TestFun() always returns error code 401 even if I call Login() first.
If I try to put return "Hello " + User.Identity.Name; in the Login() function it returns correct username, but in the TestFun() the user is not available.
I've even tried using Sessions and FormsAuthentication but I just can't get it to work, even on this really simple example.
Can someone please tell me what am I missing?
Thanks!
The Login method sets the principal for current request only. Just after the request completes, the principal context is wiped out so that the server can handle other requests for other users. When a new request comes, eons later from the server perspective, the principal context no longer exists and if nothing restores it, the request is unauthenticated.
To fix this you have to return something from your login method to the client. Not only bool but rather - an authentication token. Something the client could use to authenticate further requests.
It could be anything. Forms cookie would be fine as long as the client remembers to append it to further requests. Another common practice is to have a custom authentication token returned to the client and then appended by the client in a custom authentication header. And as forms cookies are handled by the Forms Authentication module, custom headers would need a custom mvc authentication filter or custom asp.net authentication module so that the token is readed, the identity is extracted and restored just before the request is about to execute.
If you don't like to bake your own token infrastructure, I would also recommend OAuth2 tokens. There is a great book that contains easy to follow examples on this and other possible authentication methods:
http://www.amazon.com/Pro-ASP-NET-Web-API-Security/dp/1430257822/ref=sr_1_1?ie=UTF8&sr=8-1&keywords=web+api+security
I just got the same issue, yes, I agreed we need to save that principal into somewhere (cookie, session) for other action to use, so, in SetPrincipal function I added
HttpContext.Current.Session["user"] = HttpContext.Current.User;
Now, the issue is how to get it back for other action, the idea popups in my mind is to extend AuthorizeAttribute and override IsAuthrized function, it will read the session first and if it found the session, it will return true, otherwise it will return false.
namespace BinZ
{
public class MyAuthorizeAttribute:AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext) {
HttpContext.Current.User = HttpContext.Current.Session["user"] as IPrincipal;
return HttpContext.Current.User != null;
}
}
}
Please remember to replace [Authorize] to [MyAuthorizeAttribute] in WebApi controller.
It works for me very well.
Cheers
for example I have a web API : http://example.com/api/product.
I have a C# client to consume this web API. Something like that to get whole list of product.
// List all products.
HttpResponseMessage response = client.GetAsync("api/products").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
var products = response.Content.ReadAsAsync<IEnumerable<Product>>().Result;
foreach (var p in products)
{
Console.WriteLine("{0}\t{1};\t{2}", p.Name, p.Price, p.Category);
}
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
How do I pass the username and password from C# client to server's API? What I want is when the C# client to get whole product list from web API.
The client will send the username and password to the server's API. if the server's web API checks whether it is authorized user from database, if not don't let it get product list.
I used the following approach in a proof of concept some time ago, I hope it helps you.
I wrote something like this, an "AuthenticationController" with 2 methods:
public bool Login(string username, string password, bool rememberMe)
{
if (Membership.ValidateUser(username, password))
{
FormsAuthentication.SetAuthCookie(username, rememberMe);
return true;
}
return false;
}
public void Logout()
{
FormsAuthentication.SignOut();
}
The Login method creates a cookie that will be sent to the client; then, in each request, you need to send it back to the server. You can use the [Authorize] attribute in your controller actions to validate allowed roles and rights.
My recommendation is to use have an authentication routine that will assign a token to the client. The client would then cache that token and pass that token in subsequent requests. The authentication routine should be via SSL to prevent sniffing on the wire and shouldn't be stored on the device at all (the token can be cached to the device).
This will give you a fair bit of control over the client. Your service is then in a position where it can preemptively deactivate the client (kill the token and force a re-auth - essentially a timemout situation). You are also in a position to protect your application on the client (if the application is compromised on the device the user credentials won't be passed around).
You could use DotNetOpenAuth to get you started along this path.
[System.Web.Mvc.AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOn(string loginIdentifier)
{
if (!Identifier.IsValid(loginIdentifier))
{
ModelState.AddModelError("loginIdentifier",
"The specified login identifier is invalid");
return View();
}
else
{
var openid = new OpenIdRelyingParty();
IAuthenticationRequest request = openid.CreateRequest(
Identifier.Parse(loginIdentifier));
// Require some additional data
request.AddExtension(new ClaimsRequest
{
BirthDate = DemandLevel.NoRequest,
Email = DemandLevel.Require,
FullName = DemandLevel.Require
});
return request.RedirectingResponse.AsActionResult();
}
}
Source: Sample Code
I have a web page that uses a WCF service. Multiple users maybe using the web page at any one time and therefore making requests to the WCF service which is on a remote machine.
Each user on the web page gets a unique ID, I want to add this unique ID to the request header of each request made by that user.
So far I have created the following code which correctly adds a header to the WCF message.
public class HeaderIdPusher : IClientMessageInspector
{
private static readonly string _balancerKey = "balancerId";
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
Guid userId = Guid.NewGuid();
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers[_balancerKey]))
{
httpRequestMessage.Headers[_balancerKey] = userId.ToString();
}
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(_balancerKey, userId.ToString());
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
}
However I am no stuck because I can't get the ID to persist between requests. You can see here that at the moment I am generating an ID for each request, however I can't store this in the Session of the page the user is on because the HttpContext.Current is null. Is there another way of storing this? Is there another way of passing in the HttpContext of the user on my web page?
The problem is discussed here:
http://social.msdn.microsoft.com/forums/en-US/wcf/thread/27896125-b61e-42bd-a1b0-e6da5c23e6fc
Essentially WCF doesn't have sessions, as you could pass anything you wanted as a parameter (in this case, your Unique ID) and handle it any way you wanted in your implementation.
After much hacking I found a solution, it isn't great but it works.
In the ASP.NET page before I create the WCF service instance I create an address header and endpoint:
AddressHeader header = AddressHeader.CreateAddressHeader("MyKey", "http://www.w3.org/2005/08/addressing", "MyValue");
EndpointAddress endpoint = new EndpointAddress(new Uri("http://www.myservice.com/service"), header);
Then I create an instance of the service passing in the endpoint:
using (WcfService service = new WcfService(_configName,endpoint ))
{
}
This gets the data into the WCF service, then in the HeaderIdPusher : IClientMessageInspector detailed above I pull the header value out:
public class HeaderIdPusher : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
string id = "Not found";
if(channel.RemoteAddress.Headers.Any(x=>x.Name == "MyKey"))
{
id = channel.RemoteAddress.Headers.First(x => x.Name == "MyKey").GetValue<string>();
}
This solution isn't ideal and it puts extra data into the SOAP message but it is the only way I have found of sharing data from the ASP.NET page with the WCF process.