Perhaps I am misunderstanding how this works, but I thought the OnValidateIdentity would be called on every HTTP request to the server. I see it get hit on initial login (a bunch of times, so it looks like it is every HTTP request on initially), but then it doesn't seem to get called again. I let my app sit there until it should be expired, but it never gets hit again on subsequent requests to the server, until I logout and login again.
I have set my expiry very low and turned off sliding expiration to see if I could get it to fail, but to no avail.
Should I not see the OnValidateIdentity get called on every HTTP request? Ultimately, I really just want a cookie expiry to result in a logout. I assumed I had to check the expiry on each request in the OnValidateIdentity, but if this is not the way to do it please let me know!
Am I misunderstanding how this work, or how I should be using cookie expiries to force a logout?
Here is my basic setup:
app.UseCookieAuthentication(New CookieAuthenticationOptions() With
{
.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
.AuthenticationMode = AuthenticationMode.Active,
.ExpireTimeSpan = TimeSpan.FromMinutes(1.0),
.SlidingExpiration = False,
.Provider = New CookieAuthenticationProvider() With
{
.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUser, Integer)(
validateInterval:=TimeSpan.FromMinutes(1.0),
regenerateIdentityCallback:=Async Function(manager, user)
Return Await user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie)
End Function,
getUserIdCallback:=Function(id) id.GetUserId(Of Integer)())
},
.LoginPath = New PathString("/Account/Login")
})
Probably a bit late...
To use cookie Expiry, that's delegated to the browser. If a cookie expires, then the browser wouldn't send it.
OnValidateIdentity is called on every request. If you want to debug it, pull it out into a new method and debug from that method instead.
However, the identity itself is only validated against the database when the validationInterval is passed.
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 have a question concerning a react native app and doing network requests.
I'm creating a react native app and as a backend I'm using NodeJS.
Users can sign up/sign in, and the refresh token is stored. Everytime the app opens, a new refresh token is fetched (if the user is signed in). This is an example of a network request that happens every time the app opens.
When the app opens and the user is signed in, I do a second network request (after the first network request for the access token was successful), to check if the data in redux has to be synchronized with the data in the database. I store some general information like the username locally + in the database. If - for any reason - I have to change the username in the database, I set the field "shouldSync" to true. The app sends a request to /api/sync, the controller sees that shouldSync = true and sends back all the data from the database to the user.
I also have a third network request that runs every time the app opens up: to check if there is a new version. I do a request to /api/version, with a response like "1.0.0". If the local version differs from the version in the response, I show an alert (only one time) saying there is a new version in the app store/play store.
When the app closes/goes in background mode, I also do a network request to the server to store the last active timestamp.
All these requests work, and I don't see any bottlenecks yet, but I have never built a production-graded app, so I have a few question concerning these network requests. I hope somebody can answer them.
Is it ok to do 3 separate network requests, or should I try to combine the three requests into one? I could combine the access token request + sync request (because these run when the user is signed in), but NOT the version request (because this request always has to run).
On some screens, I need dynamic data from the database/server. Sometimes I have 3-5 separate requests running at the same time. I'm using axios and I've made my code like this:
// first request
const requestOne = () => {
return axios({ ... });
};
// second request
const requestTwo = () => {
return axios({ ... });
};
// run these requests when the screen mounts
useEffect(() => {
axios.all([requestOne(), requestTwo()])
.then(axios.spread((requestOneRes, requestTwoRes) => {
// response from request one
console.log(requestOneRes.data);
// response from request two
console.log(requestTwoRes.data);
}));
}, []);
Is this a good example of combining multiple requests?
The most requests don't have a lot of response data. I have tested some requests in Postman and I've checked the response sizes of some requests, I've listed them here:
Everytime the app opens up:
/api/version: 463B request size / 231B response size
When the user signs in or signs up:
/sign-in: 476B request size / 1.16kB response size
/sign-up: 472B request size / 408B response size
When the app opens up + when the user is signed in:
/api/version: 463B request size / 231B response size
/refresh-access-token: 649B request size / 521B response size
/api/sync: 633B request size / 244B response size (NOT SYNCING) or 592B response size (SYNCING)
Total: +/- 1.6kB request size / +/- 1.25kB response size
I'm using express and BodyParser and I've set the limit to 2MB (2,000,000B).
app.use(bodyParser.json({ limit: 2000000 }));
I have installed request-stats in my NodeJS-backend to log all incoming requests. I'll all parts of my app separately and log all (big) requests, but for now I don't see any requests > 10kB, and even these are very rare.
Sorry for the long text. I want to be as clear as possible.
One recommendation, depending on the nature of your auth, is to remove the refreshToken expiry. Technically, the refresh token should be the retainer for the generation of new accessTokens (of which have an expiry). Refresh tokens themselves should not be regenerating without a relog by the end user. So instead of the user getting a new auth token, my way of assessing requests is 1. attempt the desired request, upon a 400 error, assess the response in axios for handling. If it is the result of an invalid/expired access token, request for a refresh with the stored refresh token. 2. Resend the prior request. This system removes the need for unnecessary request handling upon app start. Good luck!
I'm busy writing a test harness that will validate a REST API I've been developing. In the normal usage the my REST API will be used by a Web application that is secured and authenticated by OneLogin. My test harness will therefore have to use OneLogin to authenticate before I can call my own functions. I'm struggling to understand the right workflow for logging through an API as my test harness does not have a browser. So far I've got:
Request an Authentication Token using the Client Id and Secret (https://developers.onelogin.com/api-docs/1/oauth20-tokens/generate-tokens)
Request a Session Token using the Authentication Token, Sub-Domain, user name and password (https://developers.onelogin.com/api-docs/1/users/create-session-login-token)
I'm not quite sure what do do with the Session Token. I suspect I might have to create a session (https://developers.onelogin.com/api-docs/1/users/create-session-via-token) but that appears to be using a different URL.
Any ideas?
Updated:
Here is the code that I'm using for that final step. I pass in the Session Token obtained in the previous step.
/** see https://developers.onelogin.com/api-docs/1/users/create-session-via-token */
public void createSession(String sessionToken) {
HttpPost httpPost = new HttpPost("https://admin.us.onelogin.com/session_via_api_token");
List<NameValuePair> postParameters = new ArrayList<>();
postParameters.add(new BasicNameValuePair("session_token", sessionToken));
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
HttpEntity entity = new UrlEncodedFormEntity(postParameters);
httpPost.setEntity(entity);
HttpResponse response = httpClient.execute(httpPost);
int status = response.getStatusLine().getStatusCode();
System.out.println(status);
} catch (Exception e) {
e.printStackTrace();
}
}
The status code returned is 302 and the response headers contain Location: https://<my-company>.onelogin.com.
I'm taking this to mean that the last call has failed for some reason and I'm being redirected back to the log-in page.
You suspect right.
Basically, after your server makes the back-channel requests for the token, you have to hand the token off via the front channel (user's browser) so you can establish a session, get cookies, etc... using the session_via_api_token 'endpoint' (which is a totally different sort of API from the ones to get the token in the first place)
I've implemented sliding sessions in my Relying Party application, as described in Sliding Sessions for WIF 4.5. That works great as far as it goes, but there's one problem that it seems nobody talks about.
As the linked blog post points out, when the RP token expires, the next time make a request the token is re-issued from the STS. Assuming, of course, that the STS session lifetime is longer than the RP's session lifetime, which is almost certainly the case if you're implementing sliding sessions.
In any event, that completely defeats the whole point of sliding sessions.
What nobody seems to talk about is what to do when the RP session expires. What I want is, if the RP session times out (usually because somebody walked away from his desk for 10 minutes), is for my application to redirect to the STS login page where the user can re-authenticate, and then be redirected back to the page I had requested; or perhaps to the page that I was on when I made the request.
I'm almost certain that this is possible, but I have absolutely no idea how it's done.
Here's my code from global.asax:
private const int InactivityTimeout = 5; // minutes
void SessionAuthenticationModule_SessionSecurityTokenReceived
(object sender, SessionSecurityTokenReceivedEventArgs e)
{
var now = DateTime.UtcNow;
var validFrom = e.SessionToken.ValidFrom;
var validTo = e.SessionToken.ValidTo;
double halfSpan = (validTo - validFrom).TotalMinutes/2;
if (validFrom.AddMinutes(halfSpan) < now && now < validTo)
{
// add more time
var sam = sender as SessionAuthenticationModule;
e.SessionToken = sam.CreateSessionSecurityToken(
e.SessionToken.ClaimsPrincipal,
e.SessionToken.Context,
now,
now.AddMinutes(InactivityTimeout),
e.SessionToken.IsPersistent);
e.ReissueCookie = true;
}
else
{
// re-authenticate with STS
}
}
My questions:
Is the else clause the proper place to put the re-authentication logic?
If so, please provide an example, 'cause I have no idea.
If the answer to #1 is no, then is there a separate event I need to subscribe to that will tell me "Hey, your session security token has expired!"?
I'd recommend you sync the session lifetimes on the STS and the RP(s).
You can set the session lifetime to 10 minutes on the STS and 10 minutes on the RP and use the sliding session approach on the RP. After 10 minutes of inactivity both sessions would expire and the user should be required to re-authenticate.
If you have multiple RPs you could implement a form of keep-alive from the RP to the STS - e.g. load a resource from the STS in every webpage on the RPs. Whenever a page is loaded on an RP, the keep-alive resource would be loaded from the STS - refreshing the STS session. After 10 minutes of inactivity they would both time out and the user would have to re-authenticate.
"A resource from the STS" could mean a web page (Web Forms/MVC) loaded in an invisible iframe. The important thing is that it's a managed handler so the request is handled by ASP.NET.
As for your questions, if you sync the session lifetimes so they time out together:
No, you don't need to add any code in the else clause. If the token is expired, WIF will redirect to the STS.
Just remove the else clause.
Let WIF handle this for you.
For completeness, if you can't sync the session lifetimes you could trigger a federated sign-out when the RP session expires. The following snippet triggers a signout at the configured Issuer (STS). You could put this in the else clause to trigger a signout on the first request after the RP session expires:
using System.IdentityModel.Services; //WIF 4.5
var stsAddress = new Uri(FederatedAuthentication.FederationConfiguration.WsFederationConfiguration.Issuer);
WSFederationAuthenticationModule.FederatedSignOut(stsAddress, null); //Optional replyUrl set to null
Hope that helps!
I would like to know which http method I should use when doing a login request, and why? Since this request creates an object (a user session) on the server, I think it should be POST, what do you think? But since the login request should be idempotent, it could be PUT, couldn't it?
Same question for a logout request, should I use the DELETE method?
If your login request is via a user supplying a username and password then a POST is preferable, as details will be sent in the HTTP messages body rather than the URL. Although it will still be sent plain text, unless you're encrypting via https.
The HTTP DELETE method is a request to delete something on the server. I don't think that DELETING an in memory user session is really what it's intended; more it's for deleting the user record itself. So potentially logout can be just a GET e.g. www.yoursite.com/logout.
I believe that you can translate LOGIN & LOGOUT methods into basic CRUD operations CREATE & DELETE. Since you are creating a new resource called SESSION and destroying it when logging out:
POST /login - creates session
DELETE /logout - destroys session
I would never do LOGOUT as GET just because anyone could make an attack just simply by sending an email with IMG tag or link to website where such an IMG tag exists. (<img src="youtsite.com/logout" />)
P.S.
Long time I was wondering how would you create a RESTful login/logout and it turned out it's really simple, you do it just like I described: use /session/ endpoint with CREATE and DELETE methods and you are fine. You could also use UPDATE if you want to update session in one way or another...
Here is my solution based on REST guides and recommendations:
LOGIN - create a resource
Request:
POST => https://example.com/sessions/
BODY => {'login': 'login#example.com', 'password': '123456'}
Response:
http status code 201 (Created)
{'token': '761b69db-ace4-49cd-84cb-4550be231e8f'}
LOGOUT - delete a resource
Request:
DELETE => https://example.com/sessions/761b69db-ace4-49cd-84cb-4550be231e8f/
Response:
http status code 204 (No Content)
For login request we should use POST method. Because our login data is secure which needs security. When use POST method the data is sent to server in a bundle. But in GET method data is sent to the server followed by the url like append with url request which will be seen to everyone.
So For secure authentication and authorization process we should use POST method.
I hope this solution will help you.
Thanks
Regarding the method for logging out:
In the Spring (Java Framework) documentation, they state that a POST request is preferred, since a GET makes you vulnerable to CSRF (Cross-Site Request Forgery) and the user could be logged out.
Adding CSRF will update the LogoutFilter to only use HTTP POST. This ensures that log out requires a CSRF token and that a malicious user cannot forcibly log out your users.
See: https://docs.spring.io/spring-security/site/docs/current/reference/html/web-app-security.html#csrf-logout
Logging in should also use POST (body can be encrypted, see the other answers).
For Login I use POST, below is my code for LOGIN method
I used Nodejs with Express and Mongoose
your router.js
const express = require("express");
const router = express.Router();
router.post("/login", login);
your controller.js
export.login = async(req, res) => {
//find the user based on email
const {email, password} = req.body;
try{
const user = awaitUser.findOne({email});
if(user==null)
return res.status(400).json({err : "User with
email doesnot exists.Please signup"});
}
catch(error){
return res.status(500).json({err :
error.message});
}
//IF EVERYTHING GOES FINE, ASSIGN YOUR TOKEN
make sure you have JWT installed
const token = jwt.sign({_id: user._id}, YOUR_SECRET_KEY);
res.cookie('t');
const {_id, name, email} = user;
return res.json({token, user : {_id, email, name}});
}