JWT in Golang — How to Implement Token-Based Authentication

How to implement token-based authentication
Jan 26 2023 · 4 min read

Introduction 

We are using two types of tokens for authentication in the applications,

  • Access Token: A string used to access protected resources on the client side. Every token has a specific scope, lifetime, and other valid attributes. We can set lifetime as per our needs(1 hour or 1 day).
  • Refresh Token: A string that is used to obtain an access token. It will be created at the time of issuing an access token for authorization. Its lifetime should be greater than the access token(1 month or 1 year).

Keep in mind that, these tokens are not only JWT-specific. They provide a way to authorize resources. We can create these tokens using JWT or any other format.

Why Refresh-Token?

After the first access token has run out, the refresh token is used to authenticate the user. Without user intervention, this takes place in the background, enhancing user experience without compromising security. Refresh tokens do not grant the user any further access over what was first permitted.

If you want to know more about it, this thread will be helpful to you.

Security is mostly a matter of human behavior, and behavior can be defined by habits. Improve your habits with justly.

Introduction

JWT(JSON web token) is a token format used for authentication in any application. jwt.io provides a web interface to check the JWT tokens and a user guide for understanding the token structure.

JWT tokens contain three parts:

  • Header - (contains the type of token and signing algorithm of the token)
  • Signature — (base64 key used to verify the message wasn’t changed along the way)
  • Payload — (contains actual data that is claims used to create tokens)

We will implement Golang JWT authentication using a go-jwt package.

Terminologies by a real-life example

We can simply relate the JWT terminologies with the residential locking system as below,

  • Refresh-token: Door
  • Access-token: Lock of the door
  • Header: Things and materials used to make lock and key
  • Secret Key(signature): Key of lock
  • Scope: Home and owner
  • Claims: Validities given by the lock maker (Lifetime of lock)
  • Generate tokens: The owner buys a lock
  • Validate token: Open the lock only when the matched key will be used.
  • Renew the access token: The owner replaces a lock when it will be damaged. The door and its functionality will be considered to buy a new lock.

Generate tokens

For generating tokens, we need a separate secret key for both tokens (or the same key can also be used for both tokens). You can generate it with HSA 256 OR RS256 encryption. It should at least be 32 characters long, but longer is better.

Next, define claims. go-jwt has its standard claims for JWT. But we can create custom claims as the below struct,

type JWTData struct {
   jwt.StandardClaims
   CustomClaims map[string]string `json:"custom_claims"`
}

Let’s generate tokens using these claims,

import "github.com/golang-jwt/jwt/v4"

func GenerateJWTAccessToken(userId string, email string) (string, error) {
    // prepare claims for token
    claims := JWTData{
        StandardClaims: jwt.StandardClaims{
           // set token lifetime in timestamp
           ExpiresAt: time.Now().Add(time.Duration(tokenLifeTime)).Unix(),
        },
        // add custom claims like user_id or email, 
        // it can vary according to requirements
        CustomClaims: map[string]string{
           "user_id": userId,
           "email": email,
        },
     }
    
     // generate a string using claims and HS256 algorithm
     tokenString := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    
     // sign the generated key using secretKey
     token, err := tokenString.SignedString(secretKey)

     return token, err
}

token which will be a JWT token containing a header, signature, and payload. You can check the token at jwt.io.

The same method can be used to generate refresh tokens with a greater lifespan.

Validate tokens

For verifying the authorized resources, we need to validate the token first.

The below code will be used to validate tokens,

claims := &JWTData{}

 _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
      return secretKey, nil
 })
  • The below code is the last parameter of ParseWithClaims, is KeyFunc will return the secretKey from the given token,
func(token *jwt.Token) (interface{}, error) {
      return secretKey, nil
}
  • parseWithClaims will assign data to claims that we have defined earlier in JWTData.

We can validate the tokens by claims ExpiresAt . It also has custom claims user_id and email. You can check whether they are present in your database, and authorize users with a given token.

Here is the code for errors if the token is not valid,

if err != nil {
  // check whether error is validation error or not
  if validationErr, ok := err.(*jwt.ValidationError); ok {
   // Token is malformed
   if validationErr.Errors & jwt.ValidationErrorMalformed != 0 {
    return "Token is malformed"
   } else if validationErr.Errors & (jwt.ValidationErrorExpired | jwt.ValidationErrorNotValidYet) != 0 {
    // Token is either expired or not active yet
    return "Token is either expired or not active yet"
   } else {
    return "Other err"
   }
  } else {
   return "Other err"
  }
 }

Renew the access token using the refresh token

As we know, a refresh token will be used to renew the access token.

Here is the flow of getting access token from refresh token,

  • Validate refresh-token using the above method.

Note: You can pass refresh-token in the header or in form-data whatever is preferable.

_, err := jwt.ParseWithClaims(refreshToken, claims, func(token *jwt.Token) (interface{}, error) {
      return secretKey, nil
 })
  • Check whether the user is present in the database using custom claims.
  • If a refresh token is valid and the user is present, generate a new access token and refresh token with same scope and defined lifetime .

Consider a scenario, where the refresh-token lifetime is 1 month and the user has not used the app for 2 months. A refresh token expires if not used within the given time, after which a new refresh token and access token need to be requested again. Otherwise it will be refreshed with the access token.

Conclusion

JWT authentication provides security between two parties using signed tokens.

It is a widely used format for authentication. I have explained it to Golang, but the flow will be the same for other languages also like javascript or PHP.

We’re Grateful to have you with us on this journey!

Suggestions and feedback are more than welcome! 

Please reach us at Canopas Twitter handle @canopas_eng with your content or feedback. Your input enriches our content and fuels our motivation to create more valuable and informative articles for you.

That’s it for today, Keep reading for the best.

Similar Articles


sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.


sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.

canopas-logo
We build products that customers can't help but love!
Get in touch
background-image

Get started today

Let's build the next
big thing!

Let's improve your business's digital strategy and implement robust mobile apps to achieve your business objectives. Schedule Your Free Consultation Now.

Get Free Consultation
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.