Skip to main content
Version: Next

Paseto

Release Discord Test

PASETO returns a Web Token (PASETO) auth middleware.

  • For valid token, it sets the payload data in Ctx.Locals and calls next handler.
  • For invalid token, it returns "401 - Unauthorized" error.
  • For missing token, it returns "400 - BadRequest" error.

Note: Requires Go 1.25 and above

Install

This middleware supports Fiber v3.

go get -u github.com/gofiber/fiber/v3
go get -u github.com/gofiber/contrib/paseto
go get -u github.com/o1egl/paseto

Signature

pasetoware.New(config ...pasetoware.Config) func(fiber.Ctx) error
pasetoware.FromContext(c fiber.Ctx) interface{}

Config

PropertyTypeDescriptionDefault
Nextfunc(fiber.Ctx) boolDefines a function to skip this middleware when it returns true.nil
SuccessHandlerfunc(fiber.Ctx) errorSuccessHandler defines a function which is executed for a valid token.c.Next()
ErrorHandlerfunc(fiber.Ctx, error) errorErrorHandler defines a function which is executed for an invalid token.401 Invalid or expired PASETO
ValidatePayloadValidatorDefines a function to validate if payload is valid. Optional. In case payload used is created using CreateToken function. If token is created using another function, this function must be provided.nil
SymmetricKey[]byteSecret key to encrypt token. If present the middleware will generate local tokens.nil
PrivateKeyed25519.PrivateKeySecret key to sign the tokens. If present (along with its PublicKey) the middleware will generate public tokens.nil
PublicKeycrypto.PublicKeyPublic key to verify the tokens. If present (along with PrivateKey) the middleware will generate public tokens.nil
ExtractorExtractorExtractor defines a function to extract the token from the request.FromAuthHeader("Bearer")

Available Extractors

PASETO middleware uses the shared Fiber extractors (github.com/gofiber/fiber/v3/extractors) and provides several helpers for different token sources:

Import them like this:

import "github.com/gofiber/fiber/v3/extractors"

For an overview and additional examples, see the Fiber Extractors guide:

  • https://docs.gofiber.io/guide/extractors

  • extractors.FromAuthHeader(prefix string) - Extracts token from the Authorization header using the given scheme prefix (e.g., "Bearer"). This is the recommended and most secure method.

  • extractors.FromHeader(header string) - Extracts token from the specified HTTP header

  • extractors.FromQuery(param string) - Extracts token from URL query parameters

  • extractors.FromParam(param string) - Extracts token from URL path parameters

  • extractors.FromCookie(key string) - Extracts token from cookies

  • extractors.FromForm(param string) - Extracts token from form data

  • extractors.Chain(extrs ...extractors.Extractor) - Tries multiple extractors in order until one succeeds

Security Considerations

⚠️ Security Warning: When choosing an extractor, consider the security implications:

  • URL-based extractors (FromQuery, FromParam): Tokens can leak through server logs, browser referrer headers, proxy logs, and browser history. Use only for development or when security is not a primary concern.
  • Form-based extractors (FromForm): Similar risks to URL extractors, especially if forms are submitted via GET requests.
  • Header-based extractors (FromAuthHeader, FromHeader): Most secure as headers are not typically logged or exposed in referrers.
  • Cookie-based extractors (FromCookie): Secure for web applications but requires proper cookie security settings (HttpOnly, Secure, SameSite).

Recommendation: Use FromAuthHeader("Bearer") (the default) for production applications unless you have specific requirements that necessitate alternative extractors.

Migration from TokenPrefix

If you were previously using TokenPrefix, you can now use extractors.FromAuthHeader with the prefix:

// Old way
pasetoware.New(pasetoware.Config{
SymmetricKey: []byte("secret"),
TokenPrefix: "Bearer",
})

// New way
pasetoware.New(pasetoware.Config{
SymmetricKey: []byte("secret"),
Extractor: extractors.FromAuthHeader("Bearer"),
})

Examples

Below have a list of some examples that can help you start to use this middleware. In case of any additional example that doesn't show here, please take a look at the test file.

SymmetricKey

package main

import (
"time"

"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/extractors"
"github.com/o1egl/paseto"

pasetoware "github.com/gofiber/contrib/paseto"
)

const secretSymmetricKey = "symmetric-secret-key (size = 32)"

func main() {

app := fiber.New()

// Login route
app.Post("/login", login)

// Unauthenticated route
app.Get("/", accessible)

// Paseto Middleware with local (encrypted) token
apiGroup := app.Group("api", pasetoware.New(pasetoware.Config{
SymmetricKey: []byte(secretSymmetricKey),
Extractor: extractors.FromAuthHeader("Bearer"),
}))

// Restricted Routes
apiGroup.Get("/restricted", restricted)

err := app.Listen(":8088")
if err != nil {
return
}
}

