Documentation ¶
Overview ¶
This package provides a set of tools to help you implement IndieAuth, both as a Server or as a Client, according to the spec: https://indieauth.spec.indieweb.org
Index ¶
- Constants
- Variables
- func CanonicalizeURL(urlStr string) string
- func IsValidClientIdentifier(identifier string) error
- func IsValidCodeChallengeMethod(ccm string) bool
- func IsValidProfileURL(profile string) error
- func ValidateCodeChallenge(ccm, cc, ver string) bool
- type AuthInfo
- type AuthenticationRequest
- type Client
- func (c *Client) Authenticate(profile, scope string) (*AuthInfo, string, error)
- func (s *Client) DiscoverEndpoint(urlStr, rel string) (string, error)
- func (s *Client) DiscoverEndpoints(urlStr string) (*Endpoints, error)
- func (c *Client) FetchProfile(i *AuthInfo, code string) (*Profile, error)
- func (c *Client) GetOAuth2(e *Endpoints) *oauth2.Config
- func (c *Client) GetToken(i *AuthInfo, code string) (*oauth2.Token, *oauth2.Config, error)
- func (c *Client) ValidateCallback(i *AuthInfo, r *http.Request) (string, error)
- type Endpoints
- type Profile
- type Server
Examples ¶
Constants ¶
const ( AuthorizationEndpointRel string = "authorization_endpoint" TokenEndpointRel string = "token_endpoint" )
Variables ¶
var ( ErrCodeNotFound error = errors.New("code not found") ErrStateNotFound error = errors.New("state not found") ErrInvalidState error = errors.New("state does not match") )
var ( ErrInvalidCodeChallengeMethod error = errors.New("invalid code challenge method") ErrInvalidGrantType error = errors.New("grant_type must be authorization_code") ErrNoMatchClientID error = errors.New("client_id differs") ErrNoMatchRedirectURI error = errors.New("redirect_uri differs") ErrPKCERequired error = errors.New("pkce is required, not provided") ErrCodeChallengeFailed error = errors.New("code challenge failed") ErrInvalidResponseType error = errors.New("response_type must be code") ErrWrongCodeChallengeLenght error = errors.New("code_challenge length must be between 43 and 128 charachters long") )
var ( ErrInvalidScheme error = errors.New("scheme must be either http or https") ErrEmptyPath error = errors.New("path must not be empty") ErrInvalidPath error = errors.New("path cannot contain single or double dots") ErrInvalidFragment error = errors.New("fragment must be empty") ErrUserIsSet error = errors.New("user and or password must not be set") ErrPortIsSet error = errors.New("port must not be set") ErrIsIP error = errors.New("profile cannot be ip address") ErrIsNonLoopback error = errors.New("client id cannot be non-loopback ip") )
var CodeChallengeMethods = []string{
"plain", "S256",
}
CodeChallengeMethods are the code challenge methods that are supported by this package.
var ErrNoEndpointFound = fmt.Errorf("no endpoint found")
ErrNoEndpointFound is returned when no endpoint can be found for a certain target URL.
Functions ¶
func CanonicalizeURL ¶
CanonicalizeURL checks if a URL has a path, and appends a path "/"" if it has no path.
Example ¶
fmt.Println(CanonicalizeURL("example.com"))
Output: https://example.com/
func IsValidClientIdentifier ¶
IsValidClientIdentifier validates a client identifier according to the specification. https://indieauth.spec.indieweb.org/#client-identifier
func IsValidCodeChallengeMethod ¶
IsValidCodeChallengeMethod returns whether the provided code challenge method is or is not valid.
func IsValidProfileURL ¶
IsValidProfileURL validates the profile URL according to the specification. https://indieauth.spec.indieweb.org/#user-profile-url
func ValidateCodeChallenge ¶
ValidateCodeChallenge validates a code challenge against it code verifier. Right now, we support "plain" and "S256" code challenge methods.
Types ¶
type AuthenticationRequest ¶
type Client ¶
Client is a IndieAuth client. As a client, you want to authenticate other users to log into onto your website.
First, create a client with the correct client ID and callback URL.
client := NewClient("https://example.com/", "https://example.com/callback", nil)
Then, obtain the user's profile URL via some method, such as an HTTP form. Optionally, canonicalize the value (see CanonicalizeURL for more information).
profile = CanonicalizeURL(profile)
Then, validate the profile URL according to the specification.
err = IsValidProfileURL(profile) if err != nil { // Do something }
Obtain the authentication information and redirect URL:
authData, redirect, err := client.Authenticate(profile, "the scopes you need") if err != nil { // Do something }
The client should now store authData because it will be necessary to verify the callback. You can store it, for example, in a database or cookie. Then, redirect the user:
http.Redirect(w, r, redirect, http.StatusSeeOther)
In the callback handler, you should obtain authData according to the method you defined. Then, call ValidateCallback to obtain the code:
code, err := client.ValidateCallback(authData, r) if err != nil { // Do something }
Now that you have the code, you have to redeem it. You can either use FetchProfile to redeem it by the users' profile or GetToken.
func NewClient ¶ added in v0.1.3
NewClient creates a new Client from the provided clientID and redirectURL. If no httpClient is given, http.DefaultClient will be used.
func (*Client) Authenticate ¶
Authenticate takes a profile URL and the desired scope, discovers the required endpoints, generates a random scope and code challenge (using method SHA256), and builds the authorization URL. It returns the authorization info, redirect URI and an error.
The returned AuthInfo should be stored by the caller of this function in such a way that it can be retrieved to validate the callback.
func (*Client) DiscoverEndpoint ¶ added in v0.1.7
DiscoverEndpoint discovers as given endpoint identified by rel.
func (*Client) DiscoverEndpoints ¶
DiscoverEndpoints discovers the authorization and token endpoints for the provided URL. This code is partially based on https://github.com/willnorris/webmention/blob/main/webmention.go.
func (*Client) FetchProfile ¶
FetchProfile fetches the user profile, exchanging the authentication code from their authentication endpoint, as described in the link below. Please note that this action consumes the code.
func (*Client) GetOAuth2 ¶ added in v0.1.6
GetOAuth2 returns an oauth2.Config based on the given endpoints. This can be used to get an http.Client See https://pkg.go.dev/golang.org/x/oauth2 for more details.
func (*Client) GetToken ¶
GetToken exchanges the code for an oauth2.Token based on the provided information. It returns the token and an oauth2.Config object which can be used to create an http client that uses the token on future requests.
Note that token.Raw may contain other information returned by the server, such as "Me", "Profile" and "Scope".
token, oauth2, err := client.GetToken(authData, code) if err != nil { // Do something } httpClient := oauth2.Client(context.Background(), token)
You can now use httpClient to make requests to, for example, a Micropub endpoint. They are authenticated with token. See https://pkg.go.dev/golang.org/x/oauth2 for more details.
type Profile ¶
type Profile struct { Me string `json:"me"` Profile struct { Name string `json:"name"` URL string `json:"url"` Photo string `json:"photo"` Email string `json:"email"` } `json:"profile"` }
func ProfileFromToken ¶
ProfileFromToken retrieves the extra information from the token and creates a profile based on it. Note that the profile may be nil in case no information can be retrieved.
type Server ¶
func NewServer ¶ added in v0.1.5
NewServer creates a new Server that from the given options. If no httpClient is given, http.DefaultClient will be used.
func (*Server) ParseAuthorization ¶
func (s *Server) ParseAuthorization(r *http.Request) (*AuthenticationRequest, error)
ParseAuthorization parses an authorization request and returns all the collected information about the request.
func (*Server) ValidateTokenExchange ¶ added in v0.1.3
func (s *Server) ValidateTokenExchange(authRequest *AuthenticationRequest, r *http.Request) error
ValidateTokenExchange validates the token exchange request according to the provided authentication request and returns an error.
Please note that you need to fetch the authentication code yourself from the request.
_ = r.ParseForm() code := r.Form.Get("code")
The code was provided by you at a previous stage. Thus, you will need to use it to rebuild the AuthenticationRequest data. The AuthenticationRequest does not need to have the scope or state set for this validation.