title: Session expire design pattern
body: <p>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.</p>
  
  <h2>The problem</h2>
  
  <p>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.</p>
  
  <p>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.</p>
  
  <h2>Example</h2>
  
  <p>So imagine you have an API to get a list of news articles that requires authentication. The flow might go like this:</p>
  
  <ol>
  <li>User logs in and app receives token.</li>
  <li>View controller refreshes list of news articles.
  <ol>
  <li>API request is made with token attached.</li>
  <li>API request successful and view is updated with new articles.</li>
  </ol></li>
  <li>App is closed and some time passes.</li>
  <li>App is opened at which point the view controller wants to refresh the list of news articles.
  <ol>
  <li>API request is made with token attached.</li>
  <li>API request is unsuccessful because token has expired.</li>
  <li>View controller requests a refresh of the token and waits patiently.</li>
  <li>Once token has been refreshed, API request is retried.</li>
  </ol></li>
  </ol>
  
  <p>Now imagine that this is done from more than one place in the app.</p>
  
  <h2>How I currently solve it</h2>
  
  <p>What I usually do is store the credentials in <code>NSUserDefaults</code> (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.</p>
  
  <h2>What I don't feel is right</h2>
  
  <p>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.</p>
  
  <h2>What I want to know in answers</h2>
  
  <p>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.</p>
  
  <p>I guess the crux of the whole thing is this question:</p>
  
  <p>Where to put the retry logic of requests that failed due to session expiring. I see these places are options:</p>
  
  <ol>
  <li>At the view controller level (i.e. keep a flag to say we need to retry the last request once login refresh has finished).</li>
  <li>At the API request level (i.e. encapsulate the API request in an object that knows to retry after the login has been refreshed).</li>
  <li>At the global login manager level (i.e. perhaps take a block in when <code>refreshLogin</code> is called that is executed once the login has been refreshed).</li>
  </ol>