forked from Mirrors/oauth2
oauth2, oauth2/jwt: break JWT off into its own package
Change-Id: Iaaa36728f87744e0d9609674f0d0ad96e6ac80b4 Reviewed-on: https://go-review.googlesource.com/2198 Reviewed-by: Burcu Dogan <jbd@google.com>
This commit is contained in:
parent
dfb470cc49
commit
ed997606a9
|
@ -48,24 +48,3 @@ func ExampleConfig() {
|
||||||
client := conf.Client(oauth2.NoContext, tok)
|
client := conf.Client(oauth2.NoContext, tok)
|
||||||
client.Get("...")
|
client.Get("...")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleJWTConfig() {
|
|
||||||
conf := &oauth2.JWTConfig{
|
|
||||||
Email: "xxx@developer.com",
|
|
||||||
// The contents of your RSA private key or your PEM file
|
|
||||||
// that contains a private key.
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// It only supports PEM containers with no passphrase.
|
|
||||||
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
|
|
||||||
Subject: "user@example.com",
|
|
||||||
TokenURL: "https://provider.com/o/oauth2/token",
|
|
||||||
}
|
|
||||||
// Initiate an http.Client, the following GET request will be
|
|
||||||
// authorized and authenticated on the behalf of user@example.com.
|
|
||||||
client := conf.Client(oauth2.NoContext)
|
|
||||||
client.Get("...")
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
"google.golang.org/appengine"
|
"google.golang.org/appengine"
|
||||||
"google.golang.org/appengine/urlfetch"
|
"google.golang.org/appengine/urlfetch"
|
||||||
)
|
)
|
||||||
|
@ -76,7 +77,7 @@ func ExampleJWTConfigFromJSON() {
|
||||||
func Example_serviceAccount() {
|
func Example_serviceAccount() {
|
||||||
// Your credentials should be obtained from the Google
|
// Your credentials should be obtained from the Google
|
||||||
// Developer Console (https://console.developers.google.com).
|
// Developer Console (https://console.developers.google.com).
|
||||||
conf := &oauth2.JWTConfig{
|
conf := &jwt.Config{
|
||||||
Email: "xxx@developer.gserviceaccount.com",
|
Email: "xxx@developer.gserviceaccount.com",
|
||||||
// The contents of your RSA private key or your PEM file
|
// The contents of your RSA private key or your PEM file
|
||||||
// that contains a private key.
|
// that contains a private key.
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(bradfitz,jbd): import "google.golang.org/cloud/compute/metadata" instead of
|
// TODO(bradfitz,jbd): import "google.golang.org/cloud/compute/metadata" instead of
|
||||||
|
@ -39,7 +40,7 @@ const JWTTokenURL = "https://accounts.google.com/o/oauth2/token"
|
||||||
// the credentials that authorize and authenticate the requests.
|
// the credentials that authorize and authenticate the requests.
|
||||||
// Create a service account on "Credentials" page under "APIs & Auth" for your
|
// Create a service account on "Credentials" page under "APIs & Auth" for your
|
||||||
// project at https://console.developers.google.com to download a JSON key file.
|
// project at https://console.developers.google.com to download a JSON key file.
|
||||||
func JWTConfigFromJSON(ctx oauth2.Context, jsonKey []byte, scope ...string) (*oauth2.JWTConfig, error) {
|
func JWTConfigFromJSON(ctx oauth2.Context, jsonKey []byte, scope ...string) (*jwt.Config, error) {
|
||||||
var key struct {
|
var key struct {
|
||||||
Email string `json:"client_email"`
|
Email string `json:"client_email"`
|
||||||
PrivateKey string `json:"private_key"`
|
PrivateKey string `json:"private_key"`
|
||||||
|
@ -47,7 +48,7 @@ func JWTConfigFromJSON(ctx oauth2.Context, jsonKey []byte, scope ...string) (*oa
|
||||||
if err := json.Unmarshal(jsonKey, &key); err != nil {
|
if err := json.Unmarshal(jsonKey, &key); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &oauth2.JWTConfig{
|
return &jwt.Config{
|
||||||
Email: key.Email,
|
Email: key.Email,
|
||||||
PrivateKey: []byte(key.PrivateKey),
|
PrivateKey: []byte(key.PrivateKey),
|
||||||
Scopes: scope,
|
Scopes: scope,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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 jwt_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleJWTConfig() {
|
||||||
|
conf := &jwt.Config{
|
||||||
|
Email: "xxx@developer.com",
|
||||||
|
// The contents of your RSA private key or your PEM file
|
||||||
|
// that contains a private key.
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// It only supports PEM containers with no passphrase.
|
||||||
|
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
|
||||||
|
Subject: "user@example.com",
|
||||||
|
TokenURL: "https://provider.com/o/oauth2/token",
|
||||||
|
}
|
||||||
|
// Initiate an http.Client, the following GET request will be
|
||||||
|
// authorized and authenticated on the behalf of user@example.com.
|
||||||
|
client := conf.Client(oauth2.NoContext)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
|
@ -2,7 +2,11 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package oauth2
|
// Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly
|
||||||
|
// known as "two-legged OAuth 2.0".
|
||||||
|
//
|
||||||
|
// See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12
|
||||||
|
package jwt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -14,6 +18,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/internal"
|
"golang.org/x/oauth2/internal"
|
||||||
"golang.org/x/oauth2/jws"
|
"golang.org/x/oauth2/jws"
|
||||||
)
|
)
|
||||||
|
@ -23,9 +28,9 @@ var (
|
||||||
defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
|
defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// JWTConfig is the configuration for using JWT to fetch tokens,
|
// Config is the configuration for using JWT to fetch tokens,
|
||||||
// commonly known as "two-legged OAuth".
|
// commonly known as "two-legged OAuth 2.0".
|
||||||
type JWTConfig struct {
|
type Config struct {
|
||||||
// Email is the OAuth client identifier used when communicating with
|
// Email is the OAuth client identifier used when communicating with
|
||||||
// the configured OAuth provider.
|
// the configured OAuth provider.
|
||||||
Email string
|
Email string
|
||||||
|
@ -52,8 +57,8 @@ type JWTConfig struct {
|
||||||
|
|
||||||
// TokenSource returns a JWT TokenSource using the configuration
|
// TokenSource returns a JWT TokenSource using the configuration
|
||||||
// in c and the HTTP client from the provided context.
|
// in c and the HTTP client from the provided context.
|
||||||
func (c *JWTConfig) TokenSource(ctx Context) TokenSource {
|
func (c *Config) TokenSource(ctx oauth2.Context) oauth2.TokenSource {
|
||||||
return ReuseTokenSource(nil, jwtSource{ctx, c})
|
return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client returns an HTTP client wrapping the context's
|
// Client returns an HTTP client wrapping the context's
|
||||||
|
@ -61,26 +66,23 @@ func (c *JWTConfig) TokenSource(ctx Context) TokenSource {
|
||||||
// obtained from c.
|
// obtained from c.
|
||||||
//
|
//
|
||||||
// The returned client and its Transport should not be modified.
|
// The returned client and its Transport should not be modified.
|
||||||
func (c *JWTConfig) Client(ctx Context) *http.Client {
|
func (c *Config) Client(ctx oauth2.Context) *http.Client {
|
||||||
return NewClient(ctx, c.TokenSource(ctx))
|
return oauth2.NewClient(ctx, c.TokenSource(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// jwtSource is a source that always does a signed JWT request for a token.
|
// jwtSource is a source that always does a signed JWT request for a token.
|
||||||
// It should typically be wrapped with a reuseTokenSource.
|
// It should typically be wrapped with a reuseTokenSource.
|
||||||
type jwtSource struct {
|
type jwtSource struct {
|
||||||
ctx Context
|
ctx oauth2.Context
|
||||||
conf *JWTConfig
|
conf *Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (js jwtSource) Token() (*Token, error) {
|
func (js jwtSource) Token() (*oauth2.Token, error) {
|
||||||
pk, err := internal.ParseKey(js.conf.PrivateKey)
|
pk, err := internal.ParseKey(js.conf.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hc, err := contextClient(js.ctx)
|
hc := oauth2.NewClient(js.ctx, nil)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
claimSet := &jws.ClaimSet{
|
claimSet := &jws.ClaimSet{
|
||||||
Iss: js.conf.Email,
|
Iss: js.conf.Email,
|
||||||
Scope: strings.Join(js.conf.Scopes, " "),
|
Scope: strings.Join(js.conf.Scopes, " "),
|
||||||
|
@ -121,12 +123,13 @@ func (js jwtSource) Token() (*Token, error) {
|
||||||
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
||||||
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
}
|
}
|
||||||
token := &Token{
|
token := &oauth2.Token{
|
||||||
AccessToken: tokenRes.AccessToken,
|
AccessToken: tokenRes.AccessToken,
|
||||||
TokenType: tokenRes.TokenType,
|
TokenType: tokenRes.TokenType,
|
||||||
raw: make(map[string]interface{}),
|
|
||||||
}
|
}
|
||||||
json.Unmarshal(body, &token.raw) // no error checks for optional fields
|
raw := make(map[string]interface{})
|
||||||
|
json.Unmarshal(body, &raw) // no error checks for optional fields
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
if secs := tokenRes.ExpiresIn; secs > 0 {
|
if secs := tokenRes.ExpiresIn; secs > 0 {
|
||||||
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
|
@ -2,12 +2,14 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package oauth2
|
package jwt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var dummyPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
var dummyPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
@ -50,12 +52,12 @@ func TestJWTFetch_JSONResponse(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
conf := &JWTConfig{
|
conf := &Config{
|
||||||
Email: "aaa@xxx.com",
|
Email: "aaa@xxx.com",
|
||||||
PrivateKey: dummyPrivateKey,
|
PrivateKey: dummyPrivateKey,
|
||||||
TokenURL: ts.URL,
|
TokenURL: ts.URL,
|
||||||
}
|
}
|
||||||
tok, err := conf.TokenSource(NoContext).Token()
|
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -84,12 +86,12 @@ func TestJWTFetch_BadResponse(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
conf := &JWTConfig{
|
conf := &Config{
|
||||||
Email: "aaa@xxx.com",
|
Email: "aaa@xxx.com",
|
||||||
PrivateKey: dummyPrivateKey,
|
PrivateKey: dummyPrivateKey,
|
||||||
TokenURL: ts.URL,
|
TokenURL: ts.URL,
|
||||||
}
|
}
|
||||||
tok, err := conf.TokenSource(NoContext).Token()
|
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -117,12 +119,12 @@ func TestJWTFetch_BadResponseType(t *testing.T) {
|
||||||
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
conf := &JWTConfig{
|
conf := &Config{
|
||||||
Email: "aaa@xxx.com",
|
Email: "aaa@xxx.com",
|
||||||
PrivateKey: dummyPrivateKey,
|
PrivateKey: dummyPrivateKey,
|
||||||
TokenURL: ts.URL,
|
TokenURL: ts.URL,
|
||||||
}
|
}
|
||||||
tok, err := conf.TokenSource(NoContext).Token()
|
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("got a token; expected error")
|
t.Error("got a token; expected error")
|
||||||
if tok.AccessToken != "" {
|
if tok.AccessToken != "" {
|
11
oauth2.go
11
oauth2.go
|
@ -412,7 +412,18 @@ type contextKey struct{}
|
||||||
|
|
||||||
// NewClient creates an *http.Client from a Context and TokenSource.
|
// NewClient creates an *http.Client from a Context and TokenSource.
|
||||||
// The returned client is not valid beyond the lifetime of the context.
|
// The returned client is not valid beyond the lifetime of the context.
|
||||||
|
//
|
||||||
|
// As a special case, if src is nil, a non-OAuth2 client is returned
|
||||||
|
// using the provided context. This exists to support related OAuth2
|
||||||
|
// packages.
|
||||||
func NewClient(ctx Context, src TokenSource) *http.Client {
|
func NewClient(ctx Context, src TokenSource) *http.Client {
|
||||||
|
if src == nil {
|
||||||
|
c, err := contextClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return &http.Client{Transport: errorTransport{err}}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: &Transport{
|
Transport: &Transport{
|
||||||
Base: contextTransport(ctx),
|
Base: contextTransport(ctx),
|
||||||
|
|
10
token.go
10
token.go
|
@ -60,6 +60,16 @@ func (t *Token) SetAuthHeader(r *http.Request) {
|
||||||
r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
|
r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithExtra returns a new Token that's a clone of t, but using the
|
||||||
|
// provided raw extra map. This is only intended for use by packages
|
||||||
|
// implementing derivative OAuth2 flows.
|
||||||
|
func (t *Token) WithExtra(extra interface{}) *Token {
|
||||||
|
t2 := new(Token)
|
||||||
|
*t2 = *t
|
||||||
|
t2.raw = extra
|
||||||
|
return t2
|
||||||
|
}
|
||||||
|
|
||||||
// Extra returns an extra field returned from the server during token
|
// Extra returns an extra field returned from the server during token
|
||||||
// retrieval.
|
// retrieval.
|
||||||
func (t *Token) Extra(key string) string {
|
func (t *Token) Extra(key string) string {
|
||||||
|
|
Loading…
Reference in New Issue