Secure Your Api Using Jwt in Golang

Thursday, July 30, 2020 • 13 minutes to read

Golang2020golangjwt

In this tutorial, we will learn how to secure the APIs using the JWT authentication in Golang.
In any application, APIs are the bridge between two services. These services can be anything, like a backend service or a frontend service.
To secure the application, bridge security is important.

JWT is a JSON web token. In which, a token is generated by 1 service and shared with another service. Whenever the 2nd service make a request to the 1st service, it will send the token with the request. Then the first service will validate if the token is valid or not, in this way, it is validating if it is requested from the genuine service or not.

For Ex. In a web application, when a user login, the content is unique and personalized according to him. Even when you reload the page or close the browser, when you will open it, it is still logged in.

How the application knows this? As every time it is making a new request to the server and it is not asking to login user with each request. It is the token which is saved in the browser. It can be saved in the cookie or in the browser storage. These tokens are not limited to the JWT, there are many alternatives available.

Next time, when you login in to any application, check the cookie and storage. Then clear the cookies and storage of that site and reload it. It will ask you for sign in again. 😃

What is JWT?

JWT stands for JSON Web Token. JWT represents a claim between two parties which is shared in a JSON format.

In simple words, it is similar to your ID cards. In school, college, office etc places this ID card is provided by the organization to you to authenticate yourself whenever you enter the premises. 🧐

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

How JWT works?

The JWT structured in three parts:

  • Header: It identify which algorithm is used
  • Payload: The information
  • Signature: The encryption of the information by a Secret Key using the Algorithm

The three parts are separated by the dot(.).
For Ex: Header.Payload.Signature

Let's understand it with an example. Suppose the Header is algorithm1 and algorithm1 is the below equation.

1Signature = Payload * SecretKey

Then, the Payload is 11 and the SecretKey is 5.

When the Payload and SecretKey is passed to the algorithm1 it will generate a unique Signature.

1Signature = Payload * SecretKey = 11 * 5 = 55

The Signature is 55.

Then the JWT token will look like this.

1algorithm1.11.55

This JWT token will be shared with the requested party. After this whenever this party raise a request to this party, it will also share this token to authenticate itself.
When the first party receives the request with the token, it will first validate the token before processing the request.

As all the details are available in the token it is very easy task for the party to validate the token. It will take the Payload and passed it to the Header (algorithm1) using the SecretKey which it already have to generate the Signature. Then it will compare the token Signature with the generated Signature, if it matched Voila go ahead with the request else decline the request. 🔎

The actual JWT Token looks like this.

1eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSSBhbSBJcm9uIE1hbi4ifQ.li-FDEyAdayupFIS5P2EKexN-Rm_SWe4LXO9Xjyja4o

Take a quick look and you can see the token is divided in 3 parts:

  • Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  • Payload: eyJuYW1lIjoiSSBhbSBJcm9uIE1hbi4ifQ
  • Signature: li-FDEyAdayupFIS5P2EKexN-Rm_SWe4LXO9Xjyja4o

The Header and Payload are base64 encoded.

 1package main
 2
 3import (
 4	b64 "encoding/base64"
 5	"fmt"
 6)
 7
 8func main() {
 9	header := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
10	payload := "eyJuYW1lIjoiSSBhbSBJcm9uIE1hbi4ifQ"
11
12	decodedHeader, _ := b64.StdEncoding.DecodeString(header)
13	decodedPayload, _ := b64.StdEncoding.DecodeString(payload)
14
15	fmt.Printf("Decoded Header: %s\nDecoded Payload:%s", decodedHeader, decodedPayload)
16
17}

Try it

Output

1Decoded Header: {"alg":"HS256","typ":"JWT"}
2Decoded Payload:{"name":"I am Iron Man."}

The algorithm in Header is HS256 which is used to sign the Payload.
The Payload is a JSON object with a key as name and value as I am Iron Man.

To sign this payload, the SecretKey is secretKey. 😅

Getting Started

We are going to create a simple web application. In this application, there will be 2 routes, first login and second dashboard.

Prerequisites

  • Go v1.11 or greater (I am using Go v1.14)
  • Code Editor (For ex. VS Code, Atom)
  • Postman - to test the APIs. Curl commands can also be used.

