From 397fe7649477ff2e8ced8fc0b2696f781e53745a Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Wed, 3 Jun 2015 16:14:52 -0700 Subject: [PATCH] google: add support for JWT Access Tokens This is a new form of authentication for Google services, where instead of passing a signed claim to obtain a token from the OAuth endpoint, you present the signed claim *as* the token to the API endpoint. Fixes #139. Fixes #140. Change-Id: Ibf0f168a0ec111660ac08b86121c943fb96e146c Reviewed-on: https://go-review.googlesource.com/10667 Reviewed-by: David Symonds Reviewed-by: Dave Day --- google/jwt.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ jws/jws.go | 4 +-- 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 google/jwt.go diff --git a/google/jwt.go b/google/jwt.go new file mode 100644 index 0000000..25d1292 --- /dev/null +++ b/google/jwt.go @@ -0,0 +1,67 @@ +// Copyright 2015 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 google + +import ( + "crypto/rsa" + "fmt" + "time" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/internal" + "golang.org/x/oauth2/jws" +) + +// JWTAccessTokenSourceFromJSON uses a Google Developers service account JSON +// key file to read the credentials that authorize and authenticate the +// requests, and returns a TokenSource that does not use any OAuth2 flow but +// instead creates a JWT and sends that as the access token. +// The audience is typically a URL that specifies the scope of the credentials. +func JWTAccessTokenSourceFromJSON(jsonKey []byte, audience string) (oauth2.TokenSource, error) { + cfg, err := JWTConfigFromJSON(jsonKey) + if err != nil { + return nil, fmt.Errorf("google: could not parse JSON key: %v", err) + } + pk, err := internal.ParseKey(cfg.PrivateKey) + if err != nil { + return nil, fmt.Errorf("google: could not parse key: %v", err) + } + ts := &jwtAccessTokenSource{ + email: cfg.Email, + audience: audience, + pk: pk, + } + tok, err := ts.Token() + if err != nil { + return nil, err + } + return oauth2.ReuseTokenSource(tok, ts), nil +} + +type jwtAccessTokenSource struct { + email, audience string + pk *rsa.PrivateKey +} + +func (ts *jwtAccessTokenSource) Token() (*oauth2.Token, error) { + iat := time.Now() + exp := iat.Add(time.Hour) + cs := &jws.ClaimSet{ + Iss: ts.email, + Sub: ts.email, + Aud: ts.audience, + Iat: iat.Unix(), + Exp: exp.Unix(), + } + hdr := &jws.Header{ + Algorithm: "RS256", + Typ: "JWT", + } + msg, err := jws.Encode(hdr, cs, ts.pk) + if err != nil { + return nil, fmt.Errorf("google: could not encode JWT: %v", err) + } + return &oauth2.Token{AccessToken: msg}, nil +} diff --git a/jws/jws.go b/jws/jws.go index 362323c..37d8651 100644 --- a/jws/jws.go +++ b/jws/jws.go @@ -27,8 +27,8 @@ type ClaimSet struct { Iss string `json:"iss"` // email address of the client_id of the application making the access token request Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional). - Exp int64 `json:"exp"` // the expiration time of the assertion - Iat int64 `json:"iat"` // the time the assertion was issued. + Exp int64 `json:"exp"` // the expiration time of the assertion (seconds since Unix epoch) + Iat int64 `json:"iat"` // the time the assertion was issued (seconds since Unix epoch) Typ string `json:"typ,omitempty"` // token type (Optional). // Email for which the application is requesting delegated access (Optional).