pkce.go raw

   1  // Copyright 2023 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 oauth2
   6  
   7  import (
   8  	"crypto/rand"
   9  	"crypto/sha256"
  10  	"encoding/base64"
  11  	"net/url"
  12  )
  13  
  14  const (
  15  	codeChallengeKey       = "code_challenge"
  16  	codeChallengeMethodKey = "code_challenge_method"
  17  	codeVerifierKey        = "code_verifier"
  18  )
  19  
  20  // GenerateVerifier generates a PKCE code verifier with 32 octets of randomness.
  21  // This follows recommendations in RFC 7636.
  22  //
  23  // A fresh verifier should be generated for each authorization.
  24  // The resulting verifier should be passed to [Config.AuthCodeURL] or [Config.DeviceAuth]
  25  // with [S256ChallengeOption], and to [Config.Exchange] or [Config.DeviceAccessToken]
  26  // with [VerifierOption].
  27  func GenerateVerifier() string {
  28  	// "RECOMMENDED that the output of a suitable random number generator be
  29  	// used to create a 32-octet sequence.  The octet sequence is then
  30  	// base64url-encoded to produce a 43-octet URL-safe string to use as the
  31  	// code verifier."
  32  	// https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
  33  	data := make([]byte, 32)
  34  	if _, err := rand.Read(data); err != nil {
  35  		panic(err)
  36  	}
  37  	return base64.RawURLEncoding.EncodeToString(data)
  38  }
  39  
  40  // VerifierOption returns a PKCE code verifier [AuthCodeOption]. It should only be
  41  // passed to [Config.Exchange] or [Config.DeviceAccessToken].
  42  func VerifierOption(verifier string) AuthCodeOption {
  43  	return setParam{k: codeVerifierKey, v: verifier}
  44  }
  45  
  46  // S256ChallengeFromVerifier returns a PKCE code challenge derived from verifier with method S256.
  47  //
  48  // Prefer to use [S256ChallengeOption] where possible.
  49  func S256ChallengeFromVerifier(verifier string) string {
  50  	sha := sha256.Sum256([]byte(verifier))
  51  	return base64.RawURLEncoding.EncodeToString(sha[:])
  52  }
  53  
  54  // S256ChallengeOption derives a PKCE code challenge from the verifier with
  55  // method S256. It should be passed to [Config.AuthCodeURL] or [Config.DeviceAuth]
  56  // only.
  57  func S256ChallengeOption(verifier string) AuthCodeOption {
  58  	return challengeOption{
  59  		challenge_method: "S256",
  60  		challenge:        S256ChallengeFromVerifier(verifier),
  61  	}
  62  }
  63  
  64  type challengeOption struct{ challenge_method, challenge string }
  65  
  66  func (p challengeOption) setValue(m url.Values) {
  67  	m.Set(codeChallengeMethodKey, p.challenge_method)
  68  	m.Set(codeChallengeKey, p.challenge)
  69  }
  70