Building a User Auth System With JWT Using Golang
Hi learners, i will try to document all my learnings from the journey i tried building a user authentication system with JWT using golang with a hope it helps people as a good reference.
What is JWT?
For the audience who are not much confident about the word “JWT”, JWT expands out as JSON Web Token. JWT is an approach for authenticating a user in server side.
In traditional approach, we have sessions for authenticating users, where on a successful login we will create a new session with that user’s detail and store it in server storage. We then send back the created session’s ID to the client. The client then sends us the session ID with subsequent requests so we can get the identity of that user by querying the server storage using the session ID. This approach had more complications in scenarios where our server services are distributed(as in Microservice Architecture) with separate storage for each service.
JWT comes as a rescuer for building applications using MSA(microservice architecture). In JWT approach, the information is stored only on the client-side. JWT is simply a JSON payload that contains some claims about a user. The key property of JWT is that the token itself contains all the required details that are needed for validation as they carry a Message Authentication Code(MAC). JWT is comprised of three parts namely header, payload and signature that are dot seperated.
In the above example, the part before the first dot is the header, the part b/w first dot and second dot is the payload and the last part is the signature. The above token is just a Base64Url encoded version of the JSON data. We can head to jwt.io and decode the above token to view the JSON data.
Let go over what each part in the JWT contains:
Header : The header contains some metadata about the token like the type of signature used, etc. The decoded JSON data of the above token is below,
Payload : The payload contains identification information about a user. The decoded JSON data of the above token is below,
“name”: “John Doe”,
Signature : The signature is the Message Authentication Code(MAC) that we discussed above which helps in the validation of the token. The signature is created by combining the base64Url encoded format of the header and the payload and signing it with a secret key in the server. For example, in HS256 signature typed JWT, the signature is created as follows,
signature = hmacsha256(encoded_header + “.” + encoded_payload, “server_secret”)
where hmacsha256 is a kind of hashing algorithm. This signature can only be produced by someone who is in possession of the header, payload and secret key. Hence during authentication, upon receiving the JWT , the server will create a new signature with the encoded header and payload present in the obtained JWT and sign it with the secret key present in the server. It then compares this newly created signature with the signature that is present in the obtained JWT. If it matches, the server considers the token as valid and proceeds with the request by taking the user identification in the token payload.
The basic flow of a JWT authentication system is a follows:
- Client sends a login request with a username and password.
- The server validates the username and password combination by checking in the database and validates the request
- If valid, a new JWT token is created with the payload containing the user’s technical identifiers and an expiration timestamp(will talk about this expiration timestamp shortly).
- The server then base64Url encodes the header and payload and sign them with the secret key to create the signature. Thus the complete JWT is created.
- The server sends back the JWT as a response to the client. The client will then send back this JWT on subsequent requests to the server to get authenticated.
- The server, upon receiving a request with JWT, tries to validate the token by checking expiration and creating a new signature and comparing it with the signature present in the token.
Until now when talking about a JWT we denoted it with a general term token. But in implementations, you will note the JWT is referenced using two terms AccessToken and RefreshToken. Both of these are just JSON Web Token, then why we use two? As we know JWT itself contains all the info needed to get validated, so imagine a scenario where some hacker gets hold of the token sent in the request. The hacker can now send requests to the server by falsely posing as the user. Now the hacker has a continuous valid access to the server. To handle such problems we use two different tokens. The AccessToken is the one that is send on each request and used for authentication of the user, but this AccessToken will have an expiration time after which it will be invalidated. So any hacker who got hold of the AccessToken will lose the access in a short time. So now when the AccessToken expires, won’t the authentic user also lose the access? Yes, this is where the use of RefreshToken comes in. The RefreshToken does not have any expiration time and it can be used to get a new AccessToken from the server.
In summary, upon a successful authentication of a user based on username and password, the server now creates two token called AccessToken and RefreshToken and return them to client. The client sends the AccessToken with further requests to server for authentication. This AccessToken will be expired after a certain period of time, on which the client can get a new AccessToken using the RefreshToken.
Hope i have given an understandable theoretical explanation on JWT. But i highly recommend going though the reference provided at the end to get an in depth understanding about JWT. Lets now move on to golang implementation.
Let’s now go over the code to implement a JWT authentication in golang. We will be discussing the code snippets that perform main functionalities of JWT authentication, but the entire repo can be found here.
The data model we will use for a user object and a corresponding sql schema is as follow:
we have added json, validate and sql tags on the User struct which helps us while encoding/decoding and validation of the User object.
Lets begin from implementing the routing logic of the authentication system,
Here we use gorilla mux package for routing. We create a new serve mux and then create sub routers for each of the http method we are going to handle. We then register the routes and corresponding handle functions on the sub routers. Also note that the we register the middleware function on the sub routers with Use(). A middleware function will be initially executed for the requests on all the routes of that sub router.
Next we will go into the details of the handle functions for login and signup. But before that lets have a look at the middleware function that is used by both of the handle functions.
MiddlewareValidateUser function parses the user object given in the request body and encodes the data to a User struct. We then validate the User struct which is done using the validate tags we provided. If the User struct has all our required fields, we add the object to the request context and call the next handle function(which in this case may be signup or login).
Now lets go through the Handle functions,
First one is our signup handle func. Now we have a validated user object in the request context. We hash the user password and store it into DB. If you already noted it and wondering what is the use of the TokenHash attribute, with is just a random string of certain length that we added to the user, we will discuss it shortly. So here we store the given user and return a success response.
Here is our login handle function. As we know we should have a valid user object in the request context as the user validation middleware function would have executed before this function. We first check if any user with the given email in the request exists in the database and retrieve it. Then the user in the request(requestUser) and user retrieved from DB(User) is passed onto the Authenticate function. Lets see what its doing,
Here we just compare the password provided in the request and the password in the DB. We use a special Hash compare function as the password in our database is stored in hashed format. If the password hashes are matching, we consider given request credentials as authentic.
Once the login request creds are authentic, we get to the part of generating the AccessToken and RefreshToken. Hope we remember why two tokens, below snippets explains how they are generated,
Lets slow down and go line-by-line to decode what the above snippet is doing.Firstly we are creating a value “cusKey” which is a hashed combination of the user ID and user HashToken(the random string that we generated while storing each user). We are going to add this customkey to the JWT payload, but why? So as we discussed above, to handle cases where AccessToken may be hacked, we are adding an expiration timestamp to invalidate it after certain time period. But what if the RefreshToken is hacked!! the hacker now can use it to get new AccessToken’s and perform false actions on the server. One way how this is being handled is by periodic key rotation, where we change the secret key which will invalidate the older tokens. Though it is a must implement thing for JWT, it is a periodic activity and a user cannot perform it. Image we as a user we know that our data transfer is compromised, how can we take action. Unfortunately JWT gives user no way to do it, only thing we as a user can do it change our password, but that has no effect on the JWT token and the Refresh token will remain valid until a key rotation is performed in the server.
To tackle this particular scenario, we are adding this customkey value to the payload of RefreshToken. During the validation of the RefreshToken, we will again create this customKey using the user ID and HashToken and compare it with the customKey in the RefreshToken payload. So now if this HashToken is changed then the RefreshToken will become invalid. We will change this HashToken with a new random string everytime a user changes his password. Thus we give user a way to invalidate the RequestToken by changing their password. With the hope that long explanation of why i introduced HashToken makes sense, we will move on to next part which is the creation of the JWT payload.
The RefreshTokenCustomClaims constitutes our payload. It consists of information such as userID, customkey, tokenType, issuer(which comes under JWT standardclaims). Note that we are not adding any expiration timestamp info to the payload. Now we have the payload and the header will be generated by JWT library, the next thing we need is the signature. The signing method we used here is RS256.
In a nutshell, the RS256 method follows public key cryptography, where we have two key, public key and private key. The private key will be used in signing the token and the public key will be used in validating the token in contrast to HSA method where a single secrete key will be used for both signing and validating. Data encrypted with a private key can only be decrypted with the corresponding public key and vise versa.
(Note : While explaining JWT we use HSA method for signing. Difference is that HSA is a hashing based method and RSA is encryption based method. For further understanding the pro and cons of each please have a read on the references below
commands to generate rsa keys in linux system:
Generate rsa private key : openssl genrsa -out auth-private.pem 2048
Export rsa public key : openssl rsa -in auth-private.pem -outform PEM -pubout -out auth-public.pem
So we read our privatekey from the server filesystem. Now we have all the components we need to create the token — the payload, the header(signature method specified) and the signing secret. We just create our token and sign it with the secret. Yes!! we have our refresh token.
Next we follow a similar approach to create the AccessToken, with few modifications like using different private key, excluding the need of customKey. The important thing to note here is that we are adding an expiration timestamp for the Accesstoken.
With that we have covered all things involved with signup and login handle functions.
Lets now move on to how we handle a refresh-token request. As said, we use refresh-token to get a new AccessToken upon expiration of the AccessToken. So what we need to do here is validate the given RefreshToken and provide a new AccessToken. The validations part is carried out in the MiddlewareValidateRefreshToken middleware function registered on the refresh-token subrouter.
We extract the RefreshToken from the header and then validate it.
The important thing to note here is that we are using the public key to decrypt the RefreshToken and get the payload. This is the corresponding public key of the private key that is used to sign the RefreshToken. We then make some basic checks on the payloads present in the RefreshToken and return the userID and customKey present in the payload.
As an additional check, we regenerate the customKey from the HashToken present in the DB for the given user and compare it with the customKey present in the RefreshToken payload. If all the validation checks passes, we add the user to the request context and call the next handle function(RefreshToken is this case).
Here we use the same logic used early to create a new AccessToken and return it.
We have almost covered all the important components of our authentication system. The other routes registered on the server are “/greet” which is just to check the MiddlewareValidateAccessToken middleware function.
Nothing new here, we follow the same flow of how we validated the RefreshToken. Changes to note here is that we use a AccessToken public key for decryption and also the different check on the payload informations.
Thats all i have for this article. I wish this was a useful read for you. I am going to next work on implementing the email verification, password reset, Key rotation and Key distribution functionalities upon this authentication system along with some containerization work and will write my next article on it. Also find below some of great resources that helped me.
Thank you for your time and happy learning :-)
JWT: The Complete Guide to JSON Web Tokens
This post is the first part of a two-parts step-by-step guide for implementing JWT-based Authentication in an Angular…