Project Structure

Create a new project go-jwt-app.
Open the terminal inside the go-jwt-app and initialize the go modules using the below command.

1go mod init go-jwt-app

Go modules is a dependency manager or a package manager. It will track all the dependencies used in the project with their version. You can read more about it here.

Install the dependencies

There are 3 packages used in this Project.

  1. Gorilla/mux: It is a feature rich package to create the APIs and server.
  2. jwt-go: It is a Golang implementation of JSON Web Token(JWT). Using this package, we can create and verify the JWT tokens.
  3. godotenv: Using this package, we can access the .env file in which environment variables can be saved.
1go get github.com/gorilla/mux
2go get github.com/dgrijalva/jwt-go
3go get github.com/joho/godotenv

Environment Variable

Create a new .env file and paste the below code in it.

1SECRET_KEY=secret007

Using the godotenv package, we can read the SECRET_KEY.

JWT implementation

In this section, we can divide the process in modules to understand clearly.

Create Server and Routes

 1package main
 2
 3import (
 4	"fmt"
 5	"log"
 6	...
 7	"github.com/gorilla/mux"
 8)
 9
10func main() {
11	r := mux.NewRouter()
12
13	r.HandleFunc("/login", login).Methods("POST")
14	r.HandleFunc("/me", dashboard).Methods("GET")
15
16	fmt.Println("Starting server on the port 8000...")
17	log.Fatal(http.ListenAndServe(":8000", r))
18}

First, create a new instance of mux router using the mux.NewRouter() method.

Using the HandleFunc create 2 routes. /login as a POST request and /me as a GET request.
The /login endpoint will execute login function and /me will execute the dashboard function.

Create a new JWT Token

In this login function, when a user will enter its username and password, it will first validate if the user registered or not. To validate, we will create a local user db using the map.

On successful, verification it will generate a Token using the jwt-go package and send to the request as a response.

To generate the JWT token, we are going to use the HS256 algorithm.

 1package main
 2
 3import (
 4	"encoding/json"
 5	"fmt"
 6	"log"
 7	"net/http"
 8	"os"
 9	"strings"
10	"time"
11
12	jwt "github.com/dgrijalva/jwt-go"
13	"github.com/gorilla/mux"
14	"github.com/joho/godotenv"
15)
16
17// Secret key to uniquely sign the token
18var key []byte
19
20// Credential User's login information
21type Credential struct{
22	Username string `json:"username"`
23	Password string `json:"password"`
24}
25
26// Token jwt Standard Claim Object
27type Token struct {
28	Username string `json:"username"`
29	jwt.StandardClaims
30}
31
32// Create a dummy local db instance as a key value pair
33var userdb = map[string]string{
34	"user1": "password123",
35}
36
37// assign the secret key to key variable on program's first run
38func init() {
39	// Load the .env file to access the environment variable
40	err := godotenv.Load()
41
42	if err != nil {
43		log.Fatal("Error loading .env file")
44	}
45
46	// read the secret_key from the .env file
47	key = []byte(os.Getenv("SECRET_KEY"))
48}
49
50// login user login function
51func login(w http.ResponseWriter, r *http.Request) {
52	// create a Credentials object
53	var creds Credential
54	// decode json to struct
55	err := json.NewDecoder(r.Body).Decode(&creds)
56	if err != nil {
57		w.WriteHeader(http.StatusBadRequest)
58		return
59	}
60	// verify if user exist or not
61	userPassword, ok := userdb[creds.Username]
62
63	// if user exist, verify the password
64	if !ok || userPassword != creds.Password {
65		w.WriteHeader(http.StatusUnauthorized)
66		return
67	}
68
69	// Create a token object and add the Username and StandardClaims
70	var tokenClaim = Token {
71		Username: creds.Username,
72		StandardClaims: jwt.StandardClaims{
73			// Enter expiration in milisecond
74			ExpiresAt: time.Now().Add(10 * time.Minute).Unix(),
75		},
76	}
77
78	// Create a new claim with HS256 algorithm and token claim
79	token := jwt.NewWithClaims(jwt.SigningMethodHS256, tokenClaim )
80
81	tokenString, err := token.SignedString(key)
82
83	if err != nil {
84		log.Fatal(err)
85	}
86	json.NewEncoder(w).Encode(tokenString)
87}

