forked from golang/oauth2
/
jwt.go
142 lines (120 loc) · 3.72 KB
/
jwt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright 2014 The oauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package oauth2
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/golang/oauth2/jws"
)
var (
defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
)
// JWTOptions represents a OAuth2 client's crendentials to retrieve a
// Bearer JWT token.
type JWTOptions struct {
// ClientID is the OAuth client identifier used when communicating with
// the configured OAuth provider.
Email string `json:"email"`
// The path to the pem file. If you have a p12 file instead, you
// can use `openssl` to export the private key into a pem file.
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
// Pem file should contain your private key.
PemFilename string `json:"pemfilename"`
// Scopes identify the level of access being requested.
Scopes []string `json:"scopes"`
}
// TODO(jbd): Add p12 support.
// NewJWTConfig creates a new configuration with the specified options
// and OAuth2 provider endpoint.
func NewJWTConfig(opts *JWTOptions, aud string) (*JWTConfig, error) {
audURL, err := url.Parse(aud)
if err != nil {
return nil, err
}
contents, err := ioutil.ReadFile(opts.PemFilename)
if err != nil {
return nil, err
}
return &JWTConfig{opts: opts, aud: audURL, signature: contents}, nil
}
// JWTConfig represents an OAuth 2.0 provider and client options to
// provide authorized transports with a Bearer JWT token.
type JWTConfig struct {
opts *JWTOptions
aud *url.URL
signature []byte
}
// NewTransport creates a transport that is authorize with the
// parent JWT configuration.
func (c *JWTConfig) NewTransport() Transport {
return NewAuthorizedTransport(http.DefaultTransport, c, &Token{})
}
// NewTransportWithUser creates a transport that is authorized by
// the client and impersonates the specified user.
func (c *JWTConfig) NewTransportWithUser(user string) Transport {
return NewAuthorizedTransport(http.DefaultTransport, c, &Token{Subject: user})
}
// fetchToken retrieves a new access token and updates the existing token
// with the newly fetched credentials.
func (c *JWTConfig) FetchToken(existing *Token) (token *Token, err error) {
if existing == nil {
existing = &Token{}
}
claimSet := &jws.ClaimSet{
Iss: c.opts.Email,
Scope: strings.Join(c.opts.Scopes, " "),
Aud: c.aud.String(),
}
if existing.Subject != "" {
claimSet.Sub = existing.Subject
// prn is the old name of sub. Keep setting it
// to be compatible with legacy OAuth 2.0 providers.
claimSet.Prn = existing.Subject
}
payload, err := jws.Encode(defaultHeader, claimSet, c.signature)
if err != nil {
return
}
v := url.Values{}
v.Set("grant_type", defaultGrantType)
v.Set("assertion", payload)
// Make a request with assertion to get a new token.
resp, err := http.DefaultClient.PostForm(c.aud.String(), v)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
// TODO(jbd): Provide more context about the response.
return nil, errors.New("Cannot fetch token, response: " + resp.Status)
}
b := &tokenRespBody{}
err = json.NewDecoder(resp.Body).Decode(b)
if err != nil {
return nil, err
}
token = &Token{
AccessToken: b.AccessToken,
TokenType: b.TokenType,
Subject: existing.Subject,
}
if b.IdToken != "" {
// decode returned id token to get expiry
claimSet := &jws.ClaimSet{}
claimSet, err = jws.Decode(b.IdToken)
if err != nil {
return
}
token.Expiry = time.Unix(claimSet.Exp, 0)
return
}
token.Expiry = time.Now().Add(time.Duration(b.ExpiresIn) * time.Second)
return
}