Authentication Overview
The purpose of authentication is to verify the identity of a user. In
our application, resources are primarily owned by email addresses. If
a user is successfully able to claim an email by providing correct
password. Then he/she can get unvetted access to all resources belonging
to that email address.
Registration
Registration is the simple process of inserting a new user into the
system. In open-pocket, the registration follows this simple
procedure:
- user makes a POST request to
/registerendpoint with name, email, password. See example request below:
POST /register
{
"name": "test name",
"email": "test@example.com",
"password": "userpassworod"
}
- On receiving this request, the server:
- tries to insert the user into postgres database
- if no error occurs, a (201) CREATED response is sent
- If the user already exists in the system, the above insert fails, and server responds with (409) CONFLICT. Ensuring that no duplicate records are inserted
Login
At the /login endpoint, the user tries to proof, that they are the
owner of a particular email address. This can be done by 2 methods.
- providing
email,passwordin request body - you are already authorized because the server setted up an encrypted
cookie saying
authenticated=true - providing an authentication token in "Authorization" header
The primary method of authentication is through providing an email and
password. See below for an example request:
POST /login
{
"email": "test@example.com",
"password": "testpassword"
}
Upon receiving the request. The server does the following:
- try to retrieve the user from the database
- if user is not present or an error occurs -> return failure
- note the hashed password of the given user
- hash the password supplied in request and compare it with the
password_hash stored in the database
- if they do not match -> return failure
- the server sets an encrypted cookie (also called secure session),
having the following information:
- authenticated=true and
{ user_id }
- authenticated=true and
- the server then generates a jwt token and a refresh token
- the jwt token is created using
jwt.sign()method - the refresh token is created using
crypto.randomBytes() - in a server side key value store. It sets:
map[refresh_token] = {user_id}. - The previously generated refresh token (if any) is deleted using:
user_refToken_map[user_id] -> (refresh_token). Thendelete map[refresh_token]
- the jwt token is created using
- both jwt token and refresh tokens are send back
Refresh
The /refresh endpoint is responsible for generating a new set of
(access_token, refresh_token) using the old refresh token. Below is an
example request:
POST /refresh
{
"refresh_token": "6b2d08fc152adbb12949ba67f9d8ebc579b4dfffd75b79c2b8f9d18cf12929fd"
}
The server does the following:
- check if:
map[supplied_refresh_token] -> returns a { user_id }- if not return unauthorized
- else, generate a new pair of jwt token and refresh token, using
jwt.sign()andcrypto.randomBytes()respectively. - delete the old token using
delete map[supplied_refresh_token] - add new refresh token using:
map[refresh_token] = { user_id } - return the generated jwt token and refresh token
Userful links
TODOs
- refresh tokens should also have an expiration date
- keep the refresh token in a secure unencrypted cookie. For example a simple cookie with (secure=true, httponly, samesite=lax)
- what about the
/logoutendpoint - we should also ask for the
user_id/emailin/refreshendpoint. hence, requiring the client to tell which user are they claiming. Without this, an attacker might try to brute for all possible tokens and end up with user data of some users. - rate limit the endpoint (very imp.)