Authentication and Authorization Implementation From Scratch

Himang Sharatun
kudos to you
Published in
5 min readNov 15, 2020

Recently, I’m involved in development of a service that handle authentication, but since I have 0 knowledge and experience about it I need to learn by reading several books and article about authentication and authorization. This article will try to summarize what I learn from that experience, by explaining train of thought of implementing authorization in perspective of someone who know nothing about it.

Requirements

Let’s say we have this following endpoint to get user profile:

route: GET /users/:id/profile
response:
{
id : integer,
username : string,
email : string,
address : string,
profile_picture: string,
date_of_birth : string
}

This endpoint expose user’s private data to public and need to be secured properly. We do not want that anyone else but the user itself able to access this endpoint. It means that minimum requirement for this endpoint are:

  1. User with id 1 only authorized to access GET /user/1/profile and will be rejected when trying to access GET /user/2/profile or any other user profile
  2. To prove that user that trying to access is really user with id 1, user need to login first as authentication process

To fulfill this requirement let’s assume that user’s password is secured, not easily guessed and not leaked to other people. This assumption will help us to focus more on securing the process authentication instead of to cover every possible loophole in the system including the user.

Basic Flow

Because we need to ensure no one else but profile owner able to access profile endpoint, we need a mechanism that will enable us to distinguish who send the request or basically we need determine how we authenticate our user. The simplest way authenticate a user is by ask the client app to send header containing following data:

{
user_id : integer,
is_login: boolean
}

After this let’s call this json data as credential and in this case the credential is very straight forward. We authenticate user simply by matching the user_id in the credential with the user_id in the resource. If it doesn’t match reject the request, if it is then return user profile. The flow of such authentication flow can be

But this flow is definitely not secure at all. Now imagine you are in the position of someone that want to obtain user data from this system without having any access at all to user password and database credential. This will be an easy task since using postman or curl you can hit GET/users/:id/profile and simply put the same:id in the request params. It is even possible for you to obtain all user data, simply by using simple id loop to hit GET/users/:id/profile and store it somewhere else.

Therefore in this article what we are going to talk about is how we can safely store credential in the client side but still able to authenticate it in the backend side.

Encrypting the Credential

The simplest fix for the problem mentioned above is by encrypting user_id and is_login status. For example we can use hash algorithm to encrypt it. Hash algorithm rely on hash value to provide security for the encryption. As long as someone doesn’t know what is the hash value used to encrypt the data, it is impossible to decrypt hash encrypted data. This hash value could be anything you define during the encryption and relatively safer since we can make the hash value to never leave our backend side. For example take a look of this JWT encrypted credential:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJpc19sb2dpbiI6dHJ1ZX0.eT4te-JflFOjgqZQA0QwelK7FwK8BLDa1bYx_wGpNgk

Using simple site such as jwt.io you might be able to easily decrypt that it is this following data:

{
user_id : 1,
is_login: true
}

But event though you know it you will not be able to make the backend side to acknowledge this as valid request. If you using jwt.io you might find error Invalid Signature as follows:

This happen because you are trying to encode the credential without knowing what is the hash value I use to generate it. Now, try to change your-256-bit-secret into NowYouSeeMe then paste the credential.

Now, as you can see that there is Signature Verified info that indicate that this credential is a valid one. This show us that even tough you know the real data structure of our credential and user_idof other user, you will not be able to generate a valid credential unless you know the hash_value which in this case is NowYouSeeMe

Implement Expiry Time

Now let’s another layer of security by implementing expiry time for our credential. In the very worst case scenario where user’s credential fall to malicious party, we need a way to limit the time window of the malicious party to access our user resource. Without expiry time other people can access user resource forever and user need to make new account if he want to continue using our application, in which not many user want to do that knowing that it might happen again.

To implement expiry time is very simple, instead of simply encrypt user_id and is_login status, we add new field called expiry_timeas follows:

{
user_id : integer,
is_login: boolean,
expiry_time: time
}

This expiry time allow us to invalidate credential that has been use within certain amount of time for example 1 days, 12 hours etc. So after parsing the data contained inside the credential, our service MUST validate that the credential is still during the expiry_time .

Implement Refresh Token

Now we face a quite bit of dilemma, if we want to limit time window of the attacker we will need expiry time as short as possible. But if we have very short expiry time it will be a hassle for user to login every single hour when token is expired. permited

To bridge the compromise between security and user convenience, we will implement refresh token which basically are token with longer expiry time that is used to generate new access token when the old one is expired. The idea is when our service (BE) side receive an expired token, it doesn’t directly return unauthorized response (401) but it will first try to see if the user has a valid refresh token, then if user has one, it will generate new access token.

Ideally, this refresh token should not leave our BE side, meaning that even client side app shouldn’t locally store or even has access to user refresh token. Refresh token should be only stored in BE side whether you use MySQL, redis or any kind of data storage. Personally, I prefer to use redis to store refresh token since it will make it easier for us to set the expiry time for the refresh token.

--

--