Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
I have often had to cope with sessions / access tokens expiring in an iOS app design and have never really quite found a design that I am 100% comfortable with, so I am asking this here to see if anyone can come up with a better design than I currently use.
The problem
You have an app that logs in with a username and password. The server returns an access token that should be used for future requests to authenticate that user. At some point in the future (unknown time) the server will expire that token and any request sent with that token will return an authentication failure.
After failure due to the session expiring, the app should re-login using the original credentials and get back a fresh access token. It can then retry the original request.
Example
So imagine you have an API to get a list of news articles that requires authentication. The flow might go like this:
User logs in and app receives token.
View controller refreshes list of news articles.
API request is made with token attached.
API request successful and view is updated with new articles.
App is closed and some time passes.
App is opened at which point the view controller wants to refresh the list of news articles.
API request is made with token attached.
API request is unsuccessful because token has expired.
View controller requests a refresh of the token and waits patiently.
Once token has been refreshed, API request is retried.
Now imagine that this is done from more than one place in the app.
How I currently solve it
What I usually do is store the credentials in NSUserDefaults (if I am not concerned with security of those credentials - obviously better to be using the keychain) and then have a method on a global manager object (singleton) which refreshes the login using these credentials when I notice that the session has expired. This global manager fires off notifications when the login state changes so that other parts of the app can know when they should retry a request after failure due to the session expiring.
What I don't feel is right
Well, I just never have liked the state machine handling of the manager object. Every place that performs a request needs to save some state to know that a login refresh is going on and to retry the request after the login has been refreshed. There's also the problem of what to do if the refresh fails because the password is wrong now (the user changed it maybe) - you probably don't want to log out completely and destroy all user state of the app, because you might just be able to ask for the new password and carry on as before. But the global manager doesn't really relate to UI so it's hard to it to handle the UI of asking for the new login.
What I want to know in answers
I understand that this question is particularly vague and conceptual (I still think it's OK to be on StackOverflow though?) but I'd really just like to know how other people solve this kind of problem. Just an explanation of how you go about handling the session expiration, retrying the failed requests from all over the app and asking the user for new credentials if refreshing didn't work.
I guess the crux of the whole thing is this question:
Where to put the retry logic of requests that failed due to session expiring. I see these places are options:
At the view controller level (i.e. keep a flag to say we need to retry the last request once login refresh has finished).
At the API request level (i.e. encapsulate the API request in an object that knows to retry after the login has been refreshed).
At the global login manager level (i.e. perhaps take a block in when refreshLogin is called that is executed once the login has been refreshed).
Not to raise this question from the dead, but since it hasn't been answered I'll give my input.
What I chose to do for this scenario was to store the creds in the keychain (or wherever, really), and then I subclassed an HTTPClient to check whether to refresh or not before every call. This way, I can identify a refresh is needed, perform it, and retry the call all in one step as well as being able to send an error block up the chain if necessary to handle any cases in which the user could NOT be refreshed accordingly.
This seems to be in line with what you are (or probably were) trying to accomplish. No need for notifications or any of that jazz, and you can write it once and reuse it throughout the entire app by sending your calls through that subclassed HTTPClient.
EDIT: Bear in mind, you should remember to allow any authentication calls to pass!
What you've asked is how to deal with session expiration. The others have answered your question. But I think you're asking the wrong question. Let me explain.
What we want is a design pattern for user sessions on iOS. We need to look at it from the point of view of the iOS device:
User installs the app
User authenticates with details and a session token is returned
The app then stores the (secret) session token on the device
Any future requests include the session token
Conclusion: Any API designed for iOS shouldn't expire the session token. It's simply kept secret on the device.
So from my experience the answer to your question about a design pattern for session expiration is basically:
Do not use session expiration when using an API for iOS.
One of the biggest iOS REST API's out there is doing it this way, and I would have to agree.
You are mentioning two things in your question which seem key to answering it:
A) the app is in a state with data that could be lost if the user is not logged in.
B) the app needs to log back in to save the data.
If these are the constraints you should work around them accordingly:
Devise a scheme in which you can save changes locally. Keep track of the syncing state with the server.
If login status changes and something unexpected happens, unobtrusively alert the user and give her the opportunity to fix it.
In my point of view, the correct place to put you logic is in the View Controller level.
If I correctly understood you question, you have a Network API that handles the server calls and return the result (possibly from a JSON) to the view controller.
The best approach should be to create a Login View Controller, with the username/email and password fields, separate from the rest of the application logic. After receiving the OK from the server, this view controller can be dismissed and the application will flow as intended.
If your token was invalidated, the server should return an Error 401 to your Network API.
You can simply encapsulate that error into an NSError object, and pass to the View Controller to handle.
Anywhere in the app, a server call can return Error 401, and as a result you apologize to your user and pull back the Login View Controller, to force a new token to be created.
I hope I helped.
My approach is similar to your '(2) At the API request level' - except rather than encapsualting the request in an object, encapsulate the whole concept of the API server.
Therefor I usually end up wrapping each type of request with an async method signature like this:
(Sorry, its C#, not obj-c (I use Xamarin.iOS) but the concept is the same - Action<T> is equivalent to obj-c Blocks)
void GetLatestNews(GetLatestRequest request, Action<NewsListingResponse> success, Action<Exception> error)
void Search(SearchRequest request, Action<SearchResponse> success, Action<Exception> error)
I usually just make these static methods on a static class (very rarely will there be more than 1 server of the same type so a single static class will work fine) but I do sometimes put them within a singleton instance class so that I can pass in a mocked instance for unit tests.
Anyway, now your VC client code just consumes the api as needed:
override void ViewDidAppear(bool animated)
{
SomeNewsSiteApi.GetLatestNews( new GetLatestRequest{Count=20},
response =>
{
// Update table view here
},
error =>
{
// Show some error alert
});
}
The beauty of this is that the implementation of those methods handle sending the request with the current token, and if it fails, obtains a new token, and then resends the same request with the new token, only then finally calling back into the Action<T> success callback. The client VC code has no idea about any re-obtaining of tokens, all it knows and cares about is that it is making a request for some data, and waiting for either a response or an error.
Facing the same issue from couple of months. My approach '(2) At the API request level'
Please let me know if you got the best solution to resolve this.
That will really help many people who are facing these issues.
refresh the auth token when session expire is very big solution.
Related
I'm trying to figure out the best method to exchange user information between a Spring application, and an Identity Provider.
To be specific, it's an application generated with jHipster, using Keycloak as the IdP.
The data exchange can be done easily with keycloak-admin-client, the problem is that after the update, the client still haves the same Access Token in memory, basically it's not in sync with the new data sent to Keycloak.
Do you know which is the best method to handle this situation?
I tried to invalidate the current access token, in order to force the client app to ask for a new one using the refresh token. But apparently there's no way to do this with the admin-client (at least, I couldn't find it).
At this point I don't see other options then asking for a new access token from client, once it sends an user data update request.
It doesn't seem a proper solution to me, I feel like maybe there's a more "elegant" procedure for this situations.
If you have any better solution, I'm all ears.
My question is about the MVC Antiforgery system (described here).
Consider a simple app which posts todos to /Todo/Create. The corresponding action method has the ValidateAntiForgeryToken attribute. Consider the following client workflow:
User A logs on and goes to the page to create a todo, but doesn't do it yet.
User B (physically on the same computer) opens a new tab in the same browser, logs out of User A's account, logs in as User B. The browser then gets User B's validation cookie.
Some time later, User A switches back to their original tab and hits 'create' on the todo they were making.
In this scenario, the Antiforgery verification will not pass because the form token was meant for User A, while the validation cookie is for User B.
I'm sure there are valid security reasons for this behavior (e.g. a script on another site that manages to login as malicious user so that the 'todo' data is posted to their account instead), but it doesn't stop the above scenario happening for my legitimate users sometimes.
My questions are:
Is there a 'best practices' way to handle this scenario? Is it usually just a case of showing a custom error, telling them to reload the page and/or login again etc?
Is there any way to know when the out-of-the-box MVC Antiforgery system runs into this error? It seems to only ever throw the same kind of Exception (HttpAntiForgeryException). Would I need to revert to using/modifying their source?
I see two ways of handling it:
Use Javascript callback to the server before hitting a button to detect if the user is still logged in. If not - display him a message. It should be relatively easy to do this. But it requires one additional call, and little bit more time to execute your request.
One solution to avoid callbacks could be using html 5 localStorage (and you can support that on other browsers using modernizr, for example). It is shared between tabs. But I'm not sure if this approach is good. Additional research required.
Catch HttpAntiForgeryException on the server, check if the user is logged in. If the user is not logged in, display him a message.
Usually approach (1) is used. On banking websites they detect with JavaScript when you logged out in other browser tab.
I am currently building a rails app, using the devise gem for authentication.
Currently in the app there is only client-side timeout functionality implemented, which I don't feel is a good final solution. Because it doesn't cover the case when a user puts their computer to sleep just for 1 example.
So I wanted to implement the timeout module in devise, however there are several issues I am facing. The issues are because with server-side timeout the user needs to navigate to a different page before they are redirected to the sign in page. And there are a lot of interactions in my app when a user will open a modal in the UI, which will trigger an AJAX call (which will fail if they have been timed out on the server-side).
Here are 2 approaches I have thought of, but I don't see them as good solutions so maybe someone can build on one of these approaches or help point me in a different direction:
1.) In my AJAX requests, add a handler inside the 'error' callback that will tell the user to refresh the page or go to the login page if the error callback returns a 401 Unauthorized response.
Cons: There are a lot of these ajax requests in the app, so there would be a lot of repetitive code and I see this as being difficult to maintain.
2.) Add a click handler to the body and every time it is triggered, send a request to the backend to validate if the user is still logged in. If they aren't redirect the user to the login page.
Cons: Performance issues
Any advice would be appreciated. Thanks.
You can add this from server site.
:expire_after: 120.minute
in your initializers/session_store.rb, Below example.
Tastebook::Application.config.session_store :cookie_store,
key: '_tastebook_app_session',
expire_after: 120.minute
I spoke with another developer who helped me come up with a solution to this issue.
With the client-side timeout, I had a countdown timer starting at 15 minutes, which obviously only works if the user's computer is not asleep.
So instead of a countdown timer, I create a new date object, setting the time to 15 minutes in the future (UTC). And then I set an interval which compares the current datetime (UTC) with the future date object and if it has been reach or surpassed it renders a message with a link to the sign in page. When this message is rendered is also makes a call to the backend to kill the user session to cover the case where the user tries to refresh their browser, which would then result in them being sent back to the login page.
Yes others have asked similar questions, however, the end solution was using JavaScript. I have that working, my question becomes what happens when the user has JavaScript turned off? I would hope only advanced users would be turning off JavaScript and thus know to know click once on a button and can tell that the server is working. On the off chance they don't, how do I make sure that button is only clicked once?
I should note that this is on a payment form.
I'm afraid without JavaScript there is no way to prevent this. If the click results in a POST request, then you can try to make it idempotent on the server side.
You cannot make sure the button is only clicked once, as you have no control over user's browser. What you can do, though, is to add a hidden field, a token to your forms so that if you see a token you've already seen, you'll be able to return an already-calculated answer.
Update: In case of payment processing, it's not even a technique for preventing double submission—it's a technique protecting your clients from fraud. Check out OWASP's A5: Cross-Site Request Forgery (CSRF):
Preventing CSRF requires the inclusion of a unpredictable token in the body or URL of each HTTP request. Such tokens should at a minimum be unique per user session, but can also be unique per request.
The preferred option is to include the unique token in a hidden field. This causes the value to be sent in the body of the HTTP request, avoiding its inclusion in the URL, which is subject to exposure.
The unique token can also be included in the URL itself, or a URL parameter. However, such placement runs the risk that the URL will be exposed to an attacker, thus compromising the secret token.
Basically, each time you receive a payment form, you want to make sure it's a legitimate response to the form you've shown. Handling double submission comes free with security—a rare case indeed! ;)
what happens when the user has JavaScript turned off?
The server is hit twice and there is not much you could do about it.
Now depending on what you are doing on the server there are different ways to react. For example in a RESTful application if you are using a POST verb which modifies some state on the server and is neither safe nor idempotent it is eventually the underlying data source that will detect the anomaly and simply throw an exception which will be gracefully reported to the user telling him that his request was already submitted.
For a simple and small ASP.NET MVC app, I find using the HttpRuntime.Cache is enough:
Function SomeAction() As ActionResult
Dim cachekey = "uniquecode"
If HttpRuntime.Cache(cachekey) IsNot Nothing Then
' wait until the other request is finished
Do
Threading.Thread.Sleep(1000)
Loop Until HttpRuntime.Cache(cachekey) Is Nothing
End If
HttpRuntime.Cache.Add(
cachekey, "", Nothing,
DateTime.Now.AddMinutes(5),
Cache.NoSlidingExpiration, CacheItemPriority.Low, Nothing)
Try
' do stuff
Finally
HttpRuntime.Cache.Remove(cachekey)
End Try
End Function
The cachekey must be the same for requests that you consider double, and must be different from all the other requests. If you check that the cachekey is already in the cache, tell the thread that is processing the request to wait (or throw error).
We are attempting to integrate an ASP.NET MVC site with our client's SSO system using PingFederate. I would like to use the built in FormsAuthentication framework to do this. The way I've gone about it so far is:
Set up my Web.config so that my FormsAuthentication LoginURL goes to my site's "BeginAuthentication" action on a "Security" controller. From this action, I set up some session variables (what URL was being accessed, for example, since Ping won't send this info back to me), and then redirect to our client's login page on an external site (www.client.com/Login for example).
From here, the authentication takes place and a cookie is generated on the same domain as the one that our application is running on which contains the unique identifier of the authenticated user, I've set it up so that once this happens, the Ping server will redirect to my "EndAuthentication" action on my "Security" controller.
In this action, I call my membership class's "ValidateUser" method which takes this unique identifier from the cookie and loads in the user on our application that this ID refers to. I save that logged in user in our Session (Session["LoggedInAs"], for example) and expire the cookie that contains the id of the authenticated user that the SSO system provided for me.
All of this works well. The issue I'm wondering about is what happens after our user has already authenticated and manually goes back to our client's login page (www.client.com/login) and logs in as another user. If they do that, then the flow from #2 above to number 3 happens as normal - but since there already exists an authenticated user on our site, it seems as though the FormsAuthentication system doesn't bother kicking off anything so I don't get a chance to check for the cookie I'm looking for to login as this new user. What I'd like to do is, somewhere in my Global.asax file (probably FormsAuthenticate_OnAuthenticate), check to see if the cookie that the SSO system sends to me exists, and if so, sign out of the application using FormsAuthentication.SignOut().
Another issue that seems to be related is that if I let my Session expire, the FormsAuthentication still seems to think I am authenticated and it lets me access a page even though no currently logged in user exists in my Session, so the page doesn't render correctly. Should I tap into the Session_End event and do FormsAuthentication.SignOut() here as well?
Basically, I want to know when the authentication ticket created by
System.Web.Security.FormsAuthentication.SetAuthCookie(..) gets checked in the flow of a request so that I can determine whether I need to SignOut() and force revalidation or not.
Thanks for any help. Sorry for the length of this message, trying to be as detailed as possible.
Mustafa
Welcome to the small section of Hades that is mixing session with formsauth.
If your needs are as complex as presented, you would get more sleep if you implement a full provider stack to share amongst the participating sites. Easier said than done, I know.
But to address your question:
from http://www.codeproject.com/Articles/39026/Exploring-Web-config-system-web-httpModules.aspx
On the way in....Check ticket and set identity #
app.AuthenticateRequest += System.Web.Security.FormsAuthenticationModule.OnEnter-->OnAuthenticate
On the way out... set the ticket and redirect as necessary
app.EndRequest += System.Web.Security.FormsAuthenticationModule.OnLeave
Reflector is your friend. ;-)
I don't know about a specific event for when the cookie is checked, but you could place the appropriate logic in Application_BeginRequest() and check the user's authentication state there.
Another issue that seems to be related
is that if I let my Session expire,
the FormsAuthentication still seems to
think I am authenticated and it lets
me access a page even though no
currently logged in user exists in my
Session, so the page doesn't render
correctly.
The life of the cookie (how long until ASP.NET feels it needs to ask for a password again) and how you are managing state are unrelated. The ASP.NET authentication is cookie based so that, should a developer want to, he could turn off viewstate, session, use no query strings or hidden fields and authentication still works.
If you want to tie the interval at which you request the password to how you are persisting data, then you will want your session expiration to be roughly the same as the cookie expiration, but they will never quite match up. It would be better to have two policies (one for how fast you throw away a users session data and one for how long you are willing to wait before you need to reask for a password)