Angular 4 application running in browser (website backend), displaying from the server data owned by particular user. Server: PHP+MySQL, Zend Framework 3 + Doctrine ORM
Naming:
access_token
: Short lifetime (1 min), allows to access personal resources, carries user_id, base64 encoded, json web token spec valid.refresh_token
: Long lifetime (1 week) allows to retrieve new access_token without providing credentials, stored in db, can be revoked by admin if needed.
Main point of using refresh_tokens is to be logged in longer than access_token
short lifetime (possibly forever if refresh_token
expiration time is updated each time user authorization happens), user needs to provide credentials only in case of inactivity longer than refresh_token
lifetime.
Refresh tokens are stored in db, so can be easily revoked.
1. Browser tries to authenticate
REQUEST:
- username and password
- sent to /api/auth
RESPONSE:
Username and password is validated and checked against database
If valid:
access_token
is generated with expiration time 60secuser_id
is encoded into access_tokenrefresh_token
is generated (random string) and saved to db, expiration time 1 week, (refresh_token is not included in access_token, it's a separate key)- HTTP 200 OK
If invalid:
- HTTP 401 Unauthorized
ACTION AFTER
IF valid:
- access_token and
refresh_token
stored in browser (private member varialbe of auth service, browser's local storage).
Looks like it's not a good idea to store refresh_token
in local storage - but this allows for "keep me signed in". If stored only per browser session in private member variable, user would need to log in every time opens the browser. Any ideas?
If invalid:
- display errors, suggest to retry
2. Browser requests protected data from server
REQUEST:
- sends access_token
- to /api/resource
RESPONSE:
- if access_token is valid, json data are sent, HTTP 200 OK
- if access_token is invalid (eg. unable to decode), HTTP 400 Bad Request
- if access_token expired, HTTP 401 Unauthorized
ACTION AFTER RESPONSE:
- if HTTP 200: data displayed
- if HTTP 400: redirected to login page
- if HTTP 401: retry to obtain new access_token using refresh_token stored in browser
3. Retry to autenticate using refresh_token (after HTTP 401 Unauthorized)
REQUEST:
- access_token
- refresh_token
RESPONSE:
- Validate
access_token
(everything except expiration time, using \Firebase\JWT) - Validate
refresh_token
against database (user_id decoded from access_token, the string and expiration time)
If valid:
Generate new refresh_token, save to database, update ttl (or should be the same token, only updated ttl?) Generate new access_token HTTP 200 OK
If invalid:
HTTP 401 Unauthorized
ACTIONS AFTER RESPONSE:
If valid:
- store the new access_key and refresh_key in browser
- retry the previous resource request using new access_key
If invalid:
- display login page
Questions
The key point I don't like is that access_token and refresh_token are stored in the same place and sent the same way. Maybe there is another way to do it?
- is this logic sensible?
- are there any security flaws, assuming this happens in web app?
- should I store both the tokens in browser's local storage?
- should
refresh_token
be encoded inaccess_token
? If they are still saved in the same place, looks that it may be incorporated into access_token. Any reasons not to do it? - when should I update
refresh_token
lifetime? - any open source projects to see similar authentication in action?
- any other suggestions?