Skip to content

heywinit/go-siwx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-siwx

Simple multi-chain SIWX authentication for Go

A zero-config, chain-agnostic Go package for SIWX (Sign-In With X) authentication supporting Ethereum (EIP-4361) and Solana (SIWS). One import, three functions: New(), Challenge(), Verify(). Works with all Go web frameworks.

Go Reference License: MIT

Features

  • Zero-config defaults - Works out of the box
  • Multi-chain support - Ethereum (EIP-4361) and Solana (SIWS)
  • Framework agnostic - Works with any Go web framework
  • JWT & Opaque tokens - Choose your token format
  • Memory & Redis storage - Flexible nonce storage
  • Security first - Replay protection, nonce validation, signature verification

Installation

go get github.com/heywinit/go-siwx

Quick Start

3-Line Example

package main

import "github.com/heywinit/go-siwx"

func main() {
    client, _ := siwx.New()
    challenge, _ := client.Challenge("0x...", "eip155:1")
    session, token, _ := client.Verify(sig, "0x...", "eip155:1")
}

Usage

Basic Usage

package main

import (
    "fmt"
    "github.com/heywinit/go-siwx"
)

func main() {
    // Create client with defaults
    client, err := siwx.New()
    if err != nil {
        panic(err)
    }

    // Generate challenge message
    address := "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
    chain := "eip155:1"

    message, err := client.Challenge(address, chain)
    if err != nil {
        panic(err)
    }

    fmt.Println("Sign this message:", message)

    // After user signs, verify the signature
    signature := "0x..." // From user's wallet
    session, token, err := client.Verify(signature, address, chain)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Authenticated! Token: %s\n", token)
    fmt.Printf("Session expires: %v\n", session.Expires)
}

With Options

client, err := siwx.New(
    siwx.WithDomain("example.com"),
    siwx.WithChains("eip155:1", "solana:mainnet"),
    siwx.WithJWT([]byte("your-secret-key")),
    siwx.WithNonceTTL(5*time.Minute),
)

With Redis

import (
    "github.com/redis/go-redis/v9"
    "github.com/heywinit/go-siwx"
)

rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
})

client, err := siwx.New(
    siwx.WithRedis(rdb),
    siwx.WithJWT([]byte("secret")),
)

Framework Examples

Standard Library

package main

import (
    "net/http"
    "github.com/heywinit/go-siwx"
    "github.com/heywinit/go-siwx/handlers"
    "github.com/heywinit/go-siwx/middleware"
)

func main() {
    client, _ := siwx.New(siwx.WithJWT([]byte("secret")))

    mux := http.NewServeMux()
    mux.HandleFunc("/challenge", handlers.ChallengeHandler(client))
    mux.HandleFunc("/verify", handlers.VerifyHandler(client))

    protected := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        session := middleware.GetSession(r)
        w.Write([]byte("Hello, " + session.Address))
    })
    mux.Handle("/protected", middleware.Auth(client, protected))

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

Gin

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/heywinit/go-siwx"
    "github.com/heywinit/go-siwx/handlers"
    "github.com/heywinit/go-siwx/middleware"
)

func main() {
    client, _ := siwx.New(siwx.WithJWT([]byte("secret")))

    r := gin.Default()
    r.POST("/challenge", gin.WrapH(handlers.ChallengeHandler(client)))
    r.POST("/verify", gin.WrapH(handlers.VerifyHandler(client)))
    r.GET("/protected", gin.WrapH(middleware.Auth(client, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        session := middleware.GetSession(r)
        w.Write([]byte("Hello, " + session.Address))
    }))))

    r.Run(":8080")
}

Chi

package main

import (
    "net/http"
    "github.com/go-chi/chi/v5"
    "github.com/heywinit/go-siwx"
    "github.com/heywinit/go-siwx/handlers"
    siwxMiddleware "github.com/heywinit/go-siwx/middleware"
)

func main() {
    client, _ := siwx.New(siwx.WithJWT([]byte("secret")))

    r := chi.NewRouter()
    r.Post("/challenge", handlers.ChallengeHandler(client))
    r.Post("/verify", handlers.VerifyHandler(client))

    r.Group(func(r chi.Router) {
        r.Use(func(next http.Handler) http.Handler {
            return siwxMiddleware.Auth(client, next)
        })
        r.Get("/protected", func(w http.ResponseWriter, r *http.Request) {
            session := siwxMiddleware.GetSession(r)
            w.Write([]byte("Hello, " + session.Address))
        })
    })

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

See examples/ directory for more framework examples (Echo, Fiber, etc.).

API Reference

Client

New(opts ...Option) (*Client, error)

Creates a new SIWX client. Options:

  • WithChains(chains ...string) - Set allowed chains
  • WithRedis(client *redis.Client) - Use Redis for nonce storage
  • WithJWT(secret []byte) - Use JWT tokens with secret
  • WithOpaqueTokens() - Use opaque tokens instead of JWT
  • WithNonceTTL(ttl time.Duration) - Set nonce TTL (default: 5 minutes)
  • WithDomain(domain string) - Set domain for SIWX messages

Challenge(addr, chain string) (string, error)

Generates a challenge message for the given address and chain. Returns the message that should be signed by the user's wallet.

Verify(sig, addr, chain string) (*Session, string, error)

Verifies a signature and returns a session and token. The signature should be from the user signing the challenge message.

ValidateToken(token string) (*Session, error)

Validates a token and returns the associated session.

Session

type Session struct {
    Address string    // Wallet address
    Chain   string    // Chain identifier (eip155:1, solana:mainnet, etc.)
    Expires time.Time // Expiration time
    Issued  time.Time // Issue time
}

Supported Chains

Chain Identifier Signature Format
Ethereum Mainnet eip155:1 EIP-4361 (SIWE)
Polygon eip155:137 EIP-4361 (SIWE)
Solana Mainnet solana:mainnet SIWS (Phantom std)
Solana Devnet solana:devnet SIWS (Phantom std)

Security

  • Per-address/chain/domain nonces - Prevents replay attacks
  • 5-minute nonce TTL - Nonces expire quickly
  • One-time use nonces - Nonces are deleted after verification
  • Signature verification - Cryptographic verification of signatures
  • Domain binding - Messages are bound to your domain
  • Contract wallet support - Works with Ethereum contract wallets

Benchmarks

go test -bench=. -benchmem

Testing

go test ./...

Coverage target: 90%+

License

MIT License - see LICENSE file for details.

Contributing

Contributions welcome! Please open an issue or PR.

Author

heywinit

About

simple multi-chain SIWX auth for go

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published