Package rfc9068
Overview
Package rfc9068 implements the JWT Profile for OAuth 2.0 Access Tokens, from RFC 9068: the claims an access token carries, and the checks a resource server makes before it accepts one.
It doesn't verify signatures, which needs a JOSE library. A resource server verifies the JWS with the authorization server's keys, checks its typ header with CheckType, unmarshals the payload into Claims, and calls Claims.Validate:
err := rfc9068.CheckType(typ)
var claims rfc9068.Claims
err = json.Unmarshal(payload, &claims)
err = claims.Validate(rfc9068.Expected{
Issuer: "https://as.example.com",
Audience: rfc8707.MustResource("https://mcp.example.com/mcp"),
Now: time.Now(),
Leeway: 30 * time.Second,
})Section 4 says every failed check is answered with RFC 6750's invalid_token error.
The package doesn't cover encrypted tokens (Section 4) or the authorization claims other than scope (Section 2.2.3), which go through Claims.Extension.
The RFC text is in rfc9068.txt in this directory, and the doc comments cite its sections.
Index
Constants
const (
ClaimIssuer = "iss"
ClaimExpiry = "exp"
ClaimAudience = "aud"
ClaimSubject = "sub"
ClaimClientID = "client_id"
ClaimIssuedAt = "iat"
ClaimID = "jti"
)The names of the claims Section 2.2 requires, for ErrMissingClaim.
const MediaType = "application/at+jwt"MediaType is the media type of a JWT access token (Section 7.2).
const TokenType = "at+jwt"TokenType is the typ header of a JWT access token. MediaType is its full form, which Section 4 accepts too.
See RFC 9068, Section 2.1.
Variables
var (
// ErrWrongType is returned by [CheckType] for a typ header that isn't
// at+jwt.
ErrWrongType = errors.New("not a JWT access token")
// ErrInvalidClaims is returned by [Claims.UnmarshalJSON] for a claim
// with the wrong JSON type, such as a string exp.
ErrInvalidClaims = errors.New("invalid claims")
// ErrMissingClaim is returned by [Claims.Validate] for a token without
// a claim Section 2.2 requires.
ErrMissingClaim = errors.New("missing claim")
// ErrWrongIssuer is returned by [Claims.Validate] when iss isn't the
// expected issuer.
ErrWrongIssuer = errors.New("wrong issuer")
// ErrWrongAudience is returned by [Claims.Validate] when aud doesn't
// contain the expected resource.
ErrWrongAudience = errors.New("wrong audience")
// ErrExpired is returned by [Claims.Validate] for a token past its
// exp.
ErrExpired = errors.New("token expired")
// ErrNotYetValid is returned by [Claims.Validate] for a token whose
// iat or nbf is in the future.
ErrNotYetValid = errors.New("token not yet valid")
)Functions
CheckType source
func CheckType(typ string) errorCheckType returns ErrWrongType unless typ, a JWT's typ header, is at+jwt or application/at+jwt. Section 4 says a resource server MUST reject any other value. Media type names are case-insensitive (RFC 7515, Section 4.1.9).
Types
Claims source
type Claims struct {
Issuer string
Subject string
Audience []string
Expiry time.Time
NotBefore time.Time
IssuedAt time.Time
ID string
ClientID string
Scope oauth21draft.Scope
// contains filtered or unexported fields
}Claims are an access token's claims: those Section 2.2 requires, scope from Section 2.2.3, the optional nbf, and any others as extension members. Set the standard claims directly, and others with Claims.WithExtension.
Claims has its own MarshalJSON and UnmarshalJSON. Times are NumericDate seconds, and aud is marshalled as a string when it has one value, as RFC 7519, Section 4.1.3, allows.
(Claims) Extension source
func (c Claims) Extension(name string, v any) (bool, error)Extension unmarshals the extension claim with the name into v, as json.Unmarshal does. It reports false if c has no such claim.
(Claims) MarshalJSON source
func (c Claims) MarshalJSON() ([]byte, error)MarshalJSON returns c as a JWT claims set: the standard claims, leaving out empty ones, then extension claims sorted by name.
(Claims) UnmarshalJSON source
func (c *Claims) UnmarshalJSON(data []byte) errorUnmarshalJSON reads a JWT claims set, keeping claims it doesn't define as extension claims. It returns ErrInvalidClaims for a claim of the wrong type, a time that isn't a finite number, or a scope that isn't valid.
(Claims) Validate source
func (c Claims) Validate(want Expected) errorValidate makes the checks Section 4 lists that don't need the token's signature or header: that the claims Section 2.2 requires are present (ErrMissingClaim), that iss matches exactly (ErrWrongIssuer), that aud contains the resource (ErrWrongAudience), and that Now is before exp (ErrExpired). It also refuses a token whose iat or nbf is after Now (ErrNotYetValid), as RFC 7519, Section 4.1.5, requires for nbf. Each time is allowed the Leeway.
Resources are compared as rfc8707.Resource.Equal does, so the scheme and host are case-insensitive.
(Claims) WithExtension source
func (c Claims) WithExtension(name string, value any) ClaimsWithExtension returns a copy of c with an extension claim, replacing any with the same name. The value is marshalled when c is, so Claims.MarshalJSON returns an error if it can't be, or if the name is a standard claim's.
Expected source
type Expected struct {
// Issuer is the authorization server's issuer identifier, which iss
// must match exactly.
Issuer string
// Audience is the resource server's own resource indicator, which aud
// must contain.
Audience rfc8707.Resource
// Now is the time to check exp, nbf and iat against.
Now time.Time
// Leeway allows for clock skew between the servers. Section 4 says it's
// "usually no more than a few minutes".
Leeway time.Duration
}Expected is what a resource server expects of a token.