Package oauth21draft

import "funnyhaha.technology/rfc/oauth21draft"
View the source code

Overview

Package oauth21draft implements the parts of The OAuth 2.1 Authorization Framework that an authorization server's endpoints share: reading request parameters, scopes, redirect URI matching, and the success and error responses of the authorization and token endpoints.

OAuth 2.1 is an IETF draft, draft-ietf-oauth-v2-1. This package implements revision -16, whose text is in draft-ietf-oauth-v2-1-16.txt in this directory, and the doc comments cite its sections. It tracks the draft's latest revision, and moving to a new one is a single change reviewed against the difference between the texts. When the draft is published as an RFC, a package named after that RFC replaces this one.

At the authorization endpoint, check the parameters, then redirect the result, or an error once the client and redirect URI are known to be valid:

form, err := oauth21draft.ParseForm(r.URL.Query(), rfc8707.Param)
// ...
oauth21draft.AuthorizationResponse{Code: code, State: form.Get("state"), Issuer: issuer}.
	Redirect(w, redirectURI)

At the token endpoint, send a token or an error, both with Cache-Control: no-store:

oauth21draft.NewTokenResponse(token, oauth21draft.TokenTypeBearer).
	WithExpiresIn(ttl).
	Write(w)
oauth21draft.NewTokenErrorResponse(oauth21draft.InvalidGrant).Write(w)

The package covers the authorization code grant. It doesn't cover client authentication, refresh tokens, the client credentials grant, or error_uri.

Index

Constants

const GrantTypeAuthorizationCode = "authorization_code"
source

GrantTypeAuthorizationCode is the grant_type of a token request that redeems an authorization code (Section 4.1.3).

const ResponseTypeCode = "code"
source

ResponseTypeCode is the response_type of an authorization code request, the only one this specification defines (Section 4.1.1).

const TokenTypeBearer = "Bearer"
source

TokenTypeBearer is the token_type of a bearer token (Section 1.4.2).

Variables

var (
	// ErrInvalidErrorCode is returned by [ParseErrorCode] for a code that's
	// empty or has characters outside %x20-21 / %x23-5B / %x5D-7E.
	ErrInvalidErrorCode = errors.New("invalid error code")

	// ErrInvalidDescription is returned by [ParseDescription] for text with
	// characters outside %x20-21 / %x23-5B / %x5D-7E.
	ErrInvalidDescription = errors.New("invalid error description")
)
source
var (
	// ErrRepeatedParameter is returned by [ParseForm] for a parameter sent
	// more than once.
	ErrRepeatedParameter = errors.New("repeated parameter")

	// ErrInvalidScope is returned by [ParseScope] for a scope that isn't a
	// space-delimited list of scope tokens.
	ErrInvalidScope = errors.New("invalid scope")
)
source

Functions

MatchRedirectURI source

func MatchRedirectURI(registered, requested string) bool

MatchRedirectURI reports whether the redirect URI in an authorization request matches one the client registered. They must be identical (Section 2.3.1), except that a loopback redirect URI may use any port, since a native app listens on whatever port the operating system gives it (Section 8.4.2).

A loopback redirect URI is http with the host 127.0.0.1, [::1] or localhost. Section 8.4.2 only requires the port exception for the IP literals, and doesn't recommend localhost, but MCP clients use localhost in practice.

Types

AuthorizationErrorResponse source

type AuthorizationErrorResponse struct {
	Code        ErrorCode
	Description Description

	// State is the state the client sent, or "" if it sent none.
	State string

	// Issuer is the authorization server's issuer identifier, which
	// Section 4.1.2.1 requires.
	Issuer string
}

AuthorizationErrorResponse is an error response sent to the client's redirect URI. Send it with AuthorizationErrorResponse.Redirect, and only once the client identifier and redirect URI have been validated. Otherwise, show the error to the user instead (Section 4.1.2.1).

(AuthorizationErrorResponse) Redirect source

func (e AuthorizationErrorResponse) Redirect(w http.ResponseWriter, redirectURI string)

Redirect sends the user agent to redirectURI with error, error_description, state and iss added to its query, as AuthorizationResponse.Redirect does.

AuthorizationResponse source

type AuthorizationResponse struct {
	// Code is the authorization code. Section 4.1.2 recommends it expire
	// within 10 minutes.
	Code string

	// State is the state the client sent, or "" if it sent none.
	State string

	// Issuer is the authorization server's issuer identifier, which
	// Section 4.1.2 requires, so the client can detect mix-up attacks.
	Issuer string
}

