Skip to main content

Overview

This tutorial shows you how to authenticate to the Issuing API using Go by generating signed JSON Web Tokens (JWTs).

In This Recipe

  1. Install Dependencies
  2. Create signJWT function
  3. Usage - GET Request
  4. Usage - POST Request

Prerequisites

  • Go 1.18 or higher
  • Your Issuing API credentials (Access Key and RSA key pair)

Step 1: Install Dependencies

Install the required Go modules:
go get github.com/golang-jwt/jwt/v5

Step 2: Create signJWT Function

Create a utility function to handle JWT signing with SHA256 body hashing:
package main

import (
	"crypto"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"os"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// SignJWT creates a signed JWT for Issuing API authentication
func SignJWT(privateKeyPath, accessKey, uri, method, requestBody string) (string, error) {
	// Read private key file
	keyData, err := os.ReadFile(privateKeyPath)
	if err != nil {
		return "", fmt.Errorf("failed to read private key: %w", err)
	}

	// Parse PEM block
	block, _ := pem.Decode(keyData)
	if block == nil {
		return "", fmt.Errorf("failed to parse PEM block")
	}

	// Parse private key
	var privateKey *rsa.PrivateKey
	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		// Try PKCS1 format as fallback
		privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
		if err != nil {
			return "", fmt.Errorf("failed to parse private key: %w", err)
		}
	} else {
		var ok bool
		privateKey, ok = key.(*rsa.PrivateKey)
		if !ok {
			return "", fmt.Errorf("private key is not RSA")
		}
	}

	// Hash request body with SHA256
	hash := sha256.Sum256([]byte(requestBody))
	hashedBody := hex.EncodeToString(hash[:])

	now := time.Now()

	// Create JWT claims
	claims := jwt.MapClaims{
		"sub":    accessKey,
		"iat":    now.Unix(),
		"exp":    now.Add(30 * time.Second).Unix(), // 30 second expiration
		"uri":    uri,
		"method": method,
		"body":   hashedBody,
	}

	// Create and sign token
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
	token.Header["typ"] = "JWT"

	signedToken, err := token.SignedString(privateKey)
	if err != nil {
		return "", fmt.Errorf("failed to sign token: %w", err)
	}

	return signedToken, nil
}

Important Notes:

  • Request body is hashed with SHA256 before signing
  • 30 second expiration for security
  • Private key format: PEM-encoded PKCS8 or PKCS1
  • Empty body: Use empty string "" for GET requests

Step 3: Usage - GET Request

Make a simple GET request to test connectivity:
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	privateKeyPath := "./utgl-access.private" // Path to your private key
	accessKey := "6e33a078-99ed-4aa1-8e67-b0e19e9475fd" // Replace with your access key

	uri := "/v1/ping"
	method := "GET"
	requestBody := ""

	// Generate JWT
	jwtToken, err := SignJWT(privateKeyPath, accessKey, uri, method, requestBody)
	if err != nil {
		fmt.Printf("Error signing JWT: %v\n", err)
		return
	}

	// Make HTTP request
	client := &http.Client{}
	req, err := http.NewRequest(method, "https://sandbox.access.utgl.io"+uri, nil)
	if err != nil {
		fmt.Printf("Error creating request: %v\n", err)
		return
	}

	req.Header.Set("Authorization", "Bearer "+jwtToken)
	req.Header.Set("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("Error making request: %v\n", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("%s %s %d\n", method, uri, resp.StatusCode)
	fmt.Printf("Response: %s\n", string(body))
}

Step 4: Usage - POST Request

Make a POST request with a request body:
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
)

func main() {
	privateKeyPath := "./utgl-access.private" // Path to your private key
	accessKey := "6e33a078-99ed-4aa1-8e67-b0e19e9475fd" // Replace with your access key

	uri := "/v1/ping"
	method := "POST"
	requestBody := `{"message": "Hello, world!"}`

	// Generate JWT
	jwtToken, err := SignJWT(privateKeyPath, accessKey, uri, method, requestBody)
	if err != nil {
		fmt.Printf("Error signing JWT: %v\n", err)
		return
	}

	// Make HTTP request
	client := &http.Client{}
	req, err := http.NewRequest(method, "https://sandbox.access.utgl.io"+uri, bytes.NewBufferString(requestBody))
	if err != nil {
		fmt.Printf("Error creating request: %v\n", err)
		return
	}

	req.Header.Set("Authorization", "Bearer "+jwtToken)
	req.Header.Set("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("Error making request: %v\n", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("%s %s %d\n", method, uri, resp.StatusCode)
	fmt.Printf("Response: %s\n", string(body))
}

Common Pitfalls

Body Hashing is Critical! The SignJWT function automatically hashes the request body with SHA256 before including it in the JWT claims. Make sure you pass the exact same request body to both SignJWT() and your HTTP request.
Token Expiration: JWT tokens expire after 30 seconds. Generate a fresh token for each API request to avoid authentication errors.

Next Steps