Wocha Docs

Go quickstart

Add Wocha authentication to a Go HTTP server with JWT access token validation middleware and wocha-go for Management API calls.

Add Wocha authentication to a Go HTTP server: validate JWT access tokens with middleware and use wocha-go for Management API calls.

Prerequisites

  • Go 1.21+
  • A Wocha project with an OAuth application (Wocha Console)

What you'll build

A Go HTTP server with JWKS-backed JWT middleware and Management API client calls.

your-app.local

Welcome back

After sign-in

✓ Session stored securely

✓ User profile available in your app

✓ Organisation context ready for multi-tenant features

Installation

go get github.com/wocha-dev/wocha/sdks/go
go get github.com/MicahParks/keyfunc/v3
go get github.com/golang-jwt/jwt/v5

Environment variables

export WOCHA_ISSUER=https://your-tenant.auth.wocha.ai
export WOCHA_AUDIENCE=your-api-audience          # optional
export WOCHA_API_KEY=wocha_mgmt_...
export WOCHA_TENANT=your-tenant-slug

JWT middleware

Create middleware/auth.go:

package middleware
 
import (
	"context"
	"fmt"
	"net/http"
	"os"
	"strings"
	"time"
 
	"github.com/MicahParks/keyfunc/v3"
	"github.com/golang-jwt/jwt/v5"
)
 
type contextKey string
 
const UserClaimsKey contextKey = "userClaims"
 
type UserClaims struct {
	Subject string
	Email   string
	OrgID   string
}
 
func RequireAuth(next http.Handler) http.Handler {
	issuer := strings.TrimRight(os.Getenv("WOCHA_ISSUER"), "/")
	jwksURL := issuer + "/.well-known/jwks.json"
	audience := os.Getenv("WOCHA_AUDIENCE")
 
	jwks, err := keyfunc.NewDefaultCtx(context.Background(), []string{jwksURL})
	if err != nil {
		panic(fmt.Sprintf("failed to load JWKS: %v", err))
	}
 
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		authHeader := r.Header.Get("Authorization")
		if !strings.HasPrefix(authHeader, "Bearer ") {
			http.Error(w, `{"error":"missing bearer token"}`, http.StatusUnauthorized)
			return
		}
 
		tokenString := strings.TrimPrefix(authHeader, "Bearer ")
		token, err := jwt.Parse(tokenString, jwks.Keyfunc,
			jwt.WithIssuer(issuer),
			jwt.WithValidMethods([]string{"RS256", "ES256"}),
			jwt.WithLeeway(30*time.Second),
		)
		if err != nil || !token.Valid {
			http.Error(w, `{"error":"invalid token"}`, http.StatusUnauthorized)
			return
		}
 
		claims, ok := token.Claims.(jwt.MapClaims)
		if !ok {
			http.Error(w, `{"error":"invalid claims"}`, http.StatusUnauthorized)
			return
		}
 
		if audience != "" {
			if !claimAudienceMatches(claims, audience) {
				http.Error(w, `{"error":"invalid audience"}`, http.StatusUnauthorized)
				return
			}
		}
 
		user := UserClaims{
			Subject: fmt.Sprint(claims["sub"]),
			Email:   fmt.Sprint(claims["email"]),
			OrgID:   fmt.Sprint(claims["org_id"]),
		}
 
		ctx := context.WithValue(r.Context(), UserClaimsKey, user)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}
 
func claimAudienceMatches(claims jwt.MapClaims, expected string) bool {
	switch aud := claims["aud"].(type) {
	case string:
		return aud == expected
	case []any:
		for _, item := range aud {
			if fmt.Sprint(item) == expected {
				return true
			}
		}
	}
	return false
}
 
func UserFromContext(ctx context.Context) (UserClaims, bool) {
	user, ok := ctx.Value(UserClaimsKey).(UserClaims)
	return user, ok
}

Protected handler

Create main.go:

package main
 
import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
 
	"github.com/wocha-dev/wocha/sdks/go"
	"your-module/middleware"
)
 
func main() {
	client, err := wocha.NewClient(
		wocha.WithAPIKey(os.Getenv("WOCHA_API_KEY")),
		wocha.WithTenant(os.Getenv("WOCHA_TENANT")),
	)
	if err != nil {
		log.Fatal(err)
	}
 
	mux := http.NewServeMux()
 
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
	})
 
	mux.Handle("GET /api/me", middleware.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user, _ := middleware.UserFromContext(r.Context())
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]string{
			"userId": user.Subject,
			"email":  user.Email,
			"orgId":  user.OrgID,
		})
	})))
 
	mux.Handle("GET /api/admin/users", middleware.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		users, err := client.Users.List(context.Background(), &wocha.UserListParams{PageSize: 10})
		if err != nil {
			http.Error(w, `{"error":"failed to list users"}`, http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(users)
	})))
 
	addr := ":8080"
	fmt.Printf("Listening on http://localhost%s\n", addr)
	log.Fatal(http.ListenAndServe(addr, mux))
}

Testing the integration

  1. Run the server:

    go run .
  2. Obtain an access token from your frontend or hosted login.

  3. Call the protected endpoint:

    curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" http://localhost:8080/api/me

Next steps

On this page