In the init function, using the godotenv package load the .env file to read the SECRET_KEY.
Check this to learn more on environment variables in Golang.

In the login function, first read the request body to get the username and password. The request body is in JSON format. Read the JSON using the encoding/json package.
Check this to learn more on how to use JSON in Golang.

In the userdb, we have created a dummy user. It will validate the user using the userdb.

On successful user validation, create a Token object. The Token object has a Username and a StandardClaims. In the StandardClaims, you can define the validity of the token.

The StandardClaims takes Unix time.

Create a new Claims, with the HS256 algorithm and the token claim.

Then, sign this claim using the key which is the SECRET_KEY in the []byte form.

In the end, return the token as a response.

Verify the Token

When the /me endpoint hit, it will execute the dashboard function.
We are going to send the token as Bearer Token. You can also send it as a key value pair in the request object.

In simple language, Bearer token is the same token with Bearer prefixed to it.

1Bearer <Token>
 1// dashboard User's personalized dashboard
 2func dashboard(w http.ResponseWriter, r *http.Request) {
 3	// get the bearer token from the reuest header
 4	bearerToken := r.Header.Get("Authorization")
 5
 6	// validate token, it will return Token and error
 7	token, err := ValidateToken(bearerToken)
 8
 9	if err != nil {
10		// check if Error is Signature Invalid Error
11		if err == jwt.ErrSignatureInvalid {
12			// return the Unauthorized Status
13			w.WriteHeader(http.StatusUnauthorized)
14			return
15		}
16		// Return the Bad Request for any other error
17		w.WriteHeader(http.StatusBadRequest)
18		return
19	}
20	// Validate the token if it expired or not
21	if !token.Valid {
22		// return the Unauthoried Status for expired token
23		w.WriteHeader(http.StatusUnauthorized)
24		return
25	}
26
27	// Type cast the Claims to *Token type
28	user := token.Claims.(*Token)
29
30	// send the username Dashboard message
31	json.NewEncoder(w).Encode(fmt.Sprintf("%s Dashboard", user.Username))
32}
33
34
35
36// ValidateToken validates the token with the secret key and return the object
37func ValidateToken(bearerToken string) (*jwt.Token, error) {
38
39	// format the token string
40	tokenString := strings.Split(bearerToken, " ")[1]
41
42	// Parse the token with tokenObj
43	token, err := jwt.ParseWithClaims(tokenString, &Token{}, func(token *jwt.Token)(interface{}, error) {
44		return key, nil
45	})
46
47	// return token and err
48	return token, err
49}

Get the Bearer Token from the request header. The Bearer token's key is Authorization.

1bearerToken := r.Header.Get("Authorization")

Pass the bearerToken to the ValidateToken function. This function will validate the token if it is valid or not.
Using the ParseWithClaims method from the jwt-go package, it will validate the token and returns a *jwt.Token object and an error.

To get the user information from the *jwt.Token object, type cast it to (*Token).

Complete Code