AuthorizationResponse is a successful authorization response: the code, sent to the client's redirect URI. Send it with AuthorizationResponse.Redirect.

See Section 4.1.2.

(AuthorizationResponse) Redirect source

func (r AuthorizationResponse) Redirect(w http.ResponseWriter, redirectURI string)

Redirect sends the user agent to redirectURI with code, state and iss added to its query, keeping any query it already has (Appendix C.1). It responds with 303 See Other and Cache-Control: no-store. A 307 would make the browser resend the form it submitted, which Section 7.5.4 warns against.

Description source

type Description string

Description is the value of an "error_description" parameter: ASCII text for the client's developer. Use ParseDescription to check a string.

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 (Sections 3.2.4 and 4.1.2.1). The empty Description is left out of responses.

ErrorCode source

type ErrorCode string

ErrorCode is the value of an error response's "error" parameter. Use the constants, or ParseErrorCode for an extension's code.

const (
	// InvalidRequest: the request is missing a parameter, repeats one, has
	// an invalid value, or is otherwise malformed. Both endpoints.
	InvalidRequest ErrorCode = "invalid_request"

	// InvalidClient: client authentication failed. Token endpoint only.
	InvalidClient ErrorCode = "invalid_client"

	// InvalidGrant: the authorization code is invalid, expired, revoked,
	// doesn't match the redirect URI, or was issued to another client.
	// Token endpoint only.
	InvalidGrant ErrorCode = "invalid_grant"

	// UnauthorizedClient: the client isn't allowed this grant type or
	// response type. Both endpoints.
	UnauthorizedClient ErrorCode = "unauthorized_client"

	// UnsupportedGrantType: the server doesn't support the grant type.
	// Token endpoint only.
	UnsupportedGrantType ErrorCode = "unsupported_grant_type"

	// InvalidScope: the scope is invalid, unknown or malformed. Both
	// endpoints.
	InvalidScope ErrorCode = "invalid_scope"

	// AccessDenied: the resource owner or server denied the request.
	// Authorization endpoint only.
	AccessDenied ErrorCode = "access_denied"

	// UnsupportedResponseType: the server doesn't support the response
	// type. Authorization endpoint only.
	UnsupportedResponseType ErrorCode = "unsupported_response_type"

	// ServerError: the server failed unexpectedly. It stands in for a 500,
	// which can't be sent through a redirect. Authorization endpoint only.
	ServerError ErrorCode = "server_error"

	// TemporarilyUnavailable: the server is overloaded or down for
	// maintenance. It stands in for a 503. Authorization endpoint only.
	TemporarilyUnavailable ErrorCode = "temporarily_unavailable"
)
source

The error codes of Sections 3.2.4 (token endpoint) and 4.1.2.1 (authorization endpoint). Extensions define others, such as RFC 8707's invalid_target; use ParseErrorCode for those.

ParseErrorCode source

func ParseErrorCode(s string) (ErrorCode, error)

ParseErrorCode returns s as an ErrorCode, or ErrInvalidErrorCode if it's empty or has a character outside %x20-21 / %x23-5B / %x5D-7E (Sections 3.2.4 and 4.1.2.1).

Form source

type Form struct {
	// contains filtered or unexported fields
}

Form is the parameters of a request to the authorization or token endpoint, read by ParseForm.

ParseForm source

func ParseForm(values url.Values, repeatable ...string) (Form, error)

ParseForm checks the parameters of a request to either endpoint. Values that are empty are dropped, since "parameters sent without a value MUST be treated as if they were omitted", and a parameter sent more than once is an ErrRepeatedParameter, since request parameters "MUST NOT be included more than once" (Sections 3.1 and 3.2).

Extensions can let a parameter repeat, as RFC 8707 does for "resource"; name those in repeatable. Unrecognised parameters are kept, and the server ignores them by not asking for them.

(Form) All source

func (f Form) All(name string) []string

All returns every value of a repeatable parameter.

(Form) Get source

func (f Form) Get(name string) string

Get returns the parameter's value, or "" if it wasn't sent. For a repeatable parameter, it returns the first value.

(Form) Has source

func (f Form) Has(name string) bool

Has reports whether the parameter was sent with a value.

Scope source

