swt

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 15, 2025 License: MIT Imports: 16 Imported by: 0

README

SecureWebhookToken (SWT)

GitHub Tag Go Report Card GitHub go.mod Go version Coverage Go Reference

SecureWebhookToken is an Internet-Draft for sending secure Webhooks, based on the JWT standard. See Documentation for more details.

Install

go get github.com/SecureWebhookToken/swt

Examples

Client / Server

package main

import (
	"fmt"
	"log/slog"
	"net/http"

	"github.com/SecureWebhookToken/swt"
)

const (
	issuer = "https://example.com"
	url    = "http://localhost:8080/webhook"
)

// WARNING: This is a weak secret for demonstration purposes only!
// In production, use a strong, randomly generated secret key.
// Generate one with: openssl rand -base64 32
var secretKey = []byte("INSECURE-EXAMPLE-SECRET-DO-NOT-USE-IN-PRODUCTION")

func main() {
	go startServer()

	// Valid webhook request
	swtReq := swt.Request{
		URL:    url,
		Issuer: issuer,
		Event:  "send.stars",
		Data:   []byte(`{"username": "me", "stars": "567"}`),
	}

	req, err := swtReq.Build(secretKey)
	if err != nil {
		slog.Error(fmt.Errorf("error building request: %w", err).Error())
		return
	}

	httpClient := &http.Client{}
	res, err := httpClient.Do(req)
	if err != nil {
		slog.Error("Error executing request", "error", err)
		return
	}

	slog.Info("Successfully executed request", "status", res.StatusCode)

	// Invalid issuer webhook request
	swtReq = swt.Request{
		URL:    url,
		Issuer: "issuer",
		Event:  "send.stars",
		Data:   []byte(`{"username": "me", "stars": "567"}`),
	}

	req, err = swtReq.Build(secretKey)
	if err != nil {
		slog.Error(fmt.Errorf("error building request: %w", err).Error())
		return
	}

	httpClient = &http.Client{}
	res, err = httpClient.Do(req)
	if err != nil {
		slog.Error("Error executing request", "error", err)
		return
	}

	slog.Info("Successfully executed request", "status", res.StatusCode)
}

func startServer() {
	handler, err := swt.NewHandlerFunc(secretKey, func(token *swt.SecureWebhookToken, data []byte) error {
		slog.Info("Successfully received token: " + token.String())

		//Validate issuer
		if token.Issuer() != issuer {
			return fmt.Errorf("invalid issuer")
		}

		slog.Info("Successfully received webhook data: " + string(data))

		return nil
	}, nil)
	if err != nil {
		slog.Error("Failed to create handler", "error", err)
		return
	}

	http.Handle("/webhook", handler)

	slog.Error("Server error", "error", http.ListenAndServe(":8080", nil))
}

Documentation

Overview

