JWT Starter

Introduction

The JWT (JSON Web Token) Starter provides authentication and authorization capabilities for Hiboot applications. It supports token generation, validation, and middleware integration.

Installation

Import the JWT starter in your application:

import "github.com/hidevopsio/hiboot/pkg/starter/jwt"

Configuration

Configure JWT in your application.yml:

app:
  profiles:
    include:
    - jwt

jwt:
  # RSA private key file path
  private_key_path: "config/keys/private.pem"
  # RSA public key file path
  public_key_path: "config/keys/public.pem"

Generating RSA Keys

Generate RSA key pair for token signing:

# Generate private key
openssl genrsa -out config/keys/private.pem 2048

# Generate public key from private key
openssl rsa -in config/keys/private.pem -pubout -out config/keys/public.pem

Basic Usage

Token Generation

Inject jwt.Token to generate tokens:

package controller

import (
	"time"

	"github.com/hidevopsio/hiboot/pkg/app"
	"github.com/hidevopsio/hiboot/pkg/at"
	"github.com/hidevopsio/hiboot/pkg/model"
	"github.com/hidevopsio/hiboot/pkg/starter/jwt"
)

type loginController struct {
	at.RestController
	at.RequestMapping `value:"/auth"`

	token jwt.Token
}

type LoginRequest struct {
	model.RequestBody
	Username string `json:"username" validate:"required"`
	Password string `json:"password" validate:"required"`
}

type LoginResponse struct {
	Token     string `json:"token"`
	ExpiresIn int64  `json:"expires_in"`
}

func init() {
	app.Register(newLoginController)
}

func newLoginController(token jwt.Token) *loginController {
	return &loginController{
		token: token,
	}
}

// Post handles POST /auth/login
func (c *loginController) PostLogin(request *LoginRequest) (model.Response, error) {
	// Validate credentials (implement your own logic)
	if request.Username != "admin" || request.Password != "password" {
		response := new(model.BaseResponse)
		response.SetCode(401)
		response.SetMessage("Invalid credentials")
		return response, nil
	}

	// Generate JWT token
	claims := jwt.Map{
		"username": request.Username,
		"role":     "admin",
	}

	// Token expires in 30 minutes
	tokenString, err := c.token.Generate(claims, 30, time.Minute)
	if err != nil {
		return nil, err
	}

	response := new(model.BaseResponse)
	response.SetData(&LoginResponse{
		Token:     tokenString,
		ExpiresIn: 1800, // 30 minutes in seconds
	})
	return response, nil
}

JWT Middleware

Use the JWT middleware to protect routes:

package controller

import (
	"github.com/hidevopsio/hiboot/pkg/app"
	"github.com/hidevopsio/hiboot/pkg/at"
	"github.com/hidevopsio/hiboot/pkg/model"
	"github.com/hidevopsio/hiboot/pkg/starter/jwt"
)

type protectedController struct {
	at.RestController
	at.RequestMapping `value:"/api"`

	// JWT Middleware for route protection
	Middleware *jwt.Middleware `inject:""`
}

func init() {
	app.Register(newProtectedController)
}

func newProtectedController() *protectedController {
	return &protectedController{}
}

// Before is called before each request
// Use this to apply JWT validation
func (c *protectedController) Before() {
	c.Middleware.Serve(c.Ctx)
}

// GetProfile handles GET /api/profile
func (c *protectedController) GetProfile() (model.Response, error) {
	// Get claims from context
	claims := c.Ctx.Values().Get("jwt").(*jwt.Token)

	response := new(model.BaseResponse)
	response.SetData(map[string]interface{}{
		"username": claims.Get("username"),
		"role":     claims.Get("role"),
	})
	return response, nil
}

Token API

jwt.Token Interface

type Token interface {
	// Generate creates a new JWT token
	Generate(claims Map, expiresAt int64, timeUnit time.Duration) (string, error)

	// VerifyKey returns the public key for verification
	VerifyKey() interface{}

	// SignKey returns the private key for signing
	SignKey() interface{}
}

jwt.Map

jwt.Map is an alias for map[string]interface{} used for token claims:

claims := jwt.Map{
	"user_id":  123,
	"username": "john",
	"role":     "admin",
	"permissions": []string{"read", "write"},
}

Advanced Usage

Custom Token Expiration

// Token valid for 24 hours
token, err := c.token.Generate(claims, 24, time.Hour)

// Token valid for 7 days
token, err := c.token.Generate(claims, 7*24, time.Hour)

// Token valid for 15 minutes
token, err := c.token.Generate(claims, 15, time.Minute)

Extracting Claims from Token

func (c *protectedController) GetUserInfo() (model.Response, error) {
	// Access JWT claims from context
	jwtValue := c.Ctx.Values().Get("jwt")
	if jwtValue == nil {
		response := new(model.BaseResponse)
		response.SetCode(401)
		response.SetMessage("Unauthorized")
		return response, nil
	}

	token := jwtValue.(*jwt.MapClaims)
	username := (*token)["username"].(string)
	role := (*token)["role"].(string)

	response := new(model.BaseResponse)
	response.SetData(map[string]interface{}{
		"username": username,
		"role":     role,
	})
	return response, nil
}

Role-Based Access Control

func (c *adminController) Before() {
	c.Middleware.Serve(c.Ctx)

	// Check for admin role
	jwtValue := c.Ctx.Values().Get("jwt")
	if jwtValue != nil {
		token := jwtValue.(*jwt.MapClaims)
		role := (*token)["role"].(string)
		if role != "admin" {
			c.Ctx.StatusCode(403)
			c.Ctx.JSON(map[string]string{
				"error": "Forbidden: Admin access required",
			})
			c.Ctx.StopExecution()
		}
	}
}

Making Authenticated Requests

Include the JWT token in the Authorization header:

# Login to get token
TOKEN=$(curl -s -X POST http://localhost:8080/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"password"}' | jq -r '.data.token')

# Make authenticated request
curl http://localhost:8080/api/profile \
  -H "Authorization: Bearer $TOKEN"

Configuration Reference

Property Description Default
jwt.private_key_path Path to RSA private key -
jwt.public_key_path Path to RSA public key -

What’s Next?