HelloJohn / docs
Quickstart

Quickstart — Go

Verify HelloJohn tokens in your Go backend in 10 minutes. Works with net/http, Gin, Echo, Chi, and any Go HTTP framework.

Time: ~10 minutes Prerequisites: A HelloJohn account (Cloud) or a self-hosted server running locally. Go 1.21+.

Install the SDK

go get github.com/hellojohn/hellojohn-go

Initialize the client

internal/auth/client.go
package auth

import (
	"os"

	hellojohn "github.com/hellojohn/hellojohn-go"
)

var Client *hellojohn.Client

func init() {
	Client = hellojohn.New(hellojohn.Config{
		SecretKey: os.Getenv("HELLOJOHN_SECRET_KEY"),
	})
}

Your Secret Key must never be hardcoded or committed to version control. Use environment variables or a secrets manager.

Verify a token (net/http middleware)

internal/middleware/auth.go
package middleware

import (
	"context"
	"net/http"
	"strings"

	"myapp/internal/auth"
)

type contextKey string

const UserIDKey contextKey = "userID"

func RequireAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		authHeader := r.Header.Get("Authorization")
		if !strings.HasPrefix(authHeader, "Bearer ") {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		token := strings.TrimPrefix(authHeader, "Bearer ")

		payload, err := auth.Client.VerifyToken(r.Context(), token)
		if err != nil {
			http.Error(w, "Invalid token", http.StatusUnauthorized)
			return
		}

		ctx := context.WithValue(r.Context(), UserIDKey, payload.Subject)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

Use the middleware in your router:

cmd/server/main.go
package main

import (
	"net/http"

	"myapp/internal/middleware"
)

func main() {
	mux := http.NewServeMux()

	protected := http.NewServeMux()
	protected.HandleFunc("/profile", profileHandler)

	mux.Handle("/api/", middleware.RequireAuth(http.StripPrefix("/api", protected)))

	http.ListenAndServe(":8080", mux)
}

Verify a token (Gin middleware)

internal/middleware/gin_auth.go
package middleware

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"myapp/internal/auth"
)

func GinRequireAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		authHeader := c.GetHeader("Authorization")
		if !strings.HasPrefix(authHeader, "Bearer ") {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
			return
		}

		token := strings.TrimPrefix(authHeader, "Bearer ")

		payload, err := auth.Client.VerifyToken(c.Request.Context(), token)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
			return
		}

		c.Set("userID", payload.Subject)
		c.Next()
	}
}

Read the user ID in a handler

internal/handlers/profile.go
package handlers

import (
	"encoding/json"
	"net/http"

	"myapp/internal/middleware"
)

func ProfileHandler(w http.ResponseWriter, r *http.Request) {
	userID, ok := r.Context().Value(middleware.UserIDKey).(string)
	if !ok {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"user_id": userID,
	})
}

How token verification works

HelloJohn issues EdDSA-signed JWTs. VerifyToken():

  1. Fetches and caches your instance's JWKS endpoint (thread-safe)
  2. Verifies the EdDSA signature using the public key
  3. Validates expiry (exp), issuer (iss), and audience (aud) claims
  4. Returns the decoded *jwt.Payload — zero database calls

The JWKS is refreshed automatically when a key rotation is detected.

Next steps

On this page