This is part three of a three-part series tutorial that builds a small but complete JWT authentication solution for internal API (most concepts can also be applied to build JWT auth for public API).
- Part 1 — Public/secret key generation and storage
- Part 2 —Build a CLI to create/retrieve App object
- Part 3 — Build the JWT authentication middleware
Overview
This is the last part of the tutorial series, we will finally write methods to authenticate requests with JWT token in Authorization header. There are two parts to this.
- JWT Authentication functions
- Example of middleware using JWT authentication
JWT Authentication
In this part, we will use a JWT library to provide a way for us to parse and encode JWT token. There are a few of them and you can select your favourite one at jwt.io. In this project, I chose jwt-go. I’ve used it before so I’m more familiar with it than others.
I think it's good to wrap jwt-go in my own class and expose only what I need to use. It has three main benefits.
- I don't have to remember the documentation for the library until I need another thing from it since everything I need is written by me and documented myself.
- It’s a great way to learn how to use the library and actually understand its interface.
- Other parts of the code don’t need to be aware of the library so we can switch to another one relatively easy.
Parse JWT Token
ParseJWT takes a token and a secret key to verify the signature of the token and returns a Claims object. We use the most common signing method, HMAC, to sign the token. There are other ones in the library that you can use. They’re all defined as constants, so it’s quite convenient and readable.
Next, we check if the token is properly generated with token.Valid and return the claims wrapped under our own Claimstype. I handled some errors. However, I think one thing we can do is to wrap those errors in our own errors and propagate them up. The errors package is great for this. You can find it here.
There are many opinions on error handling. I was lazy and simply propagate the errors straight up. I think wrapping it to preserve the stack trace and provide more details will be helpful when we need to debug. One thing to note is since this package is meant to be a library package, we should not log anything out.
Encode JWT Token
EncodingJWT is quite straight forward. Again, I’m simply pushing the errors up the stack and not handle them here. It takes two arguments, a secret key, and a Claims object. We use jwt.NewWithClaims to create a new Token object. Then, we use SignedString to generate the token string.
Most of the time, we need to attach something to the token as we generate them. That’s why I only want to expose NewWithClaims method and always create a Claims object. If we don’t want to have any claims, we simply make an empty Claims object to pass in. That way, we don’t have to make 2 different methods and remember the difference between the two.
How to use it in a middleware
Now that we have the JWT auth service, how do we use it to authenticate a request from the client? If you’ve read all three parts of the tutorial, you will know that we store all the client credentials as App . That means the client needs to be registered with us before sending a request to our service.
Each client should have a public/secret key pair. The public key will be used to identify the client with the API. Using the public key, we can get the secret for the client from our database. The client used its secret key to generate a JWT token to send to the API in the Authorization header in this format: Bearer asdfasdfadsf . Therefore, we need the same secret key to verify the token.
The process is as follow:
- Extract the token from the request header.
- Parse it with the secret key. If we get claims back, it means the token is valid. We proceed with the request.
- Otherwise, we don’t let the user proceed and return a Forbidden status code.
I excluded the part where you need to get the public/secret key pair from the database. This is an example, not a 100% implementation. If you want to identify each client, then you’ll need to query the database to find a public/secret key pair that the client registered before making a request. On the other hand, if there is only one client (in the case of an internal web service) then you probably don’t need to identify it. In addition, there are many things you might want to do when you receive a request from the client. For example, you may need to get the user’s ID or email to do authorization. Middleware/handler logic varies depends on your use case.
Conclusion
And there you have it, implementation of JWT authentication using Golang for web services. You can find all the code here. If you have any suggestions, I’d love to listen. If you have any questions, please leave your comment below and I’ll do my best to answer. I really hope you find this tutorial series helpful. Thank you so much for reading it till the end. Cheers!
Top comments (0)