I have an app which authenticates with a back-end and receives a long-lived refresh token and short-lived access token back. We use the access token for authorizing our requests, and every time it expires, we trigger a token renewal (using the refresh token) before we retry the API call with the new tokens. Pretty standard stuff.
It's possible to trigger multiple network calls simultaneously (asynchronously), but if they all have invalid access tokens, we want only the first to go through and refresh the token, while the subsequent calls wait for the initial one to complete before they continue. Currently, we've implemented it like this:
private var renewingDeferred: Deferred<Unit>? = null
suspend fun renewTokens() {
coroutineScope {
if (renewingDeferred == null) {
renewingDeferred = async {
try {
tokens = tokensApi.renew().await()
} finally {
renewingDeferred = null
}
}
} else {
renewingDeferred?.await()
}
}
}
The idea is that every request that has an invalid access token will call renewTokens() and then retry with new headers. The caller of renewTokens() shouldn't care about whether it actually renews or just waits for a previous renewal to finish, as long as it knows that tokens are renewed once the function returns (is no longer suspended). And as far as I can tell, it works fine, but I'm not completely sure about the code, specifically the renewingDeferred = null part of the finally block.
For instance, say request A starts renewing, request B is triggered and starts awaiting request A, then A finishes, sets renewingDeferred to null, but does setting that value to null somehow affect request B's awaiting status?
Also, what if an exception is thrown when calling the API? I don't have a catch block, sort of assuming that await()ing on a deferred that throws will somehow rethrow, but exception handling in coroutines isn't really my strong side. Would love to get some advice on this.
Also, if anyone has a general idea about how this should be solved in a different manner, all suggestions are welcome!
You can solve this by using Mutex.
Create one property for coroutine locking, and when renewToken is called, lock Mutex until token is renewed.
Related
We are creating a Blazor WASM application for usage on unstable and possibly slow connections. We have successfully implemented authentication with OpenIdConnect.
We noticed that on every refresh (F5) of the page, the token is being validated against the Identity Provider again:
We think this is normal/desired behaviour, but is there any way around this?
We know this is a tiny amount of data, but it would be optimal to not have this every time.
The websites are for 'internal' usage only (through a VPN).
Thank you
I have personally run into this issue as well. For us, it was even worse since the IdP would take quite some time since the authorization endpoint would ignore the prompt=none parameter and try to challenge the user every time Blazor WASM Authentication tried to refresh its authentication state. This forced me to do some digging so hopefully, my findings are useful to you.
The OIDC in Blazor WASM makes use of their RemoteAuthenticationService class which implements the AuthenticationStateProvider to provide an authentication state to Blazor WASM on top of the Access Token.
I think this is the key problem here. That they are separating the AuthState and AccessToken which (at least for me) was unintuitive since in the past I would determine whether a user is "logged in", purely based on if they have a valid access token or not.
So the fact that you already have an "AccessToken" is irrelevant to the AuthState which begs the question: How do they determine your AuthState?
Lets checkout this key function in the RemoteAuthenticationService:
...
public override async Task<AuthenticationState> GetAuthenticationStateAsync() => new AuthenticationState(await GetUser(useCache: true));
...
private async Task<ClaimsPrincipal> GetUser(bool useCache = false)
{
var now = DateTimeOffset.Now;
if (useCache && now < _userLastCheck + _userCacheRefreshInterval)
{
return _cachedUser;
}
_cachedUser = await GetAuthenticatedUser();
_userLastCheck = now;
return _cachedUser;
}
In the above code snippet you can see that the AuthState is determined by this GetUser function which first checks some cache for the user which is currently hardcoded to expire every 60 seconds. This means that if you check the user's AuthState, then every 60 seconds it would have to query the IdP to determine the AuthState. This is how it does that:
Uses JSInterop to call trySilentSignIn on the oidc-client typescript library.
SilentSignIn opens a hidden iframe to the IdP authorization endpoint to see if you are in fact signed in at the IdP. If successful then it reports the signed-in user to the AuthState provider.
The problem here is that this could happen every time you refresh the page or even every 60 seconds whenever you query the current AuthState where the user cache is expired. There is no persistence of the access token or the AuthState in any way.
Ok so then how do I fix this?
The only way I can think of is to implement your own RemoteAuthenticationService with some slight modifications from the one in the Authentication Library.
Specifically to
Potentially persist the access token.
Reimplement the getUser call to check the validity/presence of the persisted access token to get the user rather than using the silentSignin function on the oidc-client library.
I am trying to use HttpContext.Session in my ASP.NET Core Blazor Server application (as described in this MS Doc, I mean: all correctly set up in startup)
Here is the code part when I try to set a value:
var session = _contextAccessor.HttpContext?.Session;
if (session != null && session.IsAvailable)
{
session.Set(key, data);
await session.CommitAsync();
}
When this code called in Razor component's OnAfterRenderAsync the session.Set throws following exception:
The session cannot be established after the response has started.
I (probably) understand the message, but this renders the Session infrastructure pretty unusable: the application needs to access its state in every phase of the execution...
Question
Should I forget completely the DistributedSession infrastructure, and go for Cookies, or Browser SessionStorage? ...or is there a workaround here still utilizing HttpContext.Session? I would not want to just drop the distributed session infra for a way lower level implementation...
(just for the record: Browser's Session Storage is NOT across tabs, which is a pain)
Blazor is fundamentally incompatible with the concept of traditional server-side sessions, especially in the client-side or WebAssembly hosting model where there is no server-side to begin with. Even in the "server-side" hosting model, though, communication with the server is over websockets. There's only one initial request. Server-side sessions require a cookie which must be sent to the client when the session is established, which means the only point you could do that is on the first load. Afterwards, there's no further requests, and thus no opportunity to establish a session.
The docs give guidance on how to maintain state in a Blazor app. For the closest thing to traditional server-side sessions, you're looking at using the browser's sessionStorage.
Note: I know this answer is a little old, but I use sessions with WebSockets just fine, and I wanted to share my findings.
Answer
I think this Session.Set() error that you're describing is a bug, since Session.Get() works just fine even after the response has started, but Session.Set() doesn't. Regardless, the workaround (or "hack" if you will) includes making a throwaway call to Session.Set() to "prime" the session for future writing. Just find a line of code in your application where you KNOW the response hasn't sent, and insert a throwaway call to Session.Set() there. Then you will be able to make subsequent calls to Session.Set() with no error, including ones after the response has started, inside your OnInitializedAsync() method. You can check if the response is started by checking the property HttpContext.Response.HasStarted.
Try adding this app.Use() snippet into your Startup.cs Configure() method. Try to ensure the line is placed somewhere before app.UseRouting():
...
...
app.UseHttpsRedirection();
app.UseStaticFiles();
//begin Set() hack
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
//this throwaway session variable will "prime" the Set() method
//to allow it to be called after the response has started
var TempKey = Guid.NewGuid().ToString(); //create a random key
Context.Session.Set(TempKey, Array.Empty<byte>()); //set the throwaway session variable
Context.Session.Remove(TempKey); //remove the throwaway session variable
await Next(); //continue on with the request
});
//end Set() hack
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
...
...
Background Info
The info I can share here is not Blazor specific, but will help you pinpoint what's happening in your setup, as I've come across the same error myself. The error occurs when BOTH of the following criteria are met simultaneously:
Criteria 1. A request is sent to the server with no session cookie, or the included session cookie is invalid/expired.
Criteria 2. The request in Criteria 1 makes a call to Session.Set() after the response has started. In other words, if the property HttpContext.Response.HasStarted is true, and Session.Set() is called, the exception will be thrown.
Important: If Criteria 1 is not met, then calling Session.Set() after the response has started will NOT cause the error.
That is why the error only seems to happen upon first load of a page--it's because often in first loads, there is no session cookie that the server can use (or the one that was provided is invalid or too old), and the server has to spin up a new session data store (I don't know why it has to spin up a new one for Set(), that's why I say I think this is a bug). If the server has to spin up a new session data store, it does so upon the first call to Session.Set(), and new session data stores cannot be spun up after the response has started. On the other hand, if the session cookie provided was a valid one, then no new data store needs to be spun up, and thus you can call Session.Set() anytime you want, including after the response has started.
What you need to do, is make a preliminary call to Session.Set() before the response gets started, so that the session data store gets spun up, and then your call to Session.Set() won't cause the error.
SessionStorege has more space than cookies.
Syncing (two ways!) the sessionStorage is impossible correctly
I think you are thinking that if it is on the browser, how can you access that in C#? Please see some examples. It actually read from the browser and transfers (use) on the server side.
sessionstorage and localstorage in blazor are encrypted. We do not need to do extra for encryption. The same applies for serialization.
I'm accessing a external rest api which is secured using OpenID connect. I must run a job which calls the api several times before it completes. This job may be executed in parallel using several concurrent threads. Thus I use a service for api access which is instantiated for each job:
private AccessAndRefresTokens tokens; // I initially get that from somewhere else
public Mono<Result> callTheApi(){
return createWebClientWith(tokens.accessToken).executeRequest();
}
public Mono<Result> callOtherApiFunction(){
return createWebClientWith(tokens.accessToken).executeRequest();
}
public Mono<Result> callYetAnotherApiFunction(){
return createWebClientWith(tokens.accessToken).executeRequest();
}
As my job is executed it might happen that the access token expires between two api calls. To prevent this from happening, I like to check the validity of the access token before every request and refresh it if necessary.
My first idea was to do the validity check inside a flatMap operator:
Mono.just(tokens).flatMap(tokens -> {
if(accessTokenExpired){
return refreshAccessToken().doOnNext(refreshedTokens -> tokens = refreshedTokens);
}else{
return Mono.just(tokens.accessToken);
}
}).flatMap(accessToken -> createWebClientWith(accessToken));
However it seems to me that this would trigger the refresh several times if multiple threads access the service and the refresh has not yet completed. As a consequence I would end up refreshing the access token several times in a very short time which might fail due to rate limits.
I am new to the whole reactive thing and I suppose using a synchronized block is not a desired option. So I tried to figure out something using reactor's Processor but I could not find a satisfying solution.
So is there a way to ensure the access token is refershed only once? And how do I achieve this without using blocking code?
I'd like to add a service that executes some initialization operations for the system when it's first created.
I'd imagine it would be a stateless service (with cluster admin rights) that should self-destruct when it's done it's thing. I am under the impression that exiting the RunAsync function allows me to indicate that I'm finished (or in an error state). However, then it still hangs around on the application's context and annoyingly looking like it's "active" when it's not really doing anything at all.
Is it possible for a service to remove itself?
I think maybe we could try using the FabricClient.ServiceManager's DeleteServiceAsync (using parameters based on the service context) inside an OnCloseAsync override but I've not been able to prove that might work and it feels a little funky:
var client = new FabricClient();
await client.ServiceManager.DeleteServiceAsync(new DeleteServiceDescription(Context.ServiceName));
Is there a better way?
Returning from RunAsync will end the code in RunAsync (indicate completion), so SF won't start RunAsync again (It would if it returned an exception, for example). RunAsync completion doesn't cause the service to be deleted. As mentioned, for example, the service might be done with background work but still listening for incoming messages.
The best way to shut down a service is to call DeleteServiceAsync. This can be done by the service itself or another service, or from outside the cluster. Services can self-delete, so for services whose work is done we typically see await DeleteServiceAsync as the last line of RunAsync, after which the method just exits. Something like:
RunAsync(CancellationToken ct)
{
while(!workCompleted && !ct.IsCancellationRequested)
{
if(!DoneWithWork())
{
DoWork()
}
if(DoneWithWork())
{
workCompleted == true;
await DeleteServiceAsync(...)
}
}
}
The goal is to ensure that if your service is actually done doing the work it cleans itself up, but doesn't trigger its own deletion for the other reasons that a CancellationToken can get signaled, such as shutting down due to some upgrade or cluster resource balancing.
As mentioned already, returning from RunAsync will end this method only, but the service will continue to run and hence not be deleted.
DeleteServiceAsync certainly is the way to go - however it's not quite as simple as just calling it because if you're not careful it will deadlock on the current thread (especially in local developer cluster). You would also likely get a few short-lived health warnings about RunAsync taking a long time to terminate and/or target replica size not being met.
In any case - solution is quite simple - just do this:
private async Task DeleteSelf(CancellationToken cancellationToken)
{
using (var client = new FabricClient())
{
await client.ServiceManager.DeleteServiceAsync(new DeleteServiceDescription(this.Context.ServiceName), TimeSpan.FromMinutes(1), cancellationToken);
}
}
Then, in last line of my RunAsync method I call:
await DeleteSelf(cancellationToken).ConfigureAwait(false);
The ConfigureAwait(false) will help with deadlock issue as it will essentially return to a new thread synchronization context - i.e. not try to return to "caller context".
I'm working on implementing the ForgotPassword functionality ie in the AccountController using ASP.NET Identity as in the standard VS 2015 project template.
The problem I'm trying to solve is that when the password reset email is sent, there is a noticeable delay in the page response. If the password recovery attempt does not find an existing account then no email is sent so there is a faster response. So I think this noticeable delay can be used for account enumeration, that is, a hacker could determine that an account exists based on the response time of the forgot password page.
So I want to eliminate this difference in page response time so that there is no way to detect if an account was found.
In the past I've queued potentially slow tasks like sending an email onto a background thread using code like this:
ThreadPool.QueueUserWorkItem(new WaitCallback(AccountNotification.SendPasswordResetLink),
notificationInfo);
But ThreadPool.QueueUserWorkItem does not exist in .NET Core, so I'm in need of some alternative.
I suppose one idea is to introduce an artificial delay in the case where no account is found with Thread.Sleep, but I'd rather find a way to send the email without blocking the UI.
UPDATE: To clarify the problem I'm posting the actual code:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await userManager.FindByNameAsync(model.Email);
if (user == null || !(await userManager.IsEmailConfirmedAsync(user)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
var code = await userManager.GeneratePasswordResetTokenAsync(user);
var resetUrl = Url.Action("ResetPassword", "Account",
new { userId = user.Id, code = code },
protocol: HttpContext.Request.Scheme);
//there is a noticeable delay in the UI here because we are awaiting
await emailSender.SendPasswordResetEmailAsync(
userManager.Site,
model.Email,
"Reset Password",
resetUrl);
return View("ForgotPasswordConfirmation");
}
// If we got this far, something failed, redisplay form
return View(model);
}
Is there a good way to handle this using other built in framework functionality?
Just don't await the task. That's then mostly-equivalent to running all of that code on the thread-pool to start with, assuming it doesn't internally await anything without calling ConfigureAwait(false). (You'll want to check that, if it's your code.)
You might want to add the task to some set of tasks which should be awaited before the server shuts down, assuming there's some appropriate notion of "requested shutdown" in ASP.NET. That's worth looking into, and would stop the notification from being lost due to unfortunate timing of the server being shut down immediately after sending the response but before sending the notification. It wouldn't help in the case where there are problems in sending the notification though, e.g. your mail server is down. At that point, the user has been told that the email is on its way, before you can really guarantee that... just something to think about.