Package swt implements the [Secure Webhook Token (SWT)](https://datatracker.ietf.org/doc/draft-knauer-secure-webhook-token/) an Internet-Draft for standardized webhook request.

Index

Constants

View Source
const (

	// Supported hash algorithms
	SHA256   = "sha-256"
	SHA384   = "sha-384"
	SHA512   = "sha-512"
	SHA3_256 = "sha3-256"
	SHA3_384 = "sha3-384"
	SHA3_512 = "sha3-512"
)

Variables

View Source
var (
	ErrInvalidToken             = errors.New("invalid token - please use New or NewWithClaims to properly initialize token")
	ErrMissingClaims            = errors.New("missing claims")
	ErrInvalidOption            = errors.New("invalid option")
	ErrInvalidData              = errors.New("invalid data")
	ErrInvalidTokenHandler      = errors.New("invalid token handler function")
	ErrInvalidHeaderClaim       = errors.New("invalid token header claim")
	ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm")
)

Functions

func HashSum

func HashSum(hashAlg string, body []byte) (string, error)

HashSum computes the hash sum for a given hashAlg and the body to be sent via http.MethodPost request.

func NewHandlerFunc

func NewHandlerFunc(secret []byte, handleFn func(token *SecureWebhookToken, data []byte) error, opts *HandlerOptions) (http.HandlerFunc, error)

NewHandlerFunc Creates a new http.HandlerFunc which will process an incoming webhook request and pass the token, if valid, to the given handleFn. Returns an error if handleFn is nil.

Types

type Claims

type Claims interface {
	GetID() (string, error)
	GetWebhook() (Webhook, error)

	jwt.Claims
	// contains filtered or unexported methods
}

type HandlerOptions

type HandlerOptions struct {
	MaxBodySize        int64         // Maximum allowed request body size (default: 32MB)
	Logger             *slog.Logger  // Custom logger (default: slog.Default())
	ReturnErrorDetails bool          // Return JSON error details in response body (default: false)
	ReplayChecker      ReplayChecker // Optional replay attack protection
}

type Hash added in v0.3.0

type Hash string

func NewHash added in v0.3.0

func NewHash(alg string, data []byte) (*Hash, error)

func NewHashFromString added in v0.3.0

func NewHashFromString(input string) *Hash

func (*Hash) Algorithm added in v0.3.0

func (h *Hash) Algorithm() string

func (*Hash) String added in v0.3.0

func (h *Hash) String() string

func (*Hash) Sum added in v0.3.0

func (h *Hash) Sum() string

type Option

type Option func(swt *SecureWebhookToken)

func WithAudience

func WithAudience(ids ...string) Option

WithAudience overrides the aud claim. The Audience claim is optional and is nil by default. It can consist of zero or more recipients containing a StringOrURI value.

func WithExpiresAt

func WithExpiresAt(t time.Time) Option

WithExpiresAt overrides the default exp claim.

func WithID

func WithID(id string) Option

WithID overrides the jti claim with a given id. Should be a UUID or similar unique id.

func WithNotBefore

func WithNotBefore(t time.Time) Option

WithNotBefore overrides the default nbf claim.

func WithRetryCount added in v0.3.0

func WithRetryCount(rc uint) Option

WithRetryCount sets the retry count for the webhook token. This is metadata that indicates how many times this webhook delivery has been attempted. The receiver can use this to: - Track delivery attempts - Apply different handling logic for retries (e.g., idempotency checks) - Implement exponential backoff strategies Note: This library does not automatically implement retry logic - this is informational only. Senders should increment this count when resending failed webhooks.

func WithSigningMethod

func WithSigningMethod(method *jwt.SigningMethodHMAC) Option

WithSigningMethod allows overriding the default signing method HS256. For the convenience of SecureHookTokens usage, only symmetrical signatures are supported. (HS256, HS384 and HS512)

func WithSubject

func WithSubject(s string) Option

WithSubject overrides the default sub claim.

type ReplayChecker added in v0.4.0

type ReplayChecker interface {
	// CheckAndRecord checks if a token ID has been seen before and records it if not.
	// Returns an error if the token has already been processed (replay attack detected).
	CheckAndRecord(ctx context.Context, jti string) error
}

ReplayChecker is an interface for checking and recording token IDs to prevent replay attacks. Implementations should store the jti (JWT ID) claim and reject tokens with previously seen IDs.

type Request added in v0.2.0

type Request struct {
	URL     string // Target URL for the webhook request
	Issuer  string // JWT issuer claim (iss) - typically the service sending the webhook
	Event   string // Event name following EVENT_NAME.ACTIVITY format (e.g., "user.created")
	HashAlg string // Hash algorithm for POST requests (SHA-256, SHA3-256, etc.). Defaults to SHA-256 if empty
	Data    []byte // Payload data to be sent with the request
}

Request represents a webhook request configuration for creating SecureWebhookTokens. It contains all the necessary information to build HTTP requests with embedded tokens.

func (*Request) Build added in v0.2.0

func (r *Request) Build(key any, opts ...Option) (*http.Request, error)

Build can be used to create a http.Request for creating and sending a SecureWebhookToken via POST method (the only allowed HTTP method for Secure Webhook Tokens).

func (*Request) BuildWithContext added in v0.4.0

func (r *Request) BuildWithContext(ctx context.Context, key any, opts ...Option) (*http.Request, error)

BuildWithContext creates an http.Request with context support for creating and sending a SecureWebhookToken via POST method. The context can be used for cancellation and timeout control.

type SecureWebhookToken

type SecureWebhookToken struct {
	// contains filtered or unexported fields
}

SecureWebhookToken is a structure for secure webhook tokens. Currently, it implements symmetrical signatures based on HMAC-SHA for example purposes only. If the Internet-Draft is accepted, other methods will be implemented in the future.

func New

func New(issuer, event string, hash *Hash, opts ...Option) (*SecureWebhookToken, error)

New creates a SecureWebhookToken with the given issuer, event name, and data. Can be further customized with additional Option parameters. See Option for available functional parameters. Returns an error if claims or options are invalid.

func NewWithClaims

func NewWithClaims(c Claims, opts ...Option) (*SecureWebhookToken, error)

NewWithClaims creates a new SecureWebhookToken with the given Claims object. Must be a pointer to an instance of WebhookClaims or a custom Claims instance, which embeds the WebhookClaims. Returns an error if claims are nil, options are invalid, or signing method is "none".

func Parse

func Parse(tokenStr string, key any) (*SecureWebhookToken, error)

Parse parses a given token string, verifies it and returns a SecureWebhookToken if successful.

func ParseWithClaims

func ParseWithClaims(tokenStr string, claims Claims, key any) (*SecureWebhookToken, error)

ParseWithClaims parses a given token string and given claims, verifies it and returns a SecureWebhookToken if successful.

func ParseWithClaimsContext added in v0.4.0

func ParseWithClaimsContext(ctx context.Context, tokenStr string, claims Claims, key any) (*SecureWebhookToken, error)

ParseWithClaimsContext parses a given token string and given claims with context support, verifies it and returns a SecureWebhookToken if successful. The context can be used for cancellation and timeout control.

func ParseWithContext added in v0.4.0

func ParseWithContext(ctx context.Context, tokenStr string, key any) (*SecureWebhookToken, error)

ParseWithContext parses a given token string with context support, verifies it and returns a SecureWebhookToken if successful. The context can be used for cancellation and timeout control.

func (*SecureWebhookToken) Algorithm

func (swt *SecureWebhookToken) Algorithm() string

Algorithm returns the used SigningMethod algorithm.

func (*SecureWebhookToken) Claims

func (swt *SecureWebhookToken) Claims() Claims

Claims return all token Claims. If no custom WebhookClaims have been set with NewWithClaims(), then the underlying type will be *WebhookClaims.

func (*SecureWebhookToken) ID

func (swt *SecureWebhookToken) ID() string

ID convenient method for accessing the ID of the SecureWebhookToken.

func (*SecureWebhookToken) Issuer

func (swt *SecureWebhookToken) Issuer() string

Issuer convenient method for accessing the Issuer claim of the SecureWebhookToken.

func (*SecureWebhookToken) SignedString

func (swt *SecureWebhookToken) SignedString(key any) (string, error)

SignedString returns the encoded and signed SecureWebhookToken as a JWT string.

func (*SecureWebhookToken) String

func (swt *SecureWebhookToken) String() string

String implements the Stringer interface for returning the SecureWebhookToken as a JSON string.

func (*SecureWebhookToken) Valid

func (swt *SecureWebhookToken) Valid() bool

Valid returns true only if the SecureWebhookToken has been created via Parse method and successfully been validated.

func (*SecureWebhookToken) Validate

func (swt *SecureWebhookToken) Validate() error

Validate validates the Claims of the SecureWebhookToken.

func (*SecureWebhookToken) Webhook

func (swt *SecureWebhookToken) Webhook() Webhook

Webhook convenient method for accessing the Webhook claim of the SecureWebhookToken.

type Webhook

type Webhook struct {
	Event      string `json:"event"` // Event name. Should have the form EVENT_NAME.ACTIVITY (e.g., user.created or pull_request.merged)
	Hash       *Hash  `json:"hash,omitempty"`
	RetryCount uint   `json:"retry_count"`
}

type WebhookClaims

type WebhookClaims struct {
	Webhook Webhook `json:"webhook"`
	jwt.RegisteredClaims
}

func NewWebhookClaims

func NewWebhookClaims(issuer, event string) WebhookClaims

NewWebhookClaims creates WebhookClaims with a given issuer, event name and hash, and the default registered claims.

func (*WebhookClaims) GetID

func (wc *WebhookClaims) GetID() (string, error)

the `jti` (JWT ID) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7

func (*WebhookClaims) MapClaims

func (wc *WebhookClaims) MapClaims() jwt.MapClaims

MapClaims will convert a WebhookClaims object into jwt.MapClaims Can easily be used with the golang-jwt package for creating JWTs directly if desired.

func (*WebhookClaims) Validate

func (wc *WebhookClaims) Validate() error

Validate implements the jwt.ClaimsValidator interface to perform further required validation on specific claims.

Directories

Path Synopsis
examples
client command
custom_claims command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL