Package rfc6750
Overview
Package rfc6750 implements the parts of RFC 6750 (OAuth 2.0 Bearer Token Usage) that a resource server needs: reading a bearer token from the Authorization header field, and challenging a client that didn't send a valid one.
Build the Challenge once, while wiring the server:
bearer := rfc6750.MustChallenge("api")Then answer each request with the status that matches the failure. The error responses set the status code that Section 3.1 gives each error code, so the two can't disagree:
rfc9110.Unauthorized(bearer.Challenge()) // No token was sent.
rfc6750.InvalidToken(bearer, "The access token expired") // 401
rfc6750.InsufficientScope(bearer, "", "users:write") // 403
rfc6750.InvalidRequest(bearer, "") // 400The package doesn't cover sending a token in a form-encoded body or a URI query parameter (Sections 2.2 and 2.3), or the error_uri attribute.
The RFC text is in rfc6750.txt in this directory, and the doc comments cite
its sections. Example shows a resource server choosing the RFC 6750 response for a request
with no token, an invalid token, and a valid token without the scope it
needs. Output:Example
package main
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"funnyhaha.technology/rfc/rfc6750"
"funnyhaha.technology/rfc/rfc9110"
)
func main() {
bearer := rfc6750.MustChallenge("api")
authorize := func(r *http.Request) rfc9110.Status {
token, err := rfc6750.ParseAuthorization(r.Header.Get("Authorization"))
switch {
case errors.Is(err, rfc6750.ErrNoToken):
return rfc9110.Unauthorized(bearer.Challenge())
case err != nil || token.Reveal() != "valid":
return rfc6750.InvalidToken(bearer, "The access token is invalid")
default:
return rfc6750.InsufficientScope(bearer, "", "users:write")
}
}
for _, header := range []string{"", "Bearer expired", "Bearer valid"} {
r := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/users", nil)
r.Header.Set("Authorization", header)
s := authorize(r)
fmt.Println(s.Code(), s.Header().Get("WWW-Authenticate"))
}
}
401 Bearer realm="api"
401 Bearer realm="api", error="invalid_token", error_description="The access token is invalid"
403 Bearer realm="api", scope="users:write", error="insufficient_scope"
Index
- Variables
func InsufficientScopefunc InvalidRequestfunc InvalidTokentype Challengetype Descriptiontype Scopetype Token
Variables
var (
// ErrInvalidRealm is returned for a realm that's empty or contains a
// control character.
ErrInvalidRealm = errors.New("invalid realm")
// ErrInvalidScope is returned for a scope value with characters outside
// the set Section 3 allows.
ErrInvalidScope = errors.New("invalid scope")
// ErrInvalidDescription is returned for an error_description with
// characters outside the set Section 3 allows.
ErrInvalidDescription = errors.New("invalid error description")
// ErrNoToken is returned by [ParseAuthorization] when the request has
// no bearer credentials: the field is empty or uses another scheme.
// Section 3.1 says to respond with a challenge but no error code.
ErrNoToken = errors.New("no bearer token")
// ErrInvalidToken is returned by [ParseAuthorization] for bearer
// credentials that aren't a b64token. Respond with [InvalidToken].
ErrInvalidToken = errors.New("invalid bearer token")
)Functions
InsufficientScope source
func InsufficientScope(c Challenge, d Description, need Scope, more ...Scope) rfc9110.StatusInsufficientScope returns 403 Forbidden with an insufficient_scope challenge listing the scopes the request needs, in place of any set with Challenge.WithScope (Section 3.1).
InvalidRequest source
func InvalidRequest(c Challenge, d Description) rfc9110.StatusInvalidRequest returns 400 Bad Request with an invalid_request challenge: the request is missing a parameter, repeats one, uses more than one way of sending a token, or is otherwise malformed (Section 3.1).
InvalidToken source
func InvalidToken(c Challenge, d Description) rfc9110.StatusInvalidToken returns 401 Unauthorized with an invalid_token challenge: the token is expired, revoked, malformed or otherwise invalid (Section 3.1).
Types
Challenge source
type Challenge struct {
// contains filtered or unexported fields
}Challenge is a Bearer challenge for a realm, with optional scopes. Build it once, with NewChallenge or MustChallenge.
See RFC 6750, Section 3.
MustChallenge source
func MustChallenge(realm string) ChallengeMustChallenge is like NewChallenge but panics if realm isn't valid. Use it while wiring a server, so a bad realm stops it starting.
NewChallenge source
func NewChallenge(realm string) (Challenge, error)NewChallenge returns a challenge for the realm. The realm is required, because Section 3 says the scheme "MUST be followed by one or more auth-param values". It returns ErrInvalidRealm if realm is empty or contains a control character.
(Challenge) Challenge source
func (c Challenge) Challenge() rfc9110.ChallengeChallenge returns c as an rfc9110.Challenge, with no error code. Send it when a request has no token (Section 3.1):
rfc9110.Unauthorized(bearer.Challenge())(Challenge) WithParam source
func (c Challenge) WithParam(p rfc9110.AuthParam) ChallengeWithParam returns a copy of c whose challenges carry an extension's auth-param, such as RFC 9728's resource_metadata. Section 3 allows them: "Additional authentication parameters MAY be used". A parameter with the name of one this package sets (realm, scope, error or error_description), or of one already added, is left out of the challenges.
(Challenge) WithScope source
func (c Challenge) WithScope(s Scope, more ...Scope) ChallengeWithScope returns a copy of c whose challenges list the scopes a token needs to access the resource.
Description source
type Description stringDescription is a human-readable explanation of an error, for developers rather than end users. The empty Description is left out. Use ParseDescription to check a string.
See RFC 6750, Section 3.
ParseDescription source
func ParseDescription(s string) (Description, error)ParseDescription returns s as a Description, or ErrInvalidDescription if it has a character outside %x20-21 / %x23-5B / %x5D-7E.
Scope source
type Scope stringScope is one scope value, such as "users:write". Use ParseScope to check a string.
See RFC 6750, Section 3.
ParseScope source
func ParseScope(s string) (Scope, error)ParseScope returns s as a Scope, or ErrInvalidScope if it's empty or has a character outside %x21 / %x23-5B / %x5D-7E. A Scope is a single value, so it can't contain a space.
Token source
type Token struct {
// contains filtered or unexported fields
}Token is a bearer access token. Its String method doesn't reveal the token, so logging a Token by accident doesn't leak it. Use Token.Reveal to get the value.
ParseAuthorization source
func ParseAuthorization(value string) (Token, error)ParseAuthorization returns the bearer token in an Authorization field value, which has the form "Bearer" 1*SP b64token (Section 2.1). The scheme is matched case-insensitively. It returns ErrNoToken if the value is empty or uses another scheme, and ErrInvalidToken if the credentials aren't a b64token.
(Token) GoString source
func (t Token) GoString() stringGoString returns a placeholder, not the token, so %#v doesn't reveal it either.
(Token) Reveal source
func (t Token) Reveal() stringReveal returns the token's value.
(Token) String source
func (t Token) String() stringString returns a placeholder, not the token.