1// Copyright 2014 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly 6// known as "two-legged OAuth 2.0". 7// 8// See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12 9package jwt 10 11import ( 12 "encoding/json" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "net/http" 17 "net/url" 18 "strings" 19 "time" 20 21 "golang.org/x/net/context" 22 "golang.org/x/oauth2" 23 "golang.org/x/oauth2/internal" 24 "golang.org/x/oauth2/jws" 25) 26 27var ( 28 defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer" 29 defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"} 30) 31 32// Config is the configuration for using JWT to fetch tokens, 33// commonly known as "two-legged OAuth 2.0". 34type Config struct { 35 // Email is the OAuth client identifier used when communicating with 36 // the configured OAuth provider. 37 Email string 38 39 // PrivateKey contains the contents of an RSA private key or the 40 // contents of a PEM file that contains a private key. The provided 41 // private key is used to sign JWT payloads. 42 // PEM containers with a passphrase are not supported. 43 // Use the following command to convert a PKCS 12 file into a PEM. 44 // 45 // $ openssl pkcs12 -in key.p12 -out key.pem -nodes 46 // 47 PrivateKey []byte 48 49 // PrivateKeyID contains an optional hint indicating which key is being 50 // used. 51 PrivateKeyID string 52 53 // Subject is the optional user to impersonate. 54 Subject string 55 56 // Scopes optionally specifies a list of requested permission scopes. 57 Scopes []string 58 59 // TokenURL is the endpoint required to complete the 2-legged JWT flow. 60 TokenURL string 61 62 // Expires optionally specifies how long the token is valid for. 63 Expires time.Duration 64} 65 66// TokenSource returns a JWT TokenSource using the configuration 67// in c and the HTTP client from the provided context. 68func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource { 69 return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c}) 70} 71 72// Client returns an HTTP client wrapping the context's 73// HTTP transport and adding Authorization headers with tokens 74// obtained from c. 75// 76// The returned client and its Transport should not be modified. 77func (c *Config) Client(ctx context.Context) *http.Client { 78 return oauth2.NewClient(ctx, c.TokenSource(ctx)) 79} 80 81// jwtSource is a source that always does a signed JWT request for a token. 82// It should typically be wrapped with a reuseTokenSource. 83type jwtSource struct { 84 ctx context.Context 85 conf *Config 86} 87 88func (js jwtSource) Token() (*oauth2.Token, error) { 89 pk, err := internal.ParseKey(js.conf.PrivateKey) 90 if err != nil { 91 return nil, err 92 } 93 hc := oauth2.NewClient(js.ctx, nil) 94 claimSet := &jws.ClaimSet{ 95 Iss: js.conf.Email, 96 Scope: strings.Join(js.conf.Scopes, " "), 97 Aud: js.conf.TokenURL, 98 } 99 if subject := js.conf.Subject; subject != "" { 100 claimSet.Sub = subject 101 // prn is the old name of sub. Keep setting it 102 // to be compatible with legacy OAuth 2.0 providers. 103 claimSet.Prn = subject 104 } 105 if t := js.conf.Expires; t > 0 { 106 claimSet.Exp = time.Now().Add(t).Unix() 107 } 108 h := *defaultHeader 109 h.KeyID = js.conf.PrivateKeyID 110 payload, err := jws.Encode(&h, claimSet, pk) 111 if err != nil { 112 return nil, err 113 } 114 v := url.Values{} 115 v.Set("grant_type", defaultGrantType) 116 v.Set("assertion", payload) 117 resp, err := hc.PostForm(js.conf.TokenURL, v) 118 if err != nil { 119 return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) 120 } 121 defer resp.Body.Close() 122 body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20)) 123 if err != nil { 124 return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) 125 } 126 if c := resp.StatusCode; c < 200 || c > 299 { 127 return nil, &oauth2.RetrieveError{ 128 Response: resp, 129 Body: body, 130 } 131 } 132 // tokenRes is the JSON response body. 133 var tokenRes struct { 134 AccessToken string `json:"access_token"` 135 TokenType string `json:"token_type"` 136 IDToken string `json:"id_token"` 137 ExpiresIn int64 `json:"expires_in"` // relative seconds from now 138 } 139 if err := json.Unmarshal(body, &tokenRes); err != nil { 140 return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) 141 } 142 token := &oauth2.Token{ 143 AccessToken: tokenRes.AccessToken, 144 TokenType: tokenRes.TokenType, 145 } 146 raw := make(map[string]interface{}) 147 json.Unmarshal(body, &raw) // no error checks for optional fields 148 token = token.WithExtra(raw) 149 150 if secs := tokenRes.ExpiresIn; secs > 0 { 151 token.Expiry = time.Now().Add(time.Duration(secs) * time.Second) 152 } 153 if v := tokenRes.IDToken; v != "" { 154 // decode returned id token to get expiry 155 claimSet, err := jws.Decode(v) 156 if err != nil { 157 return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err) 158 } 159 token.Expiry = time.Unix(claimSet.Exp, 0) 160 } 161 return token, nil 162} 163