Paseto

Paseto is everything you love about JOSE (JWT, JWE, JWS) without any of the many design deficits that plague the JOSE standards.

This is the PASETO middleware for Hertz framework.

Install

go get github.com/hertz-contrib/paseto

Example

package main

import (
   "context"
   "fmt"
   "net/http"
   "time"

   "github.com/cloudwego/hertz/pkg/app"
   "github.com/cloudwego/hertz/pkg/app/client"
   "github.com/cloudwego/hertz/pkg/app/server"
   "github.com/cloudwego/hertz/pkg/common/hlog"
   "github.com/cloudwego/hertz/pkg/protocol"
   "github.com/hertz-contrib/paseto"
)

func performRequest() {
   time.Sleep(time.Second)
   c, _ := client.NewClient()
   req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()
   req.SetRequestURI("http://127.0.0.1:8080/paseto")

   req.SetMethod("GET")
   _ = c.Do(context.Background(), req, resp)
   fmt.Printf("get token: %s\n", resp.Body())

   req.SetMethod("POST")
   req.SetHeader("Authorization", string(resp.Body()))
   _ = c.Do(context.Background(), req, resp)
   fmt.Printf("Authorization response :%s", resp.Body())
}

func main() {
   h := server.New(server.WithHostPorts(":8080"))
   h.GET("/paseto", func(c context.Context, ctx *app.RequestContext) {
      now := time.Now()
      genTokenFunc := paseto.DefaultGenTokenFunc()
      token, err := genTokenFunc(&paseto.StandardClaims{
         Issuer:    "cwg-issuer",
         ExpiredAt: now.Add(time.Hour),
         NotBefore: now,
         IssuedAt:  now,
      }, nil, nil)
      if err != nil {
         hlog.Error("generate token failed")
      }
      ctx.String(http.StatusOK, token)
   })

   h.POST("/paseto", paseto.New(), func(c context.Context, ctx *app.RequestContext) {
      ctx.String(http.StatusOK, "token is valid")
   })

   go performRequest()

   h.Spin()
}

Options

Option Default Description
Next nil Used to define a function to skip this middleware when returned true
ErrorHandler output log and response 401 Used to define a function which is executed when an error occurs
SuccessHandler save the claims to app.RequestContext Used to define a function which is executed when the token is valid
KeyLookup header:Authorization Used to define a string in the form of “:” that is used to create an Extractor that extracts the token from the request
TokenPrefix "" Used to define a string that holds the prefix for the token lookup
ParseFunc parse V4 Public Token Used to parse and verify token

Next

WithNext sets a function to judge whether to skip this middleware.

Function Signature:

func WithNext(f NextHandler) Option

Sample code:

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/client"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/protocol"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	"github.com/hertz-contrib/paseto"
)

func performRequest() {
	time.Sleep(time.Second)
	c, _ := client.NewClient()
	req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()
	req.SetRequestURI("http://127.0.0.1:8080/paseto")

	req.SetMethod("GET")
	_ = c.Do(context.Background(), req, resp)

	req.SetMethod("POST")
	req.SetHeader("Authorization", string(resp.Body()))
	_ = c.Do(context.Background(), req, resp)
	fmt.Printf("Authorization response :%s,because I have the token\n", resp.Body())

	req.SetMethod("POST")
	req.SetHeader("skip", "yes")
	_ = c.Do(context.Background(), req, resp)
	fmt.Printf("Authorization response :%s,because I trigger the nextFunc\n", resp.Body())

	req.SetMethod("POST")
	_ = c.Do(context.Background(), req, resp)
	fmt.Printf("Authorization response :%s,because I don't have token nor trigger the nextFunc\n", resp.Body())
}

func main() {
	h := server.New(server.WithHostPorts(":8080"))

	next := func(ctx context.Context, c *app.RequestContext) bool {
		return string(c.GetHeader("skip")) == "yes"
	}
	h.GET("/paseto", func(c context.Context, ctx *app.RequestContext) {
		now := time.Now()
		genTokenFunc := paseto.DefaultGenTokenFunc()
		token, err := genTokenFunc(&paseto.StandardClaims{
			Issuer:    "cwg-issuer",
			ExpiredAt: now.Add(time.Hour),
			NotBefore: now,
			IssuedAt:  now,
		}, nil, nil)
		if err != nil {
			hlog.Error("generate token failed")
		}
		ctx.String(consts.StatusOK, token)
	})

	h.POST("/paseto", paseto.New(paseto.WithNext(next)), func(c context.Context, ctx *app.RequestContext) {
		ctx.String(consts.StatusOK, "token is valid")
	})

	go performRequest()

	h.Spin()
}

