JSON Web Token (JWT) and JSON Web Signature (JWS) support for 2-legged OAuth2 flows with service account authentication.
import "golang.org/x/oauth2/jwt"
import "golang.org/x/oauth2/jws"The JWT package implements the OAuth 2.0 JSON Web Token flow (RFC 7523), commonly known as "two-legged OAuth 2.0". This is typically used for service account authentication.
See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12
type Config struct {
// Email is the OAuth client identifier
Email string
// PrivateKey contains RSA private key or PEM file contents
// PEM containers with a passphrase are not supported
PrivateKey []byte
// PrivateKeyID is an optional hint indicating which key is being used
PrivateKeyID string
// Subject is the optional user to impersonate
Subject string
// Scopes optionally specifies requested permission scopes
Scopes []string
// TokenURL is the endpoint required to complete the 2-legged JWT flow
TokenURL string
// Expires optionally specifies how long the token is valid for
Expires time.Duration
// Audience optionally specifies the intended audience
// If empty, TokenURL is used as the intended audience
Audience string
// PrivateClaims optionally specifies custom private claims in the JWT
PrivateClaims map[string]any
// UseIDToken specifies whether to use ID token instead of access token
// when the server returns both
UseIDToken bool
}func (c *Config) Client(ctx context.Context) *http.ClientReturns an HTTP client that automatically handles JWT token generation and includes authorization headers. The returned client and its Transport should not be modified.
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSourceReturns a JWT TokenSource using the configuration and HTTP client from the context. Most users should use Client instead.
import (
"context"
"io/ioutil"
"log"
"golang.org/x/oauth2/jwt"
)
func main() {
// Read private key from PEM file
privateKey, err := ioutil.ReadFile("service-account-key.pem")
if err != nil {
log.Fatal(err)
}
// Configure JWT
conf := &jwt.Config{
Email: "service@example.iam.gserviceaccount.com",
PrivateKey: privateKey,
TokenURL: "https://oauth2.googleapis.com/token",
Scopes: []string{
"https://www.googleapis.com/auth/cloud-platform",
},
}
// Create authenticated client
client := conf.Client(context.Background())
// Use client for API requests
resp, err := client.Get("https://www.googleapis.com/storage/v1/b")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// ...
}conf := &jwt.Config{
Email: "service@example.iam.gserviceaccount.com",
PrivateKey: privateKey,
Subject: "user@example.com", // Impersonate this user
TokenURL: "https://oauth2.googleapis.com/token",
Scopes: []string{"https://www.googleapis.com/auth/drive"},
}
client := conf.Client(ctx)conf := &jwt.Config{
Email: "service@example.iam.gserviceaccount.com",
PrivateKey: privateKey,
TokenURL: "https://oauth2.googleapis.com/token",
Scopes: []string{"custom_scope"},
PrivateClaims: map[string]any{
"tenant_id": "tenant123",
"custom_field": "value",
},
}
client := conf.Client(ctx)// Use ID token instead of access token
conf := &jwt.Config{
Email: "service@example.iam.gserviceaccount.com",
PrivateKey: privateKey,
TokenURL: "https://oauth2.googleapis.com/token",
UseIDToken: true,
}
client := conf.Client(ctx)The JWT package requires RSA private keys in PEM format. To convert a PKCS12 file:
openssl pkcs12 -in key.p12 -out key.pem -nodesThe JWS package provides JSON Web Signature encoding and decoding (RFC 7515). It primarily exists to support the OAuth2 JWT flow.
Note: This package is deprecated and not intended for public use. It exists for internal OAuth2 support. For production JWS needs, use a dedicated JWS library or copy this package into your own source tree.
type ClaimSet struct {
// Iss is the email address of the client_id making the request
Iss string `json:"iss"`
// Scope is the space-delimited list of requested permissions
Scope string `json:"scope,omitempty"`
// Aud is the intended target descriptor (optional)
Aud string `json:"aud"`
// Exp is the expiration time (seconds since Unix epoch)
Exp int64 `json:"exp"`
// Iat is the time the assertion was issued (seconds since Unix epoch)
Iat int64 `json:"iat"`
// Typ is the token type (optional)
Typ string `json:"typ,omitempty"`
// Sub is the email for which delegated access is requested (optional)
Sub string `json:"sub,omitempty"`
// Prn is the old name of Sub for legacy OAuth 2.0 providers (optional)
Prn string `json:"prn,omitempty"`
// PrivateClaims are custom private claims
PrivateClaims map[string]any `json:"-"`
}type Header struct {
// Algorithm is the algorithm used for signature
Algorithm string `json:"alg"`
// Typ represents the token type
Typ string `json:"typ"`
// KeyID is an optional hint of which key is being used
KeyID string `json:"kid,omitempty"`
}func Encode(header *Header, c *ClaimSet, key *rsa.PrivateKey) (string, error)Encodes a signed JWS with the provided header and claim set using RSA PKCS1v15 signing with SHA256.
type Signer func(data []byte) (sig []byte, err error)
func EncodeWithSigner(header *Header, c *ClaimSet, sg Signer) (string, error)Encodes a header and claim set with a custom signer function for alternative signing methods.
func Decode(payload string) (*ClaimSet, error)Decodes a claim set from a JWS payload string.
func Verify(token string, key *rsa.PublicKey) errorVerifies that a JWT token's signature was produced by the private key associated with the supplied public key.
import (
"crypto/rsa"
"time"
"golang.org/x/oauth2/jws"
)
func createJWT(privateKey *rsa.PrivateKey) (string, error) {
// Create header
header := &jws.Header{
Algorithm: "RS256",
Typ: "JWT",
KeyID: "key-id-123",
}
// Create claim set
now := time.Now()
claims := &jws.ClaimSet{
Iss: "service@example.com",
Scope: "read write",
Aud: "https://oauth2.googleapis.com/token",
Exp: now.Add(time.Hour).Unix(),
Iat: now.Unix(),
Sub: "user@example.com",
PrivateClaims: map[string]any{
"custom": "value",
},
}
// Encode and sign
token, err := jws.Encode(header, claims, privateKey)
if err != nil {
return "", err
}
return token, nil
}import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
)
func createJWTWithECDSA(privateKey *ecdsa.PrivateKey) (string, error) {
header := &jws.Header{
Algorithm: "ES256",
Typ: "JWT",
}
claims := &jws.ClaimSet{
Iss: "service@example.com",
Aud: "https://oauth2.example.com/token",
}
// Custom ECDSA signer
signer := func(data []byte) ([]byte, error) {
hash := sha256.Sum256(data)
return ecdsa.SignASN1(rand.Reader, privateKey, hash[:])
}
return jws.EncodeWithSigner(header, claims, signer)
}func verifyJWT(token string, publicKey *rsa.PublicKey) error {
// Verify signature
if err := jws.Verify(token, publicKey); err != nil {
return fmt.Errorf("invalid signature: %w", err)
}
// Decode and check claims
claims, err := jws.Decode(token)
if err != nil {
return fmt.Errorf("invalid token: %w", err)
}
// Check expiration
if time.Now().Unix() > claims.Exp {
return fmt.Errorf("token expired")
}
// Check issuer
if claims.Iss != "expected-issuer@example.com" {
return fmt.Errorf("unexpected issuer: %s", claims.Iss)
}
return nil
}JWT flows are commonly used for:
For Google Workspace domain-wide delegation:
conf := &jwt.Config{
Email: "service@project.iam.gserviceaccount.com",
PrivateKey: privateKey,
Subject: "admin@example.com", // Admin user to impersonate
TokenURL: "https://oauth2.googleapis.com/token",
Scopes: []string{
"https://www.googleapis.com/auth/admin.directory.user",
},
}token, err := conf.TokenSource(ctx).Token()
if err != nil {
if rerr, ok := err.(*oauth2.RetrieveError); ok {
log.Printf("OAuth error: %s - %s\n",
rerr.ErrorCode, rerr.ErrorDescription)
}
return err
}| Feature | JWT Flow | Client Credentials |
|---|---|---|
| Authentication | RSA key signing | Client ID/Secret |
| Use case | Service accounts | Machine-to-machine |
| Token request | Self-signed JWT | Standard OAuth2 |
| Key management | RSA key pairs | Shared secrets |
| Google Cloud | Primary method | Alternative method |
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"golang.org/x/oauth2/jwt"
)
func main() {
ctx := context.Background()
// Load service account key
keyData, err := ioutil.ReadFile("service-account.pem")
if err != nil {
log.Fatal(err)
}
// Configure JWT
conf := &jwt.Config{
Email: "my-service@project.iam.gserviceaccount.com",
PrivateKey: keyData,
PrivateKeyID: "key-123",
TokenURL: "https://oauth2.googleapis.com/token",
Scopes: []string{
"https://www.googleapis.com/auth/cloud-platform",
},
Expires: 3600, // 1 hour
}
// Create client
client := conf.Client(ctx)
// Make authenticated request
resp, err := client.Get("https://cloudresourcemanager.googleapis.com/v1/projects")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Response: %s\n", body)
}