type Scope struct {
	// contains filtered or unexported fields
}

Scope is a set of scope tokens, such as "read write". The zero Scope is empty, which a request sends by leaving the parameter out. Use ParseScope to check a string.

See Section 1.4.1.

MustScope source

func MustScope(s string) Scope

MustScope is like ParseScope but panics if s isn't valid. Use it while wiring a server.

ParseScope source

func ParseScope(s string) (Scope, error)

ParseScope returns s as a Scope. The empty string is the empty Scope. Otherwise s must match scope = scope-token *( SP scope-token ), where scope-token = 1*( %x21 / %x23-5B / %x5D-7E ), or ParseScope returns ErrInvalidScope. A token listed twice counts once, since "their order does not matter" (Section 1.4.1).

(Scope) Contains source

func (s Scope) Contains(token string) bool

Contains reports whether s includes the token. Tokens are case-sensitive (Section 1.4.1).

(Scope) Equal source

func (s Scope) Equal(other Scope) bool

Equal reports whether s and other have the same tokens, in any order.

(Scope) IsEmpty source

func (s Scope) IsEmpty() bool

IsEmpty reports whether s has no tokens.

(Scope) String source

func (s Scope) String() string

String returns the scope as sent: its tokens separated by spaces.

(Scope) Subset source

func (s Scope) Subset(other Scope) bool

Subset reports whether every token of s is in other.

(Scope) Tokens source

func (s Scope) Tokens() []string

Tokens returns the scope's tokens, in the order first given.

TokenErrorResponse source

type TokenErrorResponse struct {
	// contains filtered or unexported fields
}

TokenErrorResponse is an error response from the token endpoint. Build one with NewTokenErrorResponse and send it with TokenErrorResponse.Write.

See Section 3.2.4.

NewTokenErrorResponse source

func NewTokenErrorResponse(code ErrorCode) TokenErrorResponse

NewTokenErrorResponse returns a token endpoint error response with the code. It's sent with 400 Bad Request (Section 3.2.4).

(TokenErrorResponse) Code source

func (e TokenErrorResponse) Code() ErrorCode

Code returns the error code.

(TokenErrorResponse) Status source

func (e TokenErrorResponse) Status() rfc9110.Status

Status returns the status the error is sent with: 401 with its challenge if it has one, and otherwise 400. Both carry Cache-Control: no-store.

(TokenErrorResponse) WithChallenge source

func (e TokenErrorResponse) WithChallenge(c rfc9110.Challenge) TokenErrorResponse

WithChallenge returns a copy of e sent as 401 Unauthorized with the challenge in WWW-Authenticate. Section 3.2.4 requires that for invalid_client when the client authenticated with the Authorization header field, with a challenge for the scheme it used.

(TokenErrorResponse) WithDescription source

func (e TokenErrorResponse) WithDescription(d Description) TokenErrorResponse

WithDescription returns a copy of e with an error_description. A description that skipped ParseDescription and isn't valid is left out.

(TokenErrorResponse) Write source

func (e TokenErrorResponse) Write(w http.ResponseWriter) error

Write sends e as the response: its status, Cache-Control: no-store, and a JSON body with "error" and any "error_description" (Section 3.2.4 and Appendix C.3).

TokenResponse source

type TokenResponse struct {
	// contains filtered or unexported fields
}

TokenResponse is a successful response from the token endpoint. Build one with NewTokenResponse and send it with TokenResponse.Write.

See Section 3.2.3.

NewTokenResponse source

func NewTokenResponse(accessToken, tokenType string) TokenResponse

NewTokenResponse returns a response issuing the access token, of the type, such as TokenTypeBearer. Both are required (Section 3.2.3).

(TokenResponse) WithExpiresIn source

func (r TokenResponse) WithExpiresIn(d time.Duration) TokenResponse

WithExpiresIn returns a copy of r with the token's lifetime, sent in whole seconds. Section 3.2.3 recommends it.

(TokenResponse) WithScope source

func (r TokenResponse) WithScope(s Scope) TokenResponse

WithScope returns a copy of r with the token's scope. Section 3.2.3 requires it when the scope differs from the one requested, and recommends it otherwise.

(TokenResponse) Write source

func (r TokenResponse) Write(w http.ResponseWriter) error

Write sends r as the response: 200 OK, Cache-Control: no-store, which Section 3.2.3 requires of "any response containing tokens", and the JSON body.