Identity Server and two clients (SSO): .Net Core MVC and Nodejs.
When I log in with Nodejs client, after refresh MVC (second client) I got logged MVC client. It's good.
But when I logout from Nodejs it send back-channel logout url to MVC client. Nodejs doesn't have problems with logout. But MVC client - after browser refresh it stay logged.
I read this and this posts but they didn't help.
When in MVC Startup i wrote this code:
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = (e) =>
{
e.Properties.IsPersistent = true;
e.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(2);
return Task.CompletedTask;
}
};
After two minutes I refresh browser and MVC redirect to Idrsv login page. Its good, but not safe (need to wait 2 minutes).
I read about userId claim cache but I doubt - if it will be a lot of active sessions, then cache will be very big and app will work slowly.
I can do with front-channel logout, but I read about cons, and now I doubt.
What do you prefer for logout all clients from Identity Server?
In the samples the logout is performed using iframes on the logged out page. If this page is skipped or aborted, clients may not be informed. But I don't think that's the case.
I'd rather prefer a safer backend logout that doesn't rely on iframes. Take a look at my answer here for an example.
Now about the client. Non-javascript clients do need a roundtrip to update the cookie. So the flow is: user logs out from client A. IdentityServer informs other clients (backchannel) and removes server cookie.
And now the (non-javascript) client has to take action. It also needs to remove the cookie, but that is only possible after the user performs an action.
And that's where the caching comes in. The cache only contains the alert from the server. On the first opportunity it removes the cookie and also removes the user from the cache. So the cache will in fact stay quite small. Do add some cleanup code to remove logged out users (that never returned) with cookies that are expired.
Related
We use IdentityServer4 in our .NET solution, which also includes AspNetCore Web API and Angular front-end app.
There are standard middleware settings to setup identity like app.UseAuthentication() / app.UseAuthorization() / etc. (it's actually an ABP framework-based solution). As a result of these settings, all authentication tokens (access_token, refresh_token, etc.) are stored in Local Storage (I have not found where exactly I can select between Local Storage and other kinds, BTW).
Anyway, it has worked somehow in our DEV environment.
But suddenly the need to use window.open from Angular app popped up. This page is a Hangfire dashboard. Which accesses other resources related to dashboard functionality. And it caused a lot of headache: now, to identify user in server page called from window.open we need to use cookies (URL is not considered of course).
does it mean we have to switch fully from Local Storage to Cookies for storing tokens?
how and where to set it up? Or, if it's not too wild and senseless to just copy existing access_token to Cookies - when and where to do that? My idea was to copy access_token when it is created in Local Storage to Cookies and delete when a user logs off and probably under bunch of different conditions, like browser window is closed, etc. (which ones exactly?)
probably to set refresh_token to be stored in Cookies and read it in the mentioned server page, then obtain access_token by it (if it makes sense at all)
UPDATE: i've finally came up with the following, but it does not work. The idea is: when Angular app makes request to back-end and authentication token is already present - the token needs to be saved to cookies. I see the cookies is added. But later on - on next request - it falls into the condition again, because actually the given cookie is not saved:
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
if (context.SecurityToken is JwtSecurityToken accessToken && !context.HttpContext.Request.Cookies.ContainsKey(accessTokenCookieName))
{
context.HttpContext.Response.Cookies.Append(
accessTokenCookieName,
accessToken.RawData,
new CookieOptions
{
Domain = context.HttpContext.Request.Host.Host,
Path = "/",
HttpOnly = true,
SameSite = SameSiteMode.Strict,
Secure = true,
MaxAge = TimeSpan.FromMinutes(60)
});
}
return Task.CompletedTask;
},
...
}
All apps are web application sharing the same domain. And CORS is set up. AllowCredentials() is called as well when setting up middleware.
I am going to implement JWT authentication for several independent services.
There will be auth.example.com and service1.example.com, service2.example.com etc.
My assumptions:
JWT can be kept in cookie for ".example.com"
JWT expire time should be small (like 15 mins) because there is no reliable way to logout user with JWT token (revoke token).
Refresh tokens should be used to reissue JWT tokens
Refresh token cookies should be accessible only by auth.example.com for security reasons and because https://www.rfc-editor.org/rfc/rfc6749#section-1.5 says
"Unlike access tokens, refresh tokens are intended for use only with authorization servers and are never sent to resource servers."
Next, if I have a service - multi page application (i.e. not SPA), where some URLs are called "traditional" way, not via Ajax and render HTML
based on some server side logic, which, of course, include checking of user authorization.
then, say, there will be an action service1.example.com/user/showpage
if (user.logged_in) {
render_some_html(get_some_data(user.login))
}
else {
render_anonimous_uses_page()
}
Problem is:
If site user close all site tabs and, then after hour or so, go directly to page /user/showpage (or maybe he
suspend laptop and wake it up in an hour and go to that page).
What if by that time JWT token will expire. Then to refresh it by Refresh token we need to make Ajax call to auth.example.com (because Refresh
token is stored only in auth.example.com cookie) and this is just unaccessible in server side rendering (that pseudocode that I posted above, it's server side, and it's just impossible to make client ajax call in the middle of execution of server code. it's just not applicable here). This way user will be considered logged out
on this stage.
Redirect could be one solution.. but what if site should work for anonymous out users too, and anyway looking for something better.
This problem not exists for SPA application, because before every Ajax call to internal API, it can check JWT and make call to refresh JWT token.
And question is: is this true that JWT in general should not (cannot) be used in Multi-Page (traditional) applications because of this issue? or there is good way to workaround this? or this is not a problem at all (users don't close tabs too often, or they expect site to log them out or redirect etc)?
Currently, I am using ID4, asp.net core and angular 2. Everything works well but I am facing one issue, when we open many tabs on browser. Then, we logout on one tab but other tabs still keep token and call API successfully. Can we force or validate token and return to login page on all tabs?
My code to sign out on server
await _signInManager.SignOutAsync();
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
var logout = await _interaction.GetLogoutContextAsync(model.LogoutId);
return Redirect(logout?.PostLogoutRedirectUri);
Thanks,
You cannot revoke JWTs: once issued they are valid until they expire. So if you are using Identity Server to issue JWT access tokens then you what you ask isn't possible, so long as something has the JWT (and it hasn't expired) then it can be used to gain access to your resources.
Instead you could change to using Reference Tokens, which aren't self-contained and upon each use will cause the receiving resource (such as your API) to verify it with the IdSvr. One of the effects of this is that you can then revoke your access tokens, which means it doesn't matter if other tabs think they have the access token because when they try and use it the IdSvr will know that they are no longer valid. If you go down this route you will now need to persist your access tokens, and each protected call to your client will involve it doing a back-channel communication to your IdSvr.
I have a React app rendering client-side, in which I handle authentication the following way:
Upon loading, the app fires an AJAX request to the backend, basically asking whether the user's session is valid ;
It updates the app's state with the server's response ;
It renders the "/" route accordingly (the homepage if the session is invalid, a dashboard if it is valid).
(Maybe there are better solutions for handling this in front-end applications, I'm all ears if you have ideas)
This works pretty well, but introducing Service Workers into the mix and trying to turn the app into an offline-first progressive web app seems... complicated.
On the one hand, if I don't cache the "Am I logged in ?" request and the app is offline, the app will always render the homepage.
On the other hand, if I do cache the AJAX request, the users will eventually be shown an empty dashboard because their sessions will have expired and the server will be throwing 403s.
Is there a way to handle this effectively?
I solved my problem by taking a different approach: I now persist the state in localStorage.
This way, when the user arrives on the app, he is presented with stale data from his last visit. Meanwhile, a "Am I logged in?" request is fired in the background.
If it is succesful and returns true, the other AJAX requests get fired and fill the app with fresh data ;
If it is successful and returns false, the state is updated accordingly and the user redirected to the homepage ;
If the request is unsuccessful (i.e. the app is offline) the app keeps showing stale data from last session in the dashboard. We don't know if the user's session is still valid, but we can't retreive any data so it does not matter.
One way of doing it is adding a /verifyToken (assuming you are using some kind of token to validate the session) in your back-end api to check if the token is valid.
So you cache your session token. If the app is offline it shows the dashboard.
If the app is online, you fire a request to /verifyToken to check is the session is still valid. If it is then you continue to dashboard. If it isn't you redirect them back to homepage (or the sign in page).
Edit:
When your app is online, you can technically fire a request to any authorized route and check if the response was 403 (in case you can't modify the backend). If it is then you can send them back to sign in page.
Currently when a user logs out the log out process works correctly but the user stays on the same screen and therefore can still see secure data.
What is the best practice for forcing a browser redirect after logging out of ServiceStack?
It's not possible for the server to enforce a client redirect. It's really up to the client to enforce the security for the data after you logout. If the client has been trusted with the secure data already, during the course of the session, then you need to trust that the client will secure it appropriately when the session ends.
While you can have ServiceStack send redirect headers to the client when logging out, there is nothing requiring the client to actually take that action.
If a JavaScript client makes an AJAX request to ServiceStack to logout, the redirect response doesn't affect the page displaying the secure data, because the AJAX request operates effectively in a separate scope from the page showing the data, and so that page remains unaffected by the redirect. So the redirect is useless, unless the client explicitly provides a mechanism to handle such event.
The client must take responsibility to navigate away from the secure data itself. The best practise would be:
In the success method of your call to the logout action, you should:
Dispose of any sensitive in memory data. i.e. JavaScript Variables / DOM Elements displaying the data.
Delete the session cookie
Redirect to login
If you have secured your service properly, then navigating back through the history should still trigger a session check, for which there will no longer be a valid session, and you should be redirected away.
You previously mentioned using AngularJS. If you were doing this with the $http service, then the success callback can be used, like this:
$http({method: 'POST', url: '/auth/logout', data: { provider: "logout" }}).success(
function(data, status, headers, config) {
$scope.someValue = null; // Remove sensitive values from the scope (though it should be cleared up anyway with the redirect to a different state)
$cookieStore.remove('ss-id'); // Remove the cookie
$state.transitionTo('login'); // Redirect to login state
}
);
This example assumes you have injected the $http, $cookieStore, $state providers
tl;dr
The client must enforce the security. The server redirect should be treated as nothing more than a suggestion to the client.
Use the success callback of the logout request action to delete the session cookie, dispose of any values in memory and redirect away from the data.
Hope this helps.