ErrorFunc

WithErrorFunc sets ErrorHandler.

ErrorHandler defines a function which is executed when an error occurs.

Function Signature:

func WithErrorFunc(f app.HandlerFunc) Option

Sample code:

package main

import (
   "context"
   "fmt"
   "net/http"
   "time"

   "github.com/cloudwego/hertz/pkg/app"
   "github.com/cloudwego/hertz/pkg/app/client"
   "github.com/cloudwego/hertz/pkg/app/server"
   "github.com/cloudwego/hertz/pkg/common/hlog"
   "github.com/cloudwego/hertz/pkg/common/utils"
   "github.com/cloudwego/hertz/pkg/protocol"
   "github.com/cloudwego/hertz/pkg/protocol/consts"
   "github.com/hertz-contrib/paseto"
)

func performRequest() {
   time.Sleep(time.Second)
   c, _ := client.NewClient()
   req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()

   req.SetMethod("GET")
   req.SetRequestURI("http://127.0.0.1:8080/paseto/withsecret")
   _ = c.Do(context.Background(), req, resp)

   req.SetMethod("POST")
   req.SetRequestURI("http://127.0.0.1:8080/paseto")
   req.SetHeader("Authorization", string(resp.Body()))
   _ = c.Do(context.Background(), req, resp)
   fmt.Printf("Authorization response:%s\n", resp.Body())

   req.SetMethod("GET")
   req.SetRequestURI("http://127.0.0.1:8080/paseto/withnosecret")
   _ = c.Do(context.Background(), req, resp)

   req.SetMethod("POST")
   req.SetRequestURI("http://127.0.0.1:8080/paseto")
   req.SetHeader("Authorization", string(resp.Body()))
   _ = c.Do(context.Background(), req, resp)
   fmt.Printf("Authorization response:%s", resp.Body())
}

func main() {
   h := server.New(server.WithHostPorts(":8080"))
   
   handler := func(ctx context.Context, c *app.RequestContext) {
      c.JSON(http.StatusUnauthorized, "invalid token")
      c.Abort()
   }
   
   h.GET("/paseto/withsecret", func(c context.Context, ctx *app.RequestContext) {
      now := time.Now()
      genTokenFunc := paseto.DefaultGenTokenFunc()
      token, err := genTokenFunc(&paseto.StandardClaims{
         Issuer:    "cwg-issuer",
         ExpiredAt: now.Add(time.Hour),
         NotBefore: now,
         IssuedAt:  now,
      }, utils.H{
         "secret1": "answer1",
      }, nil)
      if err != nil {
         hlog.Error("generate token failed")
      }
      ctx.String(consts.StatusOK, token)
   })

   h.GET("/paseto/witherrorfunc", func(c context.Context, ctx *app.RequestContext) {
      now := time.Now()
      genTokenFunc := paseto.DefaultGenTokenFunc()
      token, err := genTokenFunc(&paseto.StandardClaims{
         Issuer:    "cwg-issuer",
         ExpiredAt: now.Add(time.Hour),
         NotBefore: now,
         IssuedAt:  now,
      }, nil, nil)
      if err != nil {
         hlog.Error("generate token failed")
      }
      ctx.String(consts.StatusOK, token)
   })

   h.POST("/paseto", paseto.New(paseto.WithErrorFunc(handler)), func(c context.Context, ctx *app.RequestContext) {
      ctx.String(consts.StatusOK, "token is valid")
   })

   go performRequest()

   h.Spin()
}

SuccessHandler

WithSuccessHandler sets the logic to handle the Parsed token.

Function Signature:

func WithSuccessHandler(f SuccessHandler) Option

Sample code:

package main

import (
	"context"
	"fmt"
	"time"

	gpaseto "aidanwoods.dev/go-paseto"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/client"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/cloudwego/hertz/pkg/protocol"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	"github.com/hertz-contrib/paseto"
)

func performRequest() {
	time.Sleep(time.Second)
	c, _ := client.NewClient()
	req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()

	req.SetMethod("GET")
	req.SetRequestURI("http://127.0.0.1:8080/paseto/withsecret")
	_ = c.Do(context.Background(), req, resp)

	req.SetMethod("POST")
	req.SetRequestURI("http://127.0.0.1:8080/paseto")
	req.SetHeader("Authorization", string(resp.Body()))
	_ = c.Do(context.Background(), req, resp)
	fmt.Printf("Authorization response:%s\n", resp.Body())

	req.SetMethod("GET")
	req.SetRequestURI("http://127.0.0.1:8080/paseto/withnosecret")
	_ = c.Do(context.Background(), req, resp)

	req.SetMethod("POST")
	req.SetRequestURI("http://127.0.0.1:8080/paseto")
	req.SetHeader("Authorization", string(resp.Body()))
	_ = c.Do(context.Background(), req, resp)
	fmt.Printf("Authorization response:%s", resp.Body())
}

