jwt.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. package jwt
  2. import (
  3. "errors"
  4. "fmt"
  5. "log"
  6. "strings"
  7. "github.com/dgrijalva/jwt-go"
  8. "github.com/kataras/iris"
  9. "github.com/kataras/iris/context"
  10. "time"
  11. )
  12. // iris provides some basic middleware, most for your learning courve.
  13. // You can use any net/http compatible middleware with iris.FromStd wrapper.
  14. //
  15. // JWT net/http video tutorial for golang newcomers: https://www.youtube.com/watch?v=dgJFeqeXVKw
  16. //
  17. // Unlike the other middleware, this middleware was cloned from external source: https://github.com/auth0/go-jwt-middleware
  18. // (because it used "context" to define the user but we don't need that so a simple iris.FromStd wouldn't work as expected.)
  19. // jwt_test.go also didn't created by me:
  20. // 28 Jul 2016
  21. // @heralight heralight add jwt unit test.
  22. //
  23. // So if this doesn't works for you just try other net/http compatible middleware and bind it via `iris.FromStd(myHandlerWithNext)`,
  24. // It's here for your learning curve.
  25. // A function called whenever an error is encountered
  26. type errorHandler func(context.Context, string)
  27. // TokenExtractor is a function that takes a context as input and returns
  28. // either a token or an error. An error should only be returned if an attempt
  29. // to specify a token was found, but the information was somehow incorrectly
  30. // formed. In the case where a token is simply not present, this should not
  31. // be treated as an error. An empty string should be returned in that case.
  32. type TokenExtractor func(context.Context) (string, error)
  33. // Middleware the middleware for JSON Web tokens authentication method
  34. type Middleware struct {
  35. Config Config
  36. }
  37. // OnError default error handler
  38. func OnError(ctx context.Context, err string) {
  39. ctx.StatusCode(iris.StatusUnauthorized)
  40. ctx.Writef(err)
  41. }
  42. // New constructs a new Secure instance with supplied options.
  43. func New(cfg ...Config) *Middleware {
  44. var c Config
  45. if len(cfg) == 0 {
  46. c = Config{}
  47. } else {
  48. c = cfg[0]
  49. }
  50. if c.ContextKey == "" {
  51. c.ContextKey = DefaultContextKey
  52. }
  53. if c.ErrorHandler == nil {
  54. c.ErrorHandler = OnError
  55. }
  56. if c.Extractor == nil {
  57. c.Extractor = FromAuthHeader
  58. }
  59. return &Middleware{Config: c}
  60. }
  61. func (m *Middleware) logf(format string, args ...interface{}) {
  62. if m.Config.Debug {
  63. log.Printf(format, args...)
  64. }
  65. }
  66. // Get returns the user (&token) information for this client/request
  67. func (m *Middleware) Get(ctx context.Context) *jwt.Token {
  68. return ctx.Values().Get(m.Config.ContextKey).(*jwt.Token)
  69. }
  70. // Serve the middleware's action
  71. func (m *Middleware) Serve(ctx context.Context) {
  72. if err := m.CheckJWT(ctx); err != nil {
  73. ctx.StopExecution()
  74. return
  75. }
  76. // If everything ok then call next.
  77. ctx.Next()
  78. }
  79. // FromAuthHeader is a "TokenExtractor" that takes a give context and extracts
  80. // the JWT token from the Authorization header.
  81. func FromAuthHeader(ctx context.Context) (string, error) {
  82. authHeader := ctx.GetHeader("Authorization")
  83. if authHeader == "" {
  84. return "", nil // No error, just no token
  85. }
  86. // TODO: Make this a bit more robust, parsing-wise
  87. authHeaderParts := strings.Split(authHeader, " ")
  88. if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
  89. return "", fmt.Errorf("Authorization header format must be Bearer {token}")
  90. }
  91. return authHeaderParts[1], nil
  92. }
  93. // FromParameter returns a function that extracts the token from the specified
  94. // query string parameter
  95. func FromParameter(param string) TokenExtractor {
  96. return func(ctx context.Context) (string, error) {
  97. return ctx.URLParam(param), nil
  98. }
  99. }
  100. // FromFirst returns a function that runs multiple token extractors and takes the
  101. // first token it finds
  102. func FromFirst(extractors ...TokenExtractor) TokenExtractor {
  103. return func(ctx context.Context) (string, error) {
  104. for _, ex := range extractors {
  105. token, err := ex(ctx)
  106. if err != nil {
  107. return "", err
  108. }
  109. if token != "" {
  110. return token, nil
  111. }
  112. }
  113. return "", nil
  114. }
  115. }
  116. // CheckJWT the main functionality, checks for token
  117. func (m *Middleware) CheckJWT(ctx context.Context) error {
  118. if !m.Config.EnableAuthOnOptions {
  119. if ctx.Method() == iris.MethodOptions {
  120. return nil
  121. }
  122. }
  123. // Use the specified token extractor to extract a token from the request
  124. token, err := m.Config.Extractor(ctx)
  125. // If debugging is turned on, log the outcome
  126. if err != nil {
  127. m.logf("Error extracting JWT: %v", err)
  128. } else {
  129. m.logf("Token extracted: %s", token)
  130. }
  131. // If an error occurs, call the error handler and return an error
  132. if err != nil {
  133. m.Config.ErrorHandler(ctx, err.Error())
  134. return fmt.Errorf("Error extracting token: %v", err)
  135. }
  136. // If the token is empty...
  137. if token == "" {
  138. // Check if it was required
  139. if m.Config.CredentialsOptional {
  140. m.logf(" No credentials found (CredentialsOptional=true)")
  141. // No error, just no token (and that is ok given that CredentialsOptional is true)
  142. return nil
  143. }
  144. // If we get here, the required token is missing
  145. errorMsg := "Required authorization token not found"
  146. m.Config.ErrorHandler(ctx, errorMsg)
  147. m.logf(" Error: No credentials found (CredentialsOptional=false)")
  148. return fmt.Errorf(errorMsg)
  149. }
  150. // Now parse the token
  151. parsedToken, err := jwt.Parse(token, m.Config.ValidationKeyGetter)
  152. // Check if there was an error in parsing...
  153. if err != nil {
  154. m.logf("Error parsing token: %v", err)
  155. m.Config.ErrorHandler(ctx, err.Error())
  156. return fmt.Errorf("Error parsing token: %v", err)
  157. }
  158. if m.Config.SigningMethod != nil && m.Config.SigningMethod.Alg() != parsedToken.Header["alg"] {
  159. message := fmt.Sprintf("Expected %s signing method but token specified %s",
  160. m.Config.SigningMethod.Alg(),
  161. parsedToken.Header["alg"])
  162. m.logf("Error validating token algorithm: %s", message)
  163. m.Config.ErrorHandler(ctx, errors.New(message).Error())
  164. return fmt.Errorf("Error validating token algorithm: %s", message)
  165. }
  166. // Check if the parsed token is valid...
  167. if !parsedToken.Valid {
  168. m.logf("Token is invalid")
  169. m.Config.ErrorHandler(ctx, "The token isn't valid")
  170. return fmt.Errorf("Token is invalid")
  171. }
  172. if m.Config.Expiration {
  173. if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok {
  174. if expired := claims.VerifyExpiresAt(time.Now().Unix(), true); !expired {
  175. return fmt.Errorf("Token is expired")
  176. }
  177. }
  178. }
  179. m.logf("JWT: %v", parsedToken)
  180. // If we get here, everything worked and we can set the
  181. // user property in context.
  182. ctx.Values().Set(m.Config.ContextKey, parsedToken)
  183. return nil
  184. }