I’m working on a webapp that provides a Log-in with Twitch, as the main authentication method, I’m both: using the oauth2 authorization server to manage my user’s sessions (maintaining state) and using sometimes some Twitch API endpoints with their user tokens. So I decided to go with the authorization-code flow.
The docs are very clear and helpful with the auth flow but when it comes to user tokens they always refer to the access/refresh token pair in singular and that makes the logic easier. But In practice users can login into a website from different devices/user-agents and they would get a different token pair for each one. What is the recommended flow then? Store all the tokens pair belonging to the user in the database? I believe that some time ago, logging into one device would invalidate the rest of your tokens, this is no longer the case, but that’s why I want to make sure that my approach is right, in case this changes again in the future.
My current planned architecture that I’m working on is something like the following:
- When a user logs in from a new user-agent via authorization code flow, store the user_id in an encrypted, httpOnly cookie, and also store the access/refresh token pair in the encrypted cookie to save some roundtrips when reading some requests (more specifically, the requests that will originate requests to Twitch API since in those cases we would get a 401 if it is invalid).
- Store in database all the tokens pair belonging to the user. One pair per device/user-agent. This is to maintain and validate session state for stateful features, as twitch requires.
- Have a service run every hour in theory (in practice the load is balanced across the hour minutes so I don’t validate all the users in the same minute). First delete all the expired access_tokens, then use /validate endpoint for every non-expired access token found for every user. Delete non-valid non-expired access tokens.
- When using features that use Twitch API: when the request comes in, read user_id and token pair from encrypted cookie, if it is expired try to refresh it with the refresh token, update the corresponding token pair in the database/cookie. Database is only needed for update if expired, not reading. Use Twitch API as expected, if 401 is returned anywhere, delete all the cookies and the corresponding token pair for the user in the database, requiring user to log in again in the corresponding device.
- When using local features that don’t need Twitch API and which are stateful; for example creating an item in my app or accessing a protected resource, check if user is still valid in my database to ensure my app hasn’t been disconnected from twitch (if it’s something really important/sensible, like payment, maybe check directly with twitch api instead of database). Check if the current access_token in the cookie matches a valid one in the database for the given user_id, if not try to refresh, if refreshed update db/cookies, if it returns 401 when using the refresh_token, delete all the cookies and ALL the tokens in the database, consider that the user has disconnected my app and redirect it to authorization code flow again. Since we run a service to validate them every hour an invalid token would last at most 1h.
Edit. Probably, it is way safer if refresh tokens are never stored in database (?)
- Is this the recommended flow? Is there any potential flaw in my architecture that you’d recommend changing? Since this is about security, I’m very interested in your opinions and other developers’ experiences.
- What is the rate limit for the /validate endpoint. As I said, I’m balancing the validations across the hour cycle period to be more gentle with twitch servers and not performing all the access_token validations at once, also I’m excluding expired access_tokens which I expect to lower the number of validations by a lot and also that would exclude non-active users, but I’m still interested in the rate limit so I can adjust better my balancer parameters and have plans to scale better, etc.
While the authentication examples are very helpful I miss some examples with the refresh token logic and mentioning what to do with multiple tokens from multiple devices/browsers, since the logic in practice is more complicated that the presented in the examples and the relation user:token is no longer 1:1, but 1:n, this changes many things and takes many considerations, many decisions directly related with security and left up to the developer’s interpretation.