middleware

package module
v1.2.3 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2023 License: MIT Imports: 9 Imported by: 0

README

PASETO Token Middleware

Middleware designed for use within gin-gonic/gin microservices to validate PASETO Bearer tokens.

Usage

package main

import (
  middleware "github.com/clearchanneloutdoor/token-middleware"
  "github.com/gin-gonic/gin"
)

func main() {
  ge := gin.Default()

  // setup a v2 private key
  symmetricKey := "brozeph, where is my secret key?" // 32 bytes
  
  // setup a v2 public key
  b, _ := hex.DecodeString("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")
  publicKey := ed25519.PublicKey(b)

  // create a token handler for v2 private and public paseto tokens
  th := middleware.NewTokenHandler([]byte(symmetricKey), publicKey)

  // validate with a specified claim
  ge.GET("/foos", th.ScopeAuthorization("cool-scope", "another-scope", "yet-another-scope"), func (ctx *gin.Context) {
    // middleware will check the token to see if there is a scope that matches any of the
    // allowed scopes supplied to the ScopeAuthorization function
    ctx.IndentedJSON(http.StatusOK, gin.H{
      "foos": "yeah!"
    })
  })

  // validate for non-expired PASETO only (no claims required)
  ge.GET("/bars", th.ValidToken(), func (ctx *gin.Context) {
    // middleware will check to see if the PASETO token has not expired, but 
    // will not perform any other validations
    ctx.IndentedJSON(http.StatusOK, gin.H{
      "bars": "woot!"
    })
  })

  ge.Run(":8080")
}
TokenHandler

The token handler middleware can be initialized with public and/or symmetric keys in order to validate and extract JWT details from PASETO tokens that are provided as Bearer tokens on API requests.

If the API only requires handling of v2 PASETO local tokens, the token handler can be initialized with a single 32 byte symmetric key (a string or []byte value). If v2 public, v2 local and v1 public keys all require validation, then the token handler can be created with an Ed25519 public key, a 32 byte symmetric key (string or []byte) and an RSA public key.

For example, the following creates a v2 local PASETO token parser

symmetricKey := "brozeph, where is my secret key?" // 32 bytes
handler := middleware.NewTokenHandler(symmetricKey)

The following is a reference to the PASETO specification protocol versions and encryption / signing details:

version local public
v1 AES-256-CTR + HMAC-SHA384 2048-bit RSA public key
v2 XChaCha20-Poly1305 Ed25519

The PASETO bearer tokens are expected to be provided via the inbound request in the Authorization header:

GET /v1/things/1234 HTTP/1.1
Authorization: Bearer v2.local.HkM8o... // <- PASETO token
Middleware

Once the TokenHandler has been created, it can be used to register middleware on a per API endpoint or resource basis.

Take, for example, the API resource below... ``GETrequests to/v1/thingsis being handled by a function calledGetThings`.

func RegisterAPIResources(c *gin.Engine, bs interfaces.IThingsController) {
  c.GET("/v1/things", bs.GetThings)
}

In order to secure the GET request to /v1/things, a TokenHandler, like the one created earlier, can perform the authorization in middleware with a minor tweak to the above method:

func RegisterAPIResources(c *gin.Engine, th *middleware.TokenHandler, bs interfaces.IThingsController) {
  c.GET("/v1/things", th.ScopeAuthorization("things-read"), bs.GetThings)
}

The middleware now looks for a valid (non-expired, properly formatted and parseable) PASETO token containing the scope things-read. In the event there is a problem, the middleware will register the error in the *gin.Context and abort the request. Otherwise, the request will succeed and the GetThings method will be called to handle the request.

Errors

The middleware will register errors of type AuthError. The AuthError contains 3 fields:

  • Message - a string message specific to the error
  • Status - an Int representing the HTTP status for unauthorized (401)
  • Title - a string value for the type of error, "Unauthorized"

The following is an example middleware error reporter that can be used to properly output the error and status to the request when there is an authorization problem.

func errorReporter(ctx *gin.Context) {
  ctx.Next()
  detectedErrs := ctx.Errors.ByType(gin.ErrorTypeAny)

  if len(detectedErrs) == 0 {
    return
  }

  // handle the last error
  err := detectedErrs.Last().Err
  switch err := err.(type) {
  case *middleware.AuthError:
    ctx.IndentedJSON(err.Status, err)
    ctx.Abort()
  case *myerrors.MyError:
    // handle custom errors
  default:
    // do some other processing here...
  }
}
JSON Token

The data stored in the PASETO is a JSON Token. This token contains scopes and claims that may be useful for subsequent API route handling, so the unencrypted JSON Token data is added to the *gin.Context per request.

func (c APIController) GetThings(ctx *gin.Context) {
  // the middleware has already authorized this request...
  // retrieve the token...
  jwt := ctx.Get("jwt")(paseto.JSONToken)

  // pull a custom claim from the token 
  // if it is expected to be there...
  thingOwner, err := jwt.Get("thingOwner")
  if err != nil {
    ctx.Error(errors.New("where's mah claim?!?!"))
    return
  }

  // do some stuff with the thingOwner...
}
PASETO

The PASETO value itself is also available for use and is added to the *gin.Context per request.

func (c APIController) GetThings(ctx *gin.Context) {
  // the middleware has already authorized this request...
  // retrieve the PASETO...
  pst := ctx.Get("pst")(string)

  // do some stuff with the PASETO...
}

Documentation

Overview

Package middleware provides handlers for PASETO token verification and authorization for gin-gonic/gin API servers

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthError

type AuthError struct {
	Message string `json:"message,omitempty"`
	Status  int    `json:"status"`
	Title   string `json:"title"`
}

AuthError contains details regarding any authorization errors encountered while attempting to read or parse a PASETO token

func AuthorizationMissingError

func AuthorizationMissingError() *AuthError

AuthorizationMissingError is for when the Authorization header is not present in the request

func BearTokenError

func BearTokenError() *AuthError

BearerTokenError is for when the Authorization header is not properly formatted or does not contain an actual Bearer value

func NotAuthorizedError

func NotAuthorizedError(scopes []string) *AuthError

NotAuthorizedError is for when the provided PASETO token is not allowed access due to a scope validation failure

func TokenParseError

func TokenParseError(m string) *AuthError

TokenParseError is for when the PASETO token can't be parsed properly

func TokenValidationError

func TokenValidationError(m string) *AuthError

TokenValidationError occurs when the PASETO token is expired or invalid and no scopes are being checked

func (AuthError) Error

func (err AuthError) Error() string

Error provides details about the error

type TokenHandler

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

TokenHandler provides middleware for validating and reading the details from a PASETO token provided as a bearer token on the request

func NewTokenHandler

func NewTokenHandler(keys ...interface{}) TokenHandler

NewTokenHandler creates a new middleware handler with the specified keys for properly verifying or decrypting v1 and v2 PASETO tokens provided as bearer tokens within the Authorization header of a request

func (TokenHandler) ScopeAuthorization

func (th TokenHandler) ScopeAuthorization(allowedScopes ...string) gin.HandlerFunc

ScopeAuthorization provides gin middleware to validate both the scope is as expected and that the token has not expired

func (TokenHandler) ValidToken

func (th TokenHandler) ValidToken() gin.HandlerFunc

ValidToken provides gin middleware that ensures a bearer token in the request has valid and has not expired.

Jump to

Keyboard shortcuts

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