func main() {
	h := server.New(server.WithHostPorts(":8080"))

	handler := func(ctx context.Context, c *app.RequestContext, token *gpaseto.Token) {
		var answer string
		if err := token.Get("secret1", &answer); err != nil {
			c.String(consts.StatusBadRequest, "you don't not the answer of secret1")
			c.Abort()
		}
	}
	h.GET("/paseto/withsecret", func(c context.Context, ctx *app.RequestContext) {
		now := time.Now()
		genTokenFunc := paseto.DefaultGenTokenFunc()
		token, err := genTokenFunc(&paseto.StandardClaims{
			Issuer:    "cwg-issuer",
			ExpiredAt: now.Add(time.Hour),
			NotBefore: now,
			IssuedAt:  now,
		}, utils.H{
			"secret1": "answer1",
		}, nil)
		if err != nil {
			hlog.Error("generate token failed")
		}
		ctx.String(consts.StatusOK, token)
	})

	h.GET("/paseto/withnosecret", func(c context.Context, ctx *app.RequestContext) {
		now := time.Now()
		genTokenFunc := paseto.DefaultGenTokenFunc()
		token, err := genTokenFunc(&paseto.StandardClaims{
			Issuer:    "cwg-issuer",
			ExpiredAt: now.Add(time.Hour),
			NotBefore: now,
			IssuedAt:  now,
		}, nil, nil)
		if err != nil {
			hlog.Error("generate token failed")
		}
		ctx.String(consts.StatusOK, token)
	})

	h.POST("/paseto", paseto.New(paseto.WithSuccessHandler(handler)), func(c context.Context, ctx *app.RequestContext) {
		ctx.String(consts.StatusOK, "token is valid")
	})

	go performRequest()

	h.Spin()
}

KeyLookup

WithKeyLookUp sets a string in the form of “:” that is used to create an Extractor that extracts the token from the request.

Function Signature:

func WithKeyLookUp(lookup string) Option

Sample code:

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/client"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/protocol"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	"github.com/hertz-contrib/paseto"
)

func performRequest() {
	time.Sleep(time.Second)
	c, _ := client.NewClient()
	req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()
	req.SetRequestURI("http://127.0.0.1:8080/paseto")

	req.SetMethod("GET")
	_ = c.Do(context.Background(), req, resp)
	fmt.Printf("get token: %s\n", resp.Body())

	req.SetMethod("POST")
	req.SetBody([]byte("Authorization=" + string(resp.Body())))
	req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
	_ = c.Do(context.Background(), req, resp)
	fmt.Printf("Authorization response :%s", resp.Body())
}

func main() {
	h := server.New(server.WithHostPorts(":8080"))
	h.GET("/paseto", func(c context.Context, ctx *app.RequestContext) {
		now := time.Now()
		genTokenFunc := paseto.DefaultGenTokenFunc()
		token, err := genTokenFunc(&paseto.StandardClaims{
			Issuer:    "cwg-issuer",
			ExpiredAt: now.Add(time.Hour),
			NotBefore: now,
			IssuedAt:  now,
		}, nil, nil)
		if err != nil {
			hlog.Error("generate token failed")
		}
		ctx.String(consts.StatusOK, token)
	})

	h.POST("/paseto", paseto.New(paseto.WithKeyLookUp("form:Authorization")), func(c context.Context, ctx *app.RequestContext) {
		ctx.String(consts.StatusOK, "token is valid")
	})

	go performRequest()

	h.Spin()
}
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/app/client"
    "github.com/cloudwego/hertz/pkg/app/server"
    "github.com/cloudwego/hertz/pkg/common/hlog"
    "github.com/cloudwego/hertz/pkg/protocol"
    "github.com/cloudwego/hertz/pkg/protocol/consts"
    "github.com/hertz-contrib/paseto"
)

func performRequest() {
    time.Sleep(time.Second)
    c, _ := client.NewClient()
    req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()
    req.SetRequestURI("http://127.0.0.1:8080/paseto")

    req.SetMethod("GET")
    _ = c.Do(context.Background(), req, resp)
    fmt.Printf("get token: %s\n", resp.Body())

    req.SetMethod("POST")
    req.SetHeader("Authorization", "Bearer "+string(resp.Body()))
    _ = c.Do(context.Background(), req, resp)
    fmt.Printf("Authorization response :%s", resp.Body())
}

