github.com/jacobsoderblom/buffalo@v0.11.0/middleware/tokenauth/tokenauth.go (about)

     1  // Package tokenauth provides jwt token authorisation middleware
     2  // supports HMAC, RSA, ECDSA, RSAPSS algorithms
     3  // uses github.com/dgrijalva/jwt-go for jwt implementation
     4  package tokenauth
     5  
     6  import (
     7  	"io/ioutil"
     8  	"log"
     9  	"net/http"
    10  	"strings"
    11  
    12  	"github.com/dgrijalva/jwt-go"
    13  	"github.com/gobuffalo/envy"
    14  
    15  	"github.com/gobuffalo/buffalo"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  var (
    20  	// ErrTokenInvalid is returned when the token provided is invalid
    21  	ErrTokenInvalid = errors.New("token invalid")
    22  	// ErrNoToken is returned if no token is supplied in the request.
    23  	ErrNoToken = errors.New("token not found in request")
    24  	// ErrBadSigningMethod is returned if the token sign method in the request
    25  	// does not match the signing method used
    26  	ErrBadSigningMethod = errors.New("unexpected signing method")
    27  )
    28  
    29  // Options for the JWT middleware
    30  type Options struct {
    31  	SignMethod jwt.SigningMethod
    32  	GetKey     func(jwt.SigningMethod) (interface{}, error)
    33  }
    34  
    35  // New enables jwt token verification if no Sign method is provided,
    36  // by default uses HMAC
    37  func New(options Options) buffalo.MiddlewareFunc {
    38  	// set sign method to HMAC if not provided
    39  	if options.SignMethod == nil {
    40  		options.SignMethod = jwt.SigningMethodHS256
    41  	}
    42  	if options.GetKey == nil {
    43  		options.GetKey = selectGetKeyFunc(options.SignMethod)
    44  	}
    45  	// get key for validation
    46  	key, err := options.GetKey(options.SignMethod)
    47  	// if error on getting key exit.
    48  	if err != nil {
    49  		log.Fatal(errors.Wrap(err, "couldn't get key"))
    50  	}
    51  	return func(next buffalo.Handler) buffalo.Handler {
    52  		return func(c buffalo.Context) error {
    53  			// get Authorisation header value
    54  			authString := c.Request().Header.Get("Authorization")
    55  
    56  			tokenString, err := getJwtToken(authString)
    57  			// if error on getting the token, return with status unauthorized
    58  			if err != nil {
    59  				return c.Error(http.StatusUnauthorized, err)
    60  			}
    61  
    62  			// validating and parsing the tokenString
    63  			token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    64  				// Validating if algorithm used for signing is same as the algorithm in token
    65  				if token.Method.Alg() != options.SignMethod.Alg() {
    66  					return nil, ErrBadSigningMethod
    67  				}
    68  				return key, nil
    69  			})
    70  			// if error validating jwt token, return with status unauthorized
    71  			if err != nil {
    72  				return c.Error(http.StatusUnauthorized, err)
    73  			}
    74  
    75  			// set the claims as context parameter.
    76  			// so that the actions can use the claims from jwt token
    77  			c.Set("claims", token.Claims)
    78  			// calling next handler
    79  			err = next(c)
    80  
    81  			return err
    82  		}
    83  	}
    84  }
    85  
    86  // selectGetKeyFunc is an helper function to choose the GetKey function
    87  // according to the Signing method used
    88  func selectGetKeyFunc(method jwt.SigningMethod) func(jwt.SigningMethod) (interface{}, error) {
    89  	switch method.(type) {
    90  	case *jwt.SigningMethodRSA:
    91  		return GetKeyRSA
    92  	case *jwt.SigningMethodECDSA:
    93  		return GetKeyECDSA
    94  	case *jwt.SigningMethodRSAPSS:
    95  		return GetKeyRSAPSS
    96  	default:
    97  		return GetHMACKey
    98  	}
    99  }
   100  
   101  // GetHMACKey gets secret key from env
   102  func GetHMACKey(jwt.SigningMethod) (interface{}, error) {
   103  	key, err := envy.MustGet("JWT_SECRET")
   104  	return []byte(key), err
   105  }
   106  
   107  // GetKeyRSA gets the public key file location from env and returns rsa.PublicKey
   108  func GetKeyRSA(jwt.SigningMethod) (interface{}, error) {
   109  	key, err := envy.MustGet("JWT_PUBLIC_KEY")
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	keyData, err := ioutil.ReadFile(key)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	return jwt.ParseRSAPublicKeyFromPEM(keyData)
   118  }
   119  
   120  // GetKeyRSAPSS uses GetKeyRSA() since both requires rsa.PublicKey
   121  func GetKeyRSAPSS(signingMethod jwt.SigningMethod) (interface{}, error) {
   122  	return GetKeyRSA(signingMethod)
   123  }
   124  
   125  // GetKeyECDSA gets the public.pem file location from env and returns ecdsa.PublicKey
   126  func GetKeyECDSA(jwt.SigningMethod) (interface{}, error) {
   127  	key, err := envy.MustGet("JWT_PUBLIC_KEY")
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	keyData, err := ioutil.ReadFile(key)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	return jwt.ParseECPublicKeyFromPEM(keyData)
   136  }
   137  
   138  // getJwtToken gets the token from the Authorisation header
   139  // removes the Bearer part from the authorisation header value.
   140  // returns No token error if Token is not found
   141  // returns Token Invalid error if the token value cannot be obtained by removing `Bearer `
   142  func getJwtToken(authString string) (string, error) {
   143  	if authString == "" {
   144  		return "", ErrNoToken
   145  	}
   146  	splitToken := strings.Split(authString, "Bearer ")
   147  	if len(splitToken) != 2 {
   148  		return "", ErrTokenInvalid
   149  	}
   150  	tokenString := splitToken[1]
   151  	return tokenString, nil
   152  }