Create a new file main.go and paste the below code.

  1package main
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"log"
  7	"net/http"
  8	"os"
  9	"strings"
 10	"time"
 11
 12	jwt "github.com/dgrijalva/jwt-go"
 13	"github.com/gorilla/mux"
 14	"github.com/joho/godotenv"
 15)
 16
 17// Secret key to uniquely sign the token
 18var key []byte
 19
 20// Credential User's login information
 21type Credential struct{
 22	Username string `json:"username"`
 23	Password string `json:"password"`
 24}
 25
 26// Token jwt Standard Claim Object
 27type Token struct {
 28	Username string `json:"username"`
 29	jwt.StandardClaims
 30}
 31
 32// Create a dummy local db instance as a key value pair
 33var userdb = map[string]string{
 34	"user1": "password123",
 35}
 36
 37// assign the secret key to key variable on program's first run
 38func init() {
 39	// Load the .env file to access the environment variable
 40	err := godotenv.Load()
 41
 42	if err != nil {
 43		log.Fatal("Error loading .env file")
 44	}
 45
 46	// read the secret_key from the .env file
 47	key = []byte(os.Getenv("SECRET_KEY"))
 48}
 49
 50func main() {
 51	r := mux.NewRouter()
 52
 53	r.HandleFunc("/login", login).Methods("POST")
 54	r.HandleFunc("/me", dashboard).Methods("GET")
 55
 56	fmt.Println("Starting server on the port 8000...")
 57	log.Fatal(http.ListenAndServe(":8000", r))
 58}
 59
 60// login user login function
 61func login(w http.ResponseWriter, r *http.Request) {
 62	// create a Credentials object
 63	var creds Credential
 64	// decode json to struct
 65	err := json.NewDecoder(r.Body).Decode(&creds)
 66	if err != nil {
 67		w.WriteHeader(http.StatusBadRequest)
 68		return
 69	}
 70	// verify if user exist or not
 71	userPassword, ok := userdb[creds.Username]
 72
 73	// if user exist, verify the password
 74	if !ok || userPassword != creds.Password {
 75		w.WriteHeader(http.StatusUnauthorized)
 76		return
 77	}
 78
 79	// Create a token object
 80	var tokenObj = Token {
 81		Username: creds.Username,
 82		StandardClaims: jwt.StandardClaims{
 83			// Enter expiration in milisecond
 84			ExpiresAt: time.Now().Add(10 * time.Minute).Unix(),
 85		},
 86	}
 87
 88	token := jwt.NewWithClaims(jwt.SigningMethodHS256, tokenObj )
 89
 90	tokenString, err := token.SignedString(key)
 91
 92	if err != nil {
 93		log.Fatal(err)
 94	}
 95	json.NewEncoder(w).Encode(tokenString)
 96}
 97
 98// dashboard User's personalized dashboard
 99func dashboard(w http.ResponseWriter, r *http.Request) {
100	// get the bearer token from the reuest header
101	bearerToken := r.Header.Get("Authorization")
102
103	// validate token, it will return Token and error
104	token, err := ValidateToken(bearerToken)
105
106	if err != nil {
107		// check if Error is Signature Invalid Error
108		if err == jwt.ErrSignatureInvalid {
109			// return the Unauthorized Status
110			w.WriteHeader(http.StatusUnauthorized)
111			return
112		}
113		// Return the Bad Request for any other error
114		w.WriteHeader(http.StatusBadRequest)
115		return
116	}
117	// Validate the token if it expired or not
118	if !token.Valid {
119		// return the Unauthoried Status for expired token
120		w.WriteHeader(http.StatusUnauthorized)
121		return
122	}
123
124	// Type cast the Claims to *Token type
125	user := token.Claims.(*Token)
126
127	// send the username Dashboard message
128	json.NewEncoder(w).Encode(fmt.Sprintf("%s Dashboard", user.Username))
129}
130
131
132
133// ValidateToken validates the token with the secret key and return the object
134func ValidateToken(bearerToken string) (*jwt.Token, error) {
135
136	// format the token string
137	tokenString := strings.Split(bearerToken, " ")[1]
138
139	// Parse the token with tokenObj
140	token, err := jwt.ParseWithClaims(tokenString, &Token{}, func(token *jwt.Token)(interface{}, error) {
141		return key, nil
142	})
143
144	// return token and err
145	return token, err
146}

Run the server

Open the terminal and first build the application.

1go build

It will generate a go-jwt-app.exe executable file.

1./go-jwt-app.exe
2
3Starting server on the port 8000...

The server is started on the port 8000.

Test the application

Open the Postman and create a new POST request.

  • URL: http://localhost:8000/login
  • Body: raw/JSON
1{
2   "username": "user1",
3   "password": "password123"
4}

Send the request. It will send a JWT token in the response.
Copy the JWT Token.
login

Create a new GET request.

  • URL: http://localhost:8000/me
  • Auth: Bearer Token

In the Token field, paste the JWT token from the last request and hit Send.

It will return "user1 Dashboard" as the response.

dashboard

Conclusion

In this tutorial, we used the HS256 algorithm which accepts a text as a Secret Key. For more security, you can use other algorithms like ECDSA.
For ECDSA, you have to first create a private-public key pair.

JWT is not the only method to secure the APIs. You should check out them too.
Thanks for reading.

Cover is designed in Canva


Golang2020golangjwt

PreviousCreate your first Serverless application

NextHow to Send Email in Nodejs with Expressjs