claims.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package jwt
  2. import (
  3. "crypto/subtle"
  4. "fmt"
  5. "time"
  6. )
  7. // Claims must just have a Valid method that determines
  8. // if the token is invalid for any supported reason
  9. type Claims interface {
  10. Valid() error
  11. }
  12. // RegisteredClaims are a structured version of the JWT Claims Set,
  13. // restricted to Registered Claim Names, as referenced at
  14. // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
  15. //
  16. // This type can be used on its own, but then additional private and
  17. // public claims embedded in the JWT will not be parsed. The typical usecase
  18. // therefore is to embedded this in a user-defined claim type.
  19. //
  20. // See examples for how to use this with your own claim types.
  21. type RegisteredClaims struct {
  22. // the `iss` (Issuer) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
  23. Issuer string `json:"iss,omitempty"`
  24. // the `sub` (Subject) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2
  25. Subject string `json:"sub,omitempty"`
  26. // the `aud` (Audience) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3
  27. Audience ClaimStrings `json:"aud,omitempty"`
  28. // the `exp` (Expiration Time) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
  29. ExpiresAt *NumericDate `json:"exp,omitempty"`
  30. // the `nbf` (Not Before) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5
  31. NotBefore *NumericDate `json:"nbf,omitempty"`
  32. // the `iat` (Issued At) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6
  33. IssuedAt *NumericDate `json:"iat,omitempty"`
  34. // the `jti` (JWT ID) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7
  35. ID string `json:"jti,omitempty"`
  36. }
  37. // Valid validates time based claims "exp, iat, nbf".
  38. // There is no accounting for clock skew.
  39. // As well, if any of the above claims are not in the token, it will still
  40. // be considered a valid claim.
  41. func (c RegisteredClaims) Valid() error {
  42. vErr := new(ValidationError)
  43. now := TimeFunc()
  44. // The claims below are optional, by default, so if they are set to the
  45. // default value in Go, let's not fail the verification for them.
  46. if !c.VerifyExpiresAt(now, false) {
  47. delta := now.Sub(c.ExpiresAt.Time)
  48. vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
  49. vErr.Errors |= ValidationErrorExpired
  50. }
  51. if !c.VerifyIssuedAt(now, false) {
  52. vErr.Inner = ErrTokenUsedBeforeIssued
  53. vErr.Errors |= ValidationErrorIssuedAt
  54. }
  55. if !c.VerifyNotBefore(now, false) {
  56. vErr.Inner = ErrTokenNotValidYet
  57. vErr.Errors |= ValidationErrorNotValidYet
  58. }
  59. if vErr.valid() {
  60. return nil
  61. }
  62. return vErr
  63. }
  64. // VerifyAudience compares the aud claim against cmp.
  65. // If required is false, this method will return true if the value matches or is unset
  66. func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool {
  67. return verifyAud(c.Audience, cmp, req)
  68. }
  69. // VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
  70. // If req is false, it will return true, if exp is unset.
  71. func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool) bool {
  72. if c.ExpiresAt == nil {
  73. return verifyExp(nil, cmp, req)
  74. }
  75. return verifyExp(&c.ExpiresAt.Time, cmp, req)
  76. }
  77. // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
  78. // If req is false, it will return true, if iat is unset.
  79. func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool {
  80. if c.IssuedAt == nil {
  81. return verifyIat(nil, cmp, req)
  82. }
  83. return verifyIat(&c.IssuedAt.Time, cmp, req)
  84. }
  85. // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
  86. // If req is false, it will return true, if nbf is unset.
  87. func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool) bool {
  88. if c.NotBefore == nil {
  89. return verifyNbf(nil, cmp, req)
  90. }
  91. return verifyNbf(&c.NotBefore.Time, cmp, req)
  92. }
  93. // VerifyIssuer compares the iss claim against cmp.
  94. // If required is false, this method will return true if the value matches or is unset
  95. func (c *RegisteredClaims) VerifyIssuer(cmp string, req bool) bool {
  96. return verifyIss(c.Issuer, cmp, req)
  97. }
  98. // StandardClaims are a structured version of the JWT Claims Set, as referenced at
  99. // https://datatracker.ietf.org/doc/html/rfc7519#section-4. They do not follow the
  100. // specification exactly, since they were based on an earlier draft of the
  101. // specification and not updated. The main difference is that they only
  102. // support integer-based date fields and singular audiences. This might lead to
  103. // incompatibilities with other JWT implementations. The use of this is discouraged, instead
  104. // the newer RegisteredClaims struct should be used.
  105. //
  106. // Deprecated: Use RegisteredClaims instead for a forward-compatible way to access registered claims in a struct.
  107. type StandardClaims struct {
  108. Audience string `json:"aud,omitempty"`
  109. ExpiresAt int64 `json:"exp,omitempty"`
  110. Id string `json:"jti,omitempty"`
  111. IssuedAt int64 `json:"iat,omitempty"`
  112. Issuer string `json:"iss,omitempty"`
  113. NotBefore int64 `json:"nbf,omitempty"`
  114. Subject string `json:"sub,omitempty"`
  115. }
  116. // Valid validates time based claims "exp, iat, nbf". There is no accounting for clock skew.
  117. // As well, if any of the above claims are not in the token, it will still
  118. // be considered a valid claim.
  119. func (c StandardClaims) Valid() error {
  120. vErr := new(ValidationError)
  121. now := TimeFunc().Unix()
  122. // The claims below are optional, by default, so if they are set to the
  123. // default value in Go, let's not fail the verification for them.
  124. if !c.VerifyExpiresAt(now, false) {
  125. delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
  126. vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
  127. vErr.Errors |= ValidationErrorExpired
  128. }
  129. if !c.VerifyIssuedAt(now, false) {
  130. vErr.Inner = ErrTokenUsedBeforeIssued
  131. vErr.Errors |= ValidationErrorIssuedAt
  132. }
  133. if !c.VerifyNotBefore(now, false) {
  134. vErr.Inner = ErrTokenNotValidYet
  135. vErr.Errors |= ValidationErrorNotValidYet
  136. }
  137. if vErr.valid() {
  138. return nil
  139. }
  140. return vErr
  141. }
  142. // VerifyAudience compares the aud claim against cmp.
  143. // If required is false, this method will return true if the value matches or is unset
  144. func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
  145. return verifyAud([]string{c.Audience}, cmp, req)
  146. }
  147. // VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
  148. // If req is false, it will return true, if exp is unset.
  149. func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
  150. if c.ExpiresAt == 0 {
  151. return verifyExp(nil, time.Unix(cmp, 0), req)
  152. }
  153. t := time.Unix(c.ExpiresAt, 0)
  154. return verifyExp(&t, time.Unix(cmp, 0), req)
  155. }
  156. // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
  157. // If req is false, it will return true, if iat is unset.
  158. func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
  159. if c.IssuedAt == 0 {
  160. return verifyIat(nil, time.Unix(cmp, 0), req)
  161. }
  162. t := time.Unix(c.IssuedAt, 0)
  163. return verifyIat(&t, time.Unix(cmp, 0), req)
  164. }
  165. // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
  166. // If req is false, it will return true, if nbf is unset.
  167. func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
  168. if c.NotBefore == 0 {
  169. return verifyNbf(nil, time.Unix(cmp, 0), req)
  170. }
  171. t := time.Unix(c.NotBefore, 0)
  172. return verifyNbf(&t, time.Unix(cmp, 0), req)
  173. }
  174. // VerifyIssuer compares the iss claim against cmp.
  175. // If required is false, this method will return true if the value matches or is unset
  176. func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
  177. return verifyIss(c.Issuer, cmp, req)
  178. }
  179. // ----- helpers
  180. func verifyAud(aud []string, cmp string, required bool) bool {
  181. if len(aud) == 0 {
  182. return !required
  183. }
  184. // use a var here to keep constant time compare when looping over a number of claims
  185. result := false
  186. var stringClaims string
  187. for _, a := range aud {
  188. if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 {
  189. result = true
  190. }
  191. stringClaims = stringClaims + a
  192. }
  193. // case where "" is sent in one or many aud claims
  194. if len(stringClaims) == 0 {
  195. return !required
  196. }
  197. return result
  198. }
  199. func verifyExp(exp *time.Time, now time.Time, required bool) bool {
  200. if exp == nil {
  201. return !required
  202. }
  203. return now.Before(*exp)
  204. }
  205. func verifyIat(iat *time.Time, now time.Time, required bool) bool {
  206. if iat == nil {
  207. return !required
  208. }
  209. return now.After(*iat) || now.Equal(*iat)
  210. }
  211. func verifyNbf(nbf *time.Time, now time.Time, required bool) bool {
  212. if nbf == nil {
  213. return !required
  214. }
  215. return now.After(*nbf) || now.Equal(*nbf)
  216. }
  217. func verifyIss(iss string, cmp string, required bool) bool {
  218. if iss == "" {
  219. return !required
  220. }
  221. return subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0
  222. }