DEV Community

Kittipat.po
Kittipat.po

Posted on

JWT Tokens in Golang: A Developer’s Guide to Secure APIs

JWT Image

Introduction

In modern web development, secure and scalable authentication is essential. JSON Web Tokens (JWT) have become a standard approach to achieving this. In this blog, we’ll explore what JWT is, how it works, and how to implement it in Golang.

What is a JWT?

A JSON Web Token (JWT) is a compact, URL-safe way of representing claims to be securely transferred between two parties. It is commonly used to authenticate and authorize users in APIs and distributed systems.

Structure of a JWT

A JWT consists of three parts separated by dots (.):

Header.Payload.Signature
Enter fullscreen mode Exit fullscreen mode

Example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.reGQzG3OKdoIMWLDKOZ4TICJit3EW69cQE72E2CfzRE
Enter fullscreen mode Exit fullscreen mode

Each part is:
1.Header: Specifies the token type (JWT) and signing algorithm (HS256).

{
  "alg": "HS256",
  "typ": "JWT"
}
Enter fullscreen mode Exit fullscreen mode

2.Payload: Contains claims (user data like id, role, or name).

{
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true,
    "iat": 1516239022
}
Enter fullscreen mode Exit fullscreen mode

3.Signature: Ensures the token’s integrity using a cryptographic signing process. Let’s explore this in detail.

The signature is created by:

  • Concatenating the Encoded Header and Payload:
  base64UrlEncode(header) + "." + base64UrlEncode(payload)
  • Signing the Result:
    • A cryptographic signing algorithm (e.g., HMACSHA256, RS256) is used with a secret or private key.
  • Appending the Signature: The final JWT becomes
  header.payload.signature

How JWT Works

  1. The client sends login credentials to the server.
  2. If valid, the server generates a JWT and returns it to the client.
  3. The client stores the JWT (e.g., in localStorage or cookies).
  4. For every request, the client includes the JWT in the Authorization header: Authorization: Bearer <token>
  5. The server validates the JWT on each request.
    • Recalculating the signature using the received header and payload.
    • Comparing the recalculated signature with the received signature.

Implementing JWT in Golang

Golang developers can leverage the excellent golang-jwt/jwt library for handling JWTs. This library provides robust features for creating, signing, and validating JWTs. You can find it here.

However, managing JWTs often requires repetitive tasks like configuring signing methods, parsing tokens, and validating claims. To simplify this, I’ve written a custom package that wraps the golang-jwt functionality. You can check out my package here.

Below is an example of how to use my custom JWT package.

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/golang-jwt/jwt/v5"
    jwtutil "github.com/kittipat1413/go-common/util/jwt"
)

type MyCustomClaims struct {
    jwt.RegisteredClaims
    UserID string `json:"uid"`
}

func main() {
    ctx := context.Background()
    signingKey := []byte("super-secret-key")
    manager, err := jwtutil.NewJWTManager(jwtutil.HS256, signingKey)
    if err != nil {
        log.Fatalf("Failed to create JWTManager: %v", err)
    }

    // Prepare custom claims
    claims := &MyCustomClaims{
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
            Issuer:    "example-HS256",
            Subject:   "example-subject",
        },
        UserID: "abc123",
    }

    // Create the token
    tokenStringHS256, err := manager.CreateToken(ctx, claims)
    if err != nil {
        log.Fatalf("Failed to create token: %v", err)
    }
    fmt.Println("Generated Token:", tokenStringHS256)

    // Validate the token
    parsedClaims := &MyCustomClaims{}
    err = manager.ParseAndValidateToken(ctx, tokenStringHS256, parsedClaims)
    if err != nil {
        log.Fatalf("Failed to validate token: %v", err)
    }

    fmt.Printf("Token is valid! UserID: %s, Issuer: %s\n", parsedClaims.UserID, parsedClaims.Issuer)
}
Enter fullscreen mode Exit fullscreen mode

You can explore the full implementation and documentation of my package on GitHub: here.

Best Practices for JWT

  1. Use HTTPS: Always transmit tokens over secure channels.
  2. Set Expiration Times: Include a reasonable exp to limit token misuse.
  3. Secure the Secret Key: Store keys securely using environment variables or secret managers.
  4. Avoid Sensitive Data in Payload: Only include non-critical information.
  5. Use Refresh Tokens: Pair JWT with refresh tokens to securely extend sessions.

Conclusion 🥂

JWT is a powerful tool for secure, stateless authentication. The signature ensures token integrity and authenticity, making JWT ideal for APIs and distributed systems. With libraries like jwt-go, you can easily implement JWT in your Golang projects.

☕ Support My Work ☕

If you enjoy my work, consider buying me a coffee! Your support helps me keep creating valuable content and sharing knowledge. ☕

Buy Me A Coffee

Top comments (0)