func login(c fiber.Ctx) error {
user := c.FormValue("user")
pass := c.FormValue("pass")

// Throws Unauthorized error
if user != "john" || pass != "doe" {
return c.SendStatus(fiber.StatusUnauthorized)
}

// Create token and encrypt it
encryptedToken, err := pasetoware.CreateToken([]byte(secretSymmetricKey), user, 12*time.Hour, pasetoware.PurposeLocal)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}

return c.JSON(fiber.Map{"token": encryptedToken})
}

func accessible(c fiber.Ctx) error {
return c.SendString("Accessible")
}

func restricted(c fiber.Ctx) error {
payload := pasetoware.FromContext(c).(string)
return c.SendString("Welcome " + payload)
}

Test it

Login using username and password to retrieve a token.

curl --data "user=john&pass=doe" http://localhost:8088/login

Response

{
"token": "v2.local.eY7o9YAJ7Uqyo0JdyfHXKVARj3HgBhqIHckPgNIJOU6u489CXYL6bpOXbEtTB_nNM7nTFpcRVi7YAtJToxbxkkraHmE39pqjnBgkca-URgE-jhZGuhGu7ablmK-8tVoe5iY8mQqWFuJHAznTASUHh4AG55AMUcIALi6pEG28lAgVfw2azvnvbg4JOVZnjutcOVswd-ErsAuGtuEZkTmX7BfaLaO9ZvEX9cHahYPajuRjwU2TQrcpqITg-eYMNA1NuO8OVdnGf0mkUk6ElJUTZqhx4CSSylNXr7IlOwzTbUotEDAQTcNP7IRZI3VfpnRgnmtnZ5s.bnVsbAY"
}

Request a restricted resource using the token in Authorization request header.

curl localhost:8088/api/restricted -H "Authorization: Bearer v2.local.eY7o9YAJ7Uqyo0JdyfHXKVARj3HgBhqIHckPgNIJOU6u489CXYL6bpOXbEtTB_nNM7nTFpcRVi7YAtJToxbxkkraHmE39pqjnBgkca-URgE-jhZGuhGu7ablmK-8tVoe5iY8mQqWFuJHAznTASUHh4AG55AMUcIALi6pEG28lAgVfw2azvnvbg4JOVZnjutcOVswd-ErsAuGtuEZkTmX7BfaLaO9ZvEX9cHahYPajuRjwU2TQrcpqITg-eYMNA1NuO8OVdnGf0mkUk6ElJUTZqhx4CSSylNXr7IlOwzTbUotEDAQTcNP7IRZI3VfpnRgnmtnZ5s.bnVsbA"

Response

Welcome john

SymmetricKey + Custom Validator callback

package main

import (
"encoding/json"
"time"

"github.com/gofiber/fiber/v3/extractors"
"github.com/o1egl/paseto"

pasetoware "github.com/gofiber/contrib/paseto"
)

const secretSymmetricKey = "symmetric-secret-key (size = 32)"

type customPayloadStruct struct {
Name string `json:"name"`
ExpiresAt time.Time `json:"expiresAt"`
}

func main() {

app := fiber.New()

// Login route
app.Post("/login", login)

// Unauthenticated route
app.Get("/", accessible)

// Paseto Middleware with local (encrypted) token
apiGroup := app.Group("api", pasetoware.New(pasetoware.Config{
SymmetricKey: []byte(secretSymmetricKey),
Extractor: extractors.FromAuthHeader("Bearer"),
Validate: func(decrypted []byte) (any, error) {
var payload customPayloadStruct
err := json.Unmarshal(decrypted, &payload)
return payload, err
},
}))

// Restricted Routes
apiGroup.Get("/restricted", restricted)

err := app.Listen(":8088")
if err != nil {
return
}
}

func login(c fiber.Ctx) error {
user := c.FormValue("user")
pass := c.FormValue("pass")

// Throws Unauthorized error
if user != "john" || pass != "doe" {
return c.SendStatus(fiber.StatusUnauthorized)
}

// Create the payload
payload := customPayloadStruct{
Name: "John Doe",
ExpiresAt: time.Now().Add(12 * time.Hour),
}

// Create token and encrypt it
encryptedToken, err := paseto.NewV2().Encrypt([]byte(secretSymmetricKey), payload, nil)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}

return c.JSON(fiber.Map{"token": encryptedToken})
}

func accessible(c fiber.Ctx) error {
return c.SendString("Accessible")
}

func restricted(c fiber.Ctx) error {
payload := pasetoware.FromContext(c).(customPayloadStruct)
return c.SendString("Welcome " + payload.Name)
}

package main

import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/extractors"

pasetoware "github.com/gofiber/contrib/paseto"
)

const secretSymmetricKey = "symmetric-secret-key (size = 32)"

func main() {
app := fiber.New()

// Paseto Middleware with cookie extractor
app.Use(pasetoware.New(pasetoware.Config{
SymmetricKey: []byte(secretSymmetricKey),
Extractor: extractors.FromCookie("token"),
}))

app.Get("/protected", func(c fiber.Ctx) error {
return c.SendString("Protected route")
})

app.Listen(":8080")
}

Query Extractor Example

package main

import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/extractors"

pasetoware "github.com/gofiber/contrib/paseto"
)

const secretSymmetricKey = "symmetric-secret-key (size = 32)"

func main() {
app := fiber.New()

// Paseto Middleware with query extractor
app.Use(pasetoware.New(pasetoware.Config{
SymmetricKey: []byte(secretSymmetricKey),
Extractor: extractors.FromQuery("token"),
}))

app.Get("/protected", func(c fiber.Ctx) error {
return c.SendString("Protected route")
})

app.Listen(":8080")
}

PublicPrivate Key

package main

import (
"crypto/ed25519"
"encoding/hex"
"time"

"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/extractors"

pasetoware "github.com/gofiber/contrib/paseto"
)

const privateKeySeed = "e9c67fe2433aa4110caf029eba70df2c822cad226b6300ead3dcae443ac3810f"

var seed, _ = hex.DecodeString(privateKeySeed)
var privateKey = ed25519.NewKeyFromSeed(seed)

type customPayloadStruct struct {
Name string `json:"name"`
ExpiresAt time.Time `json:"expiresAt"`
}

func main() {

app := fiber.New()

// Login route
app.Post("/login", login)

// Unauthenticated route
app.Get("/", accessible)

// Paseto Middleware with public (signed) token
apiGroup := app.Group("api", pasetoware.New(pasetoware.Config{
Extractor: extractors.FromAuthHeader("Bearer"),
PrivateKey: privateKey,
PublicKey: privateKey.Public(),
}))

// Restricted Routes
apiGroup.Get("/restricted", restricted)

err := app.Listen(":8088")
if err != nil {
return
}
}

func login(c fiber.Ctx) error {
user := c.FormValue("user")
pass := c.FormValue("pass")

// Throws Unauthorized error
if user != "john" || pass != "doe" {
return c.SendStatus(fiber.StatusUnauthorized)
}

// Create token and encrypt it
encryptedToken, err := pasetoware.CreateToken(privateKey, user, 12*time.Hour, pasetoware.PurposePublic)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}

return c.JSON(fiber.Map{"token": encryptedToken})
}

func accessible(c fiber.Ctx) error {
return c.SendString("Accessible")
}

func restricted(c fiber.Ctx) error {
payload := pasetoware.FromContext(c).(string)
return c.SendString("Welcome " + payload)
}

Get the payload from the context

payloadFromCtx := pasetoware.FromContext(c)  
if payloadFromCtx == nil {
// Handle case where token is not in context, e.g. by returning an error
return
}
payload := payloadFromCtx.(string)

Test it

Login using username and password to retrieve a token.

curl --data "user=john&pass=doe" http://localhost:8088/login

Response

{
"token": "v2.public.eyJhdWQiOiJnb2ZpYmVyLmdvcGhlcnMiLCJkYXRhIjoiam9obiIsImV4cCI6IjIwMjMtMDctMTNUMDg6NDk6MzctMDM6MDAiLCJpYXQiOiIyMDIzLTA3LTEyVDIwOjQ5OjM3LTAzOjAwIiwianRpIjoiMjIzYjM0MjQtNWNkZS00NDFhLWJiZWEtZjBjYWFhYTdiYWFlIiwibmJmIjoiMjAyMy0wNy0xMlQyMDo0OTozNy0wMzowMCIsInN1YiI6InVzZXItdG9rZW4ifWiqK_yg0eJbIs2hnup4NuBYg7v4lxh33zEhEljsH7QUaZXAdtbCPK7cN-NSfSxrw68owwgo-dOlPrD7lc5M_AU.bnVsbA"
}

Request a restricted resource using the token in Authorization request header.

curl localhost:8088/api/restricted -H "Authorization: Bearer v2.public.eyJhdWQiOiJnb2ZpYmVyLmdvcGhlcnMiLCJkYXRhIjoiam9obiIsImV4cCI6IjIwMjMtMDctMTNUMDg6NDk6MzctMDM6MDAiLCJpYXQiOiIyMDIzLTA3LTEyVDIwOjQ5OjM3LTAzOjAwIiwianRpIjoiMjIzYjM0MjQtNWNkZS00NDFhLWJiZWEtZjBjYWFhYTdiYWFlIiwibmJmIjoiMjAyMy0wNy0xMlQyMDo0OTozNy0wMzowMCIsInN1YiI6InVzZXItdG9rZW4ifWiqK_yg0eJbIs2hnup4NuBYg7v4lxh33zEhEljsH7QUaZXAdtbCPK7cN-NSfSxrw68owwgo-dOlPrD7lc5M_AU.bnVsbA"

Response

Welcome John Doe