func main() {
    h := server.New(server.WithHostPorts(":8080"))
    h.GET("/paseto", func(c context.Context, ctx *app.RequestContext) {
        now := time.Now()
        genTokenFunc := paseto.DefaultGenTokenFunc()
        token, err := genTokenFunc(&paseto.StandardClaims{
            Issuer:    "cwg-issuer",
            ExpiredAt: now.Add(time.Hour),
            NotBefore: now,
            IssuedAt:  now,
        }, nil, nil)
        if err != nil {
            hlog.Error("generate token failed")
        }
        ctx.String(consts.StatusOK, token)
    })

    h.POST("/paseto", paseto.New(paseto.WithTokenPrefix("Bearer ")), func(c context.Context, ctx *app.RequestContext) {
        ctx.String(consts.StatusOK, "token is valid")
    })

    go performRequest()

    h.Spin()
}

ParseFunc

WithParseFunc sets the ParseFunc.

ParseFunc parse and verify token.

Function Signature:

func WithParseFunc(f ParseFunc) Option

Sample code:

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/app/client"
    "github.com/cloudwego/hertz/pkg/app/server"
    "github.com/cloudwego/hertz/pkg/common/hlog"
    "github.com/cloudwego/hertz/pkg/protocol"
    "github.com/cloudwego/hertz/pkg/protocol/consts"
    "github.com/hertz-contrib/paseto"
)

func performRequest() {
    time.Sleep(time.Second)
    c, _ := client.NewClient()
    req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()

    req.SetMethod("GET")
    req.SetRequestURI("http://127.0.0.1:8080/paseto/correct-issuer")
    _ = c.Do(context.Background(), req, resp)

    req.SetMethod("POST")
    req.SetRequestURI("http://127.0.0.1:8080/paseto")
    req.SetHeader("Authorization", string(resp.Body()))
    _ = c.Do(context.Background(), req, resp)
    fmt.Printf("Authorization response:%s\n", resp.Body())

    req.SetMethod("GET")
    req.SetRequestURI("http://127.0.0.1:8080/paseto/wrong-issuer")
    _ = c.Do(context.Background(), req, resp)

    req.SetMethod("POST")
    req.SetRequestURI("http://127.0.0.1:8080/paseto")
    req.SetHeader("Authorization", string(resp.Body()))
    _ = c.Do(context.Background(), req, resp)
    fmt.Printf("Authorization response:%s,because issuer is wrong", resp.Body())
}

func main() {
    h := server.New(server.WithHostPorts(":8080"))

    h.GET("/paseto/correct-issuer", func(c context.Context, ctx *app.RequestContext) {
        now := time.Now()
        token, err := paseto.DefaultGenTokenFunc()(&paseto.StandardClaims{
            Issuer:    "CloudWeGo-issuer",
            ExpiredAt: now.Add(time.Hour),
            NotBefore: now,
            IssuedAt:  now,
        }, nil, nil)
        if err != nil {
            hlog.Error("generate token failed")
        }
        ctx.String(consts.StatusOK, token)
    })
    h.GET("/paseto/wrong-issuer", func(c context.Context, ctx *app.RequestContext) {
        now := time.Now()
        token, err := paseto.DefaultGenTokenFunc()(&paseto.StandardClaims{
            Issuer:    "CloudWeRun-issuer",
            ExpiredAt: now.Add(time.Hour),
            NotBefore: now,
            IssuedAt:  now,
        }, nil, nil)
        if err != nil {
            hlog.Error("generate token failed")
        }
        ctx.String(consts.StatusOK, token)
    })

    parseFunc, _ := paseto.NewV4PublicParseFunc(paseto.DefaultPublicKey, []byte(paseto.DefaultImplicit), paseto.WithIssuer("CloudWeGo-issuer"))
    h.POST("/paseto", paseto.New(paseto.WithParseFunc(parseFunc)), func(c context.Context, ctx *app.RequestContext) {
        ctx.String(consts.StatusOK, "token is valid")
    })
    go performRequest()
    h.Spin()
}

Version comparison

Version Local Public
v1 Encrypted with AES-256-CBC and signed with HMAC-SHA-256 Signed with RSA-SHA-256
v2 Encrypted with XSalsa20Poly1305 and signed with HMAC-SHA-384 Signed with EdDSA(Ed25519)
v3 Encrypted with XChaCha20Poly1305 and signed withHMAC-SHA-384 Signed with EdDSA(Ed25519)
v4 Encrypted with XChaCha20Poly1305 and signed with HMAC-SHA-512-256 Signed with EdDSA(Ed448)

Full Example

Refer to the paseto/example for full usage examples.


Last modified July 18, 2023 : doc: improve context doc (#722) (fca6c87)