Package oauth21draft
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
- Variables
func MatchRedirectURItype AuthorizationErrorResponsetype AuthorizationResponsetype Descriptiontype ErrorCodetype Formtype Scopetype TokenErrorResponsetype TokenResponse
Constants
const GrantTypeAuthorizationCode = "authorization_code"GrantTypeAuthorizationCode is the grant_type of a token request that redeems an authorization code (Section 4.1.3).
const ResponseTypeCode = "code"ResponseTypeCode is the response_type of an authorization code request, the only one this specification defines (Section 4.1.1).
const TokenTypeBearer = "Bearer"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")
)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")
)Functions
MatchRedirectURI source
func MatchRedirectURI(registered, requested string) boolMatchRedirectURI 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 stringDescription 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 stringErrorCode 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.
ErrorCode = "temporarily_unavailable"
)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) []stringAll returns every value of a repeatable parameter.
(Form) Get source
func (f Form) Get(name string) stringGet 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) boolHas 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) ScopeMustScope 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) boolContains reports whether s includes the token. Tokens are case-sensitive (Section 1.4.1).
(Scope) Equal source
func (s Scope) Equal(other Scope) boolEqual reports whether s and other have the same tokens, in any order.
(Scope) IsEmpty source
func (s Scope) IsEmpty() boolIsEmpty reports whether s has no tokens.
(Scope) String source
func (s Scope) String() stringString returns the scope as sent: its tokens separated by spaces.
(Scope) Subset source
func (s Scope) Subset(other Scope) boolSubset reports whether every token of s is in other.
(Scope) Tokens source
func (s Scope) Tokens() []stringTokens 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) TokenErrorResponseNewTokenErrorResponse 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() ErrorCodeCode returns the error code.
(TokenErrorResponse) Status source
func (e TokenErrorResponse) Status() rfc9110.StatusStatus 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) TokenErrorResponseWithChallenge 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) TokenErrorResponseWithDescription 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) errorWrite 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) TokenResponseNewTokenResponse 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) TokenResponseWithExpiresIn 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) TokenResponseWithScope 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) errorWrite 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.