verify.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. package oidc
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/base64"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "strings"
  10. "time"
  11. "golang.org/x/oauth2"
  12. jose "gopkg.in/square/go-jose.v2"
  13. )
  14. const (
  15. issuerGoogleAccounts = "https://accounts.google.com"
  16. issuerGoogleAccountsNoScheme = "accounts.google.com"
  17. )
  18. // KeySet is a set of publc JSON Web Keys that can be used to validate the signature
  19. // of JSON web tokens. This is expected to be backed by a remote key set through
  20. // provider metadata discovery or an in-memory set of keys delivered out-of-band.
  21. type KeySet interface {
  22. // VerifySignature parses the JSON web token, verifies the signature, and returns
  23. // the raw payload. Header and claim fields are validated by other parts of the
  24. // package. For example, the KeySet does not need to check values such as signature
  25. // algorithm, issuer, and audience since the IDTokenVerifier validates these values
  26. // independently.
  27. //
  28. // If VerifySignature makes HTTP requests to verify the token, it's expected to
  29. // use any HTTP client associated with the context through ClientContext.
  30. VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)
  31. }
  32. // IDTokenVerifier provides verification for ID Tokens.
  33. type IDTokenVerifier struct {
  34. keySet KeySet
  35. config *Config
  36. issuer string
  37. }
  38. // NewVerifier returns a verifier manually constructed from a key set and issuer URL.
  39. //
  40. // It's easier to use provider discovery to construct an IDTokenVerifier than creating
  41. // one directly. This method is intended to be used with provider that don't support
  42. // metadata discovery, or avoiding round trips when the key set URL is already known.
  43. //
  44. // This constructor can be used to create a verifier directly using the issuer URL and
  45. // JSON Web Key Set URL without using discovery:
  46. //
  47. // keySet := oidc.NewRemoteKeySet(ctx, "https://www.googleapis.com/oauth2/v3/certs")
  48. // verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
  49. //
  50. // Since KeySet is an interface, this constructor can also be used to supply custom
  51. // public key sources. For example, if a user wanted to supply public keys out-of-band
  52. // and hold them statically in-memory:
  53. //
  54. // // Custom KeySet implementation.
  55. // keySet := newStatisKeySet(publicKeys...)
  56. //
  57. // // Verifier uses the custom KeySet implementation.
  58. // verifier := oidc.NewVerifier("https://auth.example.com", keySet, config)
  59. //
  60. func NewVerifier(issuerURL string, keySet KeySet, config *Config) *IDTokenVerifier {
  61. return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL}
  62. }
  63. // Config is the configuration for an IDTokenVerifier.
  64. type Config struct {
  65. // Expected audience of the token. For a majority of the cases this is expected to be
  66. // the ID of the client that initialized the login flow. It may occasionally differ if
  67. // the provider supports the authorizing party (azp) claim.
  68. //
  69. // If not provided, users must explicitly set SkipClientIDCheck.
  70. ClientID string
  71. // If specified, only this set of algorithms may be used to sign the JWT.
  72. //
  73. // Since many providers only support RS256, SupportedSigningAlgs defaults to this value.
  74. SupportedSigningAlgs []string
  75. // If true, no ClientID check performed. Must be true if ClientID field is empty.
  76. SkipClientIDCheck bool
  77. // If true, token expiry is not checked.
  78. SkipExpiryCheck bool
  79. // Time function to check Token expiry. Defaults to time.Now
  80. Now func() time.Time
  81. }
  82. // Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
  83. //
  84. // The returned IDTokenVerifier is tied to the Provider's context and its behavior is
  85. // undefined once the Provider's context is canceled.
  86. func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
  87. return NewVerifier(p.issuer, p.remoteKeySet, config)
  88. }
  89. func parseJWT(p string) ([]byte, error) {
  90. parts := strings.Split(p, ".")
  91. if len(parts) < 2 {
  92. return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
  93. }
  94. payload, err := base64.RawURLEncoding.DecodeString(parts[1])
  95. if err != nil {
  96. return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
  97. }
  98. return payload, nil
  99. }
  100. func contains(sli []string, ele string) bool {
  101. for _, s := range sli {
  102. if s == ele {
  103. return true
  104. }
  105. }
  106. return false
  107. }
  108. // Verify parses a raw ID Token, verifies it's been signed by the provider, preforms
  109. // any additional checks depending on the Config, and returns the payload.
  110. //
  111. // Verify does NOT do nonce validation, which is the callers responsibility.
  112. //
  113. // See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
  114. //
  115. // oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
  116. // if err != nil {
  117. // // handle error
  118. // }
  119. //
  120. // // Extract the ID Token from oauth2 token.
  121. // rawIDToken, ok := oauth2Token.Extra("id_token").(string)
  122. // if !ok {
  123. // // handle error
  124. // }
  125. //
  126. // token, err := verifier.Verify(ctx, rawIDToken)
  127. //
  128. func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
  129. jws, err := jose.ParseSigned(rawIDToken)
  130. if err != nil {
  131. return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
  132. }
  133. // Throw out tokens with invalid claims before trying to verify the token. This lets
  134. // us do cheap checks before possibly re-syncing keys.
  135. payload, err := parseJWT(rawIDToken)
  136. if err != nil {
  137. return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
  138. }
  139. var token idToken
  140. if err := json.Unmarshal(payload, &token); err != nil {
  141. return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
  142. }
  143. t := &IDToken{
  144. Issuer: token.Issuer,
  145. Subject: token.Subject,
  146. Audience: []string(token.Audience),
  147. Expiry: time.Time(token.Expiry),
  148. IssuedAt: time.Time(token.IssuedAt),
  149. Nonce: token.Nonce,
  150. AccessTokenHash: token.AtHash,
  151. claims: payload,
  152. }
  153. // Check issuer.
  154. if t.Issuer != v.issuer {
  155. // Google sometimes returns "accounts.google.com" as the issuer claim instead of
  156. // the required "https://accounts.google.com". Detect this case and allow it only
  157. // for Google.
  158. //
  159. // We will not add hooks to let other providers go off spec like this.
  160. if !(v.issuer == issuerGoogleAccounts && t.Issuer == issuerGoogleAccountsNoScheme) {
  161. return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.issuer, t.Issuer)
  162. }
  163. }
  164. // If a client ID has been provided, make sure it's part of the audience. SkipClientIDCheck must be true if ClientID is empty.
  165. //
  166. // This check DOES NOT ensure that the ClientID is the party to which the ID Token was issued (i.e. Authorized party).
  167. if !v.config.SkipClientIDCheck {
  168. if v.config.ClientID != "" {
  169. if !contains(t.Audience, v.config.ClientID) {
  170. return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.ClientID, t.Audience)
  171. }
  172. } else {
  173. return nil, fmt.Errorf("oidc: invalid configuration, clientID must be provided or SkipClientIDCheck must be set")
  174. }
  175. }
  176. // If a SkipExpiryCheck is false, make sure token is not expired.
  177. if !v.config.SkipExpiryCheck {
  178. now := time.Now
  179. if v.config.Now != nil {
  180. now = v.config.Now
  181. }
  182. if t.Expiry.Before(now()) {
  183. return nil, fmt.Errorf("oidc: token is expired (Token Expiry: %v)", t.Expiry)
  184. }
  185. }
  186. switch len(jws.Signatures) {
  187. case 0:
  188. return nil, fmt.Errorf("oidc: id token not signed")
  189. case 1:
  190. default:
  191. return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
  192. }
  193. sig := jws.Signatures[0]
  194. supportedSigAlgs := v.config.SupportedSigningAlgs
  195. if len(supportedSigAlgs) == 0 {
  196. supportedSigAlgs = []string{RS256}
  197. }
  198. if !contains(supportedSigAlgs, sig.Header.Algorithm) {
  199. return nil, fmt.Errorf("oidc: id token signed with unsupported algorithm, expected %q got %q", supportedSigAlgs, sig.Header.Algorithm)
  200. }
  201. t.sigAlgorithm = sig.Header.Algorithm
  202. gotPayload, err := v.keySet.VerifySignature(ctx, rawIDToken)
  203. if err != nil {
  204. return nil, fmt.Errorf("failed to verify signature: %v", err)
  205. }
  206. // Ensure that the payload returned by the square actually matches the payload parsed earlier.
  207. if !bytes.Equal(gotPayload, payload) {
  208. return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
  209. }
  210. return t, nil
  211. }
  212. // Nonce returns an auth code option which requires the ID Token created by the
  213. // OpenID Connect provider to contain the specified nonce.
  214. func Nonce(nonce string) oauth2.AuthCodeOption {
  215. return oauth2.SetAuthURLParam("nonce", nonce)
  216. }