Integration and Tools

Building Secure API’s: Integrating OAuth2 and JWT with Golang

Building-Secure-APIs-Integrating-OAuth2-and-JWT-With-Go

In the modern world of software development, ensuring the security of RESTful APIs is crucial. OAuth2 and JWT (JSON Web Tokens) are powerful tools that provide robust mechanisms for authentication and authorization. This blog post will guide you through implementing these security measures in a Golang application. 

Introduction

What is OAuth2?

Authorization Framework: Manages access by issuing tokens to third-party services without exposing user credentials.

What is JWT?

Token Format: A compact, URL-safe format with three parts: header, payload, and signature. 

What is Go?

Programming Language: Go, also known as Golang, is an open-source language developed by Google, known for its simplicity and efficiency.

Pre-requisites

Before diving into the implementation, ensure you have the following prerequisites: 

  • Basic Knowledge of Go: Familiarity with Go syntax and library. 
  • Understanding of REST APIs: Knowledge of RESTful principles. 
  • Familiarity with OAuth2 and JWT: Basics of OAuth2 for authorization and JWT for secure handling. 
  • Go Modules: Ensure they are enabled in your project. 

Implementing OAuth2 and JWT using Golang

JWT  Authentication  Workflow

To protect APIs with OAuth2 and JWT using Golang, follow these steps:

A. Setting Up Your Go Project

Create a new Go project and install the necessary packages. For this, we use the echo framework for building the REST API and the jwt-go package for handling JWTs. 

go mod init your_project  

go get github.com/labstack/echo/v4

go get github.com/dgrijalva/jwt-go

B. Writing the Code

Here’s a basic implementation that demonstrates OAuth2 and JWT for securing REST API endpoints: 

Step 1: Imports
package main 


import (

"net/http"

"strings"

"sync"

"time"



"github.com/dgrijalva/jwt-go"

"github.com/labstack/echo/v4"

"github.com/labstack/echo/v4/middleware"
)
  • net/http builds servers and handles requests 
  • strings offers string manipulation. 
  • sync provides concurrency primitives 
  • time manages date and time. 
  • github.com/golang-jwt/jwt/v4 handles JWTs for secure transmission. 
  • github.com/labstack/echo/v4 defines routes and handlers, with middleware for logging, security, and JWT authentication. 
Step 2: Type Definition and Global Variables
var ( 

jwtSecret = []byte("your_jwt_secret")

username = "your_username"

password = "your_password"

tokenVersion = int64(1) // Initial version

mu sync.Mutex

)

type CustomClaims struct {

jwt.StandardClaims

Version int64 `json:"version,omitempty"`}

type StandardClaims struct {

Audience string `json:"aud,omitempty"`

ExpiresAt int64 `json:"exp,omitempty"`

ID string `json:"jti,omitempty"`

IssuedAt int64 `json:"iat,omitempty"`

Issuer string `json:"iss,omitempty"`

NotBefore int64 `json:"nbf,omitempty"`

Subject string `json:"sub,omitempty"`

}
Step 3: Define the Main Function
func main() { 

e := echo.New()


e.POST("/auth/login", handleLogin)

e.POST("/auth/refresh", handleRefresh)


jwtAuth := middleware.JWTWithConfig(middleware.JWTConfig{

SigningKey: jwtSecret,

TokenLookup: "header:Authorization",

AuthScheme: "Bearer",

})


e.GET("/heartbeat", pingHandler, jwtAuth)


port := "8080" // Hardcoded port

e.Logger.Fatal(e.Start(":" + port))
}
  • Echo Web Server Initialization sets up a new Echo instance to handle HTTP requests, define routes, and listen on port 8080. 
  • Login Route (/auth/login) authenticates users and issues JWTs, while Token Refresh Route (/auth/refresh) refreshes tokens for session continuity.  
  • JWT Middleware Configuration secures routes requiring a valid JWT, shown with the Secured /heartbeat Endpoint.  
  • Logging Fatal Errors uses Echo’s logging to capture and record startup errors. 
Step 4: Define Handlers
func handleLogin(c echo.Context) error { 

reqUsername := c.FormValue("username")

reqPassword := c.FormValue("password")

if reqUsername != username || reqPassword != password {

return echo.NewHTTPError(http.StatusUnauthorized, "Invalid username or password")

}


// Create JWT token

claims := &jwt.StandardClaims{

Subject: reqUsername,

ExpiresAt: jwt.TimeFunc().Add(time.Hour * 1).Unix(), // Token expiration

}


token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

signedToken, err := token.SignedString(jwtSecret)
if err != nil {

return err

}


return c.JSON(http.StatusOK, map[string]string{"token": signedToken})
}
  • Credential Validation: Checks if the provided username and password match predefined values to authenticate the user.  
  • Creating a JWT Token: Generates a JWT with claims and an expiration time of one hour upon successful credential validation. 
  • Token Signing Using jwtSecret: Signs the JWT with a secret key (jwtSecret) to ensure token integrity and authenticity.  
  • Returning the Signed Token: Sends the signed JWT back to the client in the response as a JSON object for use in future authenticated requests.  
  • Error Handling During Token Signing: Returns an error if there is an issue during the token signing process, ensuring proper error communication. 
func handleRefresh(c echo.Context) error { 

tokenString := c.Request().Header.Get("Authorization")

if tokenString == "" {

return echo.NewHTTPError(http.StatusUnauthorized, "Missing token")

}


tokenString = strings.TrimPrefix(tokenString, "Bearer ")

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {

return jwtSecret, nil

})

if err != nil || !token.Valid {

return echo.NewHTTPError(http.StatusUnauthorized, "Invalid token")

}


claims := token.Claims.(jwt.MapClaims)

username := claims["sub"].(string)


// Lock to safely increment token version

mu.Lock()

tokenVersion++

currentVersion := tokenVersion

mu.Unlock()


// Use currentVersion somewhere

newClaims := &CustomClaims{

StandardClaims: jwt.StandardClaims{
Subject: username,

ExpiresAt: jwt.TimeFunc().Add(time.Hour * 1).Unix(), // Token expiration

},

Version: currentVersion,

}

newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims)

signedToken, err := newToken.SignedString(jwtSecret)

if err != nil {

return err

}



return c.JSON(http.StatusOK, map[string]string{"token": signedToken})

}



func pingHandler(c echo.Context) error {

return c.String(http.StatusOK, "Pong")
}
  • Handling Token Refresh Requests manages the process of refreshing JWTs, including Extracting and Verifying Existing JWT for validity and signature.  
  • Incrementing the Token Version tracks the latest token and invalidates older ones.  
  • Creating a new JWT with Updated Claims generates a new token with updated claims and version number, and Signing and Returning the new Token provides it to the client.  
  • Simple Health Check (pingHandler) responds with “Pong” to check server health at the /heartbeat endpoint. 

C. Execution

  1. Set Up and Initialize the Go environment by creating a directory, initializing a Go module, and installing dependencies. 
  2. Run the Go Server with go run main.go (Using the command `go run .`) 
  3. Test Endpoints with curl or Postman for login, token refresh, and heartbeat. 
  4. Handle Error Scenarios by testing responses to invalid credentials and unauthorized access. 
     

Pros and Cons 

Pros:

  • Secure Authentication: JWTs are self-contained and easy to verify. 
  • Stateless: No need for server-side session storage. 
  • Flexibility: OAuth2 with JWT offers a flexible authorization framework.

Cons:

  • Token Revocation: JWTs cannot be revoked until they expire. 
  • Complexity: OAuth2 and JWT add system complexity. 

Precautions

  • Keep Secrets Safe: Store JWT secrets securely, not hardcoded. 
  • Handle Expiry: Implement token expiration and renewal. 
  • Avoid Over-Reliance: Use JWT with broader security practices and secure coding. 

Conclusion

Implementing OAuth2 and JWT in your Golang applications is crucial for securing APIs against unauthorized access and threats. These technologies provide robust authentication, effective session management, and protection for sensitive resources. They also ensure scalability and align with industry best practices, enabling you to build secure, reliable APIs with confidence. 

nagraj-todkari

Software Engineer