123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- package jwt
- import (
- "errors"
- "fmt"
- "strings"
- "time"
- "github.com/kataras/iris/v12"
- "github.com/kataras/iris/v12/context"
- "github.com/golang-jwt/jwt/v4"
- )
- func init() {
- context.SetHandlerName("github.com/iris-contrib/middleware/jwt.*", "iris-contrib.jwt")
- }
- type (
- // Token for JWT. Different fields will be used depending on whether you're
- // creating or parsing/verifying a token.
- //
- // A type alias for jwt.Token.
- Token = jwt.Token
- // MapClaims type that uses the map[string]interface{} for JSON decoding
- // This is the default claims type if you don't supply one
- //
- // A type alias for jwt.MapClaims.
- MapClaims = jwt.MapClaims
- // Claims must just have a Valid method that determines
- // if the token is invalid for any supported reason.
- //
- // A type alias for jwt.Claims.
- Claims = jwt.Claims
- )
- // Shortcuts to create a new Token.
- var (
- NewToken = jwt.New
- NewTokenWithClaims = jwt.NewWithClaims
- )
- // HS256 and company.
- var (
- SigningMethodHS256 = jwt.SigningMethodHS256
- SigningMethodHS384 = jwt.SigningMethodHS384
- SigningMethodHS512 = jwt.SigningMethodHS512
- )
- // ECDSA - EC256 and company.
- var (
- SigningMethodES256 = jwt.SigningMethodES256
- SigningMethodES384 = jwt.SigningMethodES384
- SigningMethodES512 = jwt.SigningMethodES512
- )
- // A function called whenever an error is encountered
- type errorHandler func(iris.Context, error)
- // TokenExtractor is a function that takes a context as input and returns
- // either a token or an error. An error should only be returned if an attempt
- // to specify a token was found, but the information was somehow incorrectly
- // formed. In the case where a token is simply not present, this should not
- // be treated as an error. An empty string should be returned in that case.
- type TokenExtractor func(iris.Context) (string, error)
- // Middleware the middleware for JSON Web tokens authentication method
- type Middleware struct {
- Config Config
- }
- // OnError is the default error handler.
- // Use it to change the behavior for each error.
- // See `Config.ErrorHandler`.
- func OnError(ctx iris.Context, err error) {
- if err == nil {
- return
- }
- ctx.StopExecution()
- ctx.StatusCode(iris.StatusUnauthorized)
- ctx.WriteString(err.Error())
- }
- // New constructs a new Secure instance with supplied options.
- func New(cfg ...Config) *Middleware {
- var c Config
- if len(cfg) == 0 {
- c = Config{}
- } else {
- c = cfg[0]
- }
- if c.ContextKey == "" {
- c.ContextKey = DefaultContextKey
- }
- if c.ErrorHandler == nil {
- c.ErrorHandler = OnError
- }
- if c.Extractor == nil {
- c.Extractor = FromAuthHeader
- }
- return &Middleware{Config: c}
- }
- func logf(ctx iris.Context, format string, args ...interface{}) {
- ctx.Application().Logger().Debugf(format, args...)
- }
- // Get returns the user (&token) information for this client/request
- func (m *Middleware) Get(ctx iris.Context) *jwt.Token {
- v := ctx.Values().Get(m.Config.ContextKey)
- if v == nil {
- return nil
- }
- return v.(*jwt.Token)
- }
- // Serve the middleware's action
- func (m *Middleware) Serve(ctx iris.Context) {
- if err := m.CheckJWT(ctx); err != nil {
- m.Config.ErrorHandler(ctx, err)
- return
- }
- // If everything ok then call next.
- ctx.Next()
- }
- // FromAuthHeader is a "TokenExtractor" that takes a give context and extracts
- // the JWT token from the Authorization header.
- func FromAuthHeader(ctx iris.Context) (string, error) {
- authHeader := ctx.GetHeader("Authorization")
- if authHeader == "" {
- return "", nil // No error, just no token
- }
- // TODO: Make this a bit more robust, parsing-wise
- authHeaderParts := strings.Split(authHeader, " ")
- if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
- return "", fmt.Errorf("authorization header format must be Bearer {token}")
- }
- return authHeaderParts[1], nil
- }
- // FromParameter returns a function that extracts the token from the specified
- // query string parameter
- func FromParameter(param string) TokenExtractor {
- return func(ctx iris.Context) (string, error) {
- return ctx.URLParam(param), nil
- }
- }
- // FromFirst returns a function that runs multiple token extractors and takes the
- // first token it finds
- func FromFirst(extractors ...TokenExtractor) TokenExtractor {
- return func(ctx iris.Context) (string, error) {
- for _, ex := range extractors {
- token, err := ex(ctx)
- if err != nil {
- return "", err
- }
- if token != "" {
- return token, nil
- }
- }
- return "", nil
- }
- }
- var (
- // ErrTokenMissing is the error value that it's returned when
- // a token is not found based on the token extractor.
- ErrTokenMissing = errors.New("required authorization token not found")
- // ErrTokenInvalid is the error value that it's returned when
- // a token is not valid.
- ErrTokenInvalid = errors.New("token is invalid")
- // ErrTokenExpired is the error value that it's returned when
- // a token value is found and it's valid but it's expired.
- ErrTokenExpired = errors.New("token is expired")
- )
- var jwtParser = new(jwt.Parser)
- // CheckJWT the main functionality, checks for token
- func (m *Middleware) CheckJWT(ctx iris.Context) error {
- if !m.Config.EnableAuthOnOptions {
- if ctx.Method() == iris.MethodOptions {
- return nil
- }
- }
- // Use the specified token extractor to extract a token from the request
- token, err := m.Config.Extractor(ctx)
- // If debugging is turned on, log the outcome
- if err != nil {
- logf(ctx, "Error extracting JWT: %v", err)
- return err
- }
- logf(ctx, "Token extracted: %s", token)
- // If the token is empty...
- if token == "" {
- // Check if it was required
- if m.Config.CredentialsOptional {
- logf(ctx, "No credentials found (CredentialsOptional=true)")
- // No error, just no token (and that is ok given that CredentialsOptional is true)
- return nil
- }
- // If we get here, the required token is missing
- logf(ctx, "Error: No credentials found (CredentialsOptional=false)")
- return ErrTokenMissing
- }
- // Now parse the token
- parsedToken, err := jwtParser.Parse(token, m.Config.ValidationKeyGetter)
- // Check if there was an error in parsing...
- if err != nil {
- logf(ctx, "Error parsing token: %v", err)
- return err
- }
- if m.Config.SigningMethod != nil && m.Config.SigningMethod.Alg() != parsedToken.Header["alg"] {
- err := fmt.Errorf("expected %s signing method but token specified %s",
- m.Config.SigningMethod.Alg(),
- parsedToken.Header["alg"])
- logf(ctx, "Error validating token algorithm: %v", err)
- return err
- }
- // Check if the parsed token is valid...
- if !parsedToken.Valid {
- logf(ctx, "Token is invalid")
- // m.Config.ErrorHandler(ctx, ErrTokenInvalid)
- return ErrTokenInvalid
- }
- if m.Config.Expiration {
- if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok {
- if expired := claims.VerifyExpiresAt(time.Now().Unix(), true); !expired {
- logf(ctx, "Token is expired")
- return ErrTokenExpired
- }
- }
- }
- logf(ctx, "JWT: %v", parsedToken)
- // If we get here, everything worked and we can set the
- // user property in context.
- ctx.Values().Set(m.Config.ContextKey, parsedToken)
- return nil
- }
|