Documentation
¶
Overview ¶
Package sociable authenticates people with Google, GitHub, GitLab, Facebook, LinkedIn, Bitbucket, Slack, Twitch, X, or any OAuth 2 or OpenID Connect provider, on net/http. It hands back who they are and a token to use.
sociable.Configure(sociable.Config{
Key: os.Getenv("APP_KEY"),
Drivers: sociable.Drivers{
"github": {ClientID: id, ClientSecret: secret, Redirect: "/auth/github/callback"},
},
})
sociable.Driver("github").Redirect(w, r)
user, err := sociable.Driver("github").User(w, r)
Index ¶
- Variables
- func Bool(raw map[string]any, path string) bool
- func Configure(config Config)
- func Extend(name string, factory Factory)
- func GetJSON(ctx context.Context, client *http.Client, url string, into any) error
- func String(raw map[string]any, path string) string
- type Authenticator
- type AuthorizationError
- type Config
- type Credentials
- type Drivers
- type Factory
- type Faked
- func (self *Faked) AssertNotRedirected(t testing.TB, conditions ...func(Redirect) bool)
- func (self *Faked) AssertRedirected(t testing.TB, conditions ...func(Redirect) bool)
- func (self *Faked) AssertRedirectedCount(t testing.TB, count int)
- func (self *Faked) Redirects() []Redirect
- func (self *Faked) Restore()
- type Flow
- func (self Flow) AuthURL(w http.ResponseWriter, r *http.Request) (string, error)
- func (self Flow) Client(ctx context.Context, token *oauth2.Token) *http.Client
- func (self Flow) Redirect(w http.ResponseWriter, r *http.Request) error
- func (self Flow) RedirectURL(url string) Flow
- func (self Flow) RefreshToken(ctx context.Context, refreshToken string) (*oauth2.Token, error)
- func (self Flow) Scopes(scopes ...string) Flow
- func (self Flow) SetScopes(scopes ...string) Flow
- func (self Flow) Stateless() Flow
- func (self Flow) User(w http.ResponseWriter, r *http.Request) (*User, error)
- func (self Flow) UserFromToken(ctx context.Context, token *oauth2.Token) (*User, error)
- func (self Flow) With(params map[string]string) Flow
- func (self Flow) WithState(state map[string]string) Flow
- type Manager
- type OAuth2
- type Provider
- func Bitbucket(credentials Credentials) Provider
- func Facebook(credentials Credentials) Provider
- func GitHub(credentials Credentials) Provider
- func GitLab(credentials Credentials) Provider
- func Google(credentials Credentials) Provider
- func LinkedIn(credentials Credentials) Provider
- func OpenID(credentials Credentials, issuer string) Provider
- func Slack(credentials Credentials) Provider
- func Twitch(credentials Credentials) Provider
- func X(credentials Credentials) Provider
- type Redirect
- type Redirector
- type Session
- type Unprotected
- type User
Constants ¶
This section is empty.
Variables ¶
var ( // ErrUnknownDriver means no driver is configured under that name. ErrUnknownDriver = errors.New("sociable: unknown driver") // ErrNotConfigured means the driver has no credentials or no endpoints, // because discovery failed. Refuse the route rather than redirect. ErrNotConfigured = errors.New("sociable: driver not configured") // ErrInvalidState means the callback was not started here, was already // used, expired, or came back with another state. ErrInvalidState = errors.New("sociable: invalid state") // ErrAuthorization is what every provider refusal matches. ErrAuthorization = errors.New("sociable: the provider refused") // ErrAccessDenied is the refusal that means the person said no. ErrAccessDenied = fmt.Errorf("%w: access denied", ErrAuthorization) // ErrExchange means the code could not be exchanged for a token, or the // token is not usable. ErrExchange = errors.New("sociable: token exchange failed") // ErrIDToken means the ID token failed verification: signature, issuer, // audience, expiry or nonce. ErrIDToken = errors.New("sociable: the id token could not be verified") // ErrProfile means the provider would not describe the person. ErrProfile = errors.New("sociable: could not read the profile") )
var ErrNoKey = errors.New("sociable: a Key or a Session is required")
ErrNoKey is returned by New when there is nowhere to keep a handshake.
var ErrSealed = errors.New("sociable: the cookie cannot be opened")
ErrSealed means a cookie that was forged, truncated or issued under another key. The flow treats it as no handshake.
Functions ¶
func Configure ¶
func Configure(config Config)
Configure sets up the default manager. It panics on a configuration that cannot work, because that is a mistake at boot rather than at runtime.
func Extend ¶
Extend registers a provider under a name on the default manager. The factory receives the credentials configured under that name.
Types ¶
type Authenticator ¶
Authenticator is a provider that reads the person from the callback itself, with no code to exchange.
type AuthorizationError ¶
AuthorizationError is a refusal the provider sent back on the redirect, in the shape RFC 6749 section 4.1.2.1 defines.
func (*AuthorizationError) Error ¶
func (self *AuthorizationError) Error() string
func (*AuthorizationError) Is ¶
func (self *AuthorizationError) Is(target error) bool
Is makes every refusal match ErrAuthorization, and access_denied match ErrAccessDenied as well.
type Config ¶
type Config struct {
// Key encrypts the cookie that carries the handshake between the redirect
// and the callback. Any string; required unless Session is set.
Key string
// URL is the application's own, for resolving a relative Redirect.
URL string
Drivers Drivers
// Session holds the handshake instead of the cookie, for an application
// that has a session already.
Session Session
// Client talks to the providers. nil is http.DefaultClient.
Client *http.Client
}
Config is set once at boot.
type Credentials ¶
type Credentials struct {
ClientID string
ClientSecret string
// Redirect is the callback URL the provider sends the browser back to. A
// path is resolved against Config.URL.
Redirect string
// Scopes replace the driver's defaults.
Scopes []string
}
Credentials are what a provider issued the application.
type Drivers ¶
type Drivers map[string]Credentials
Drivers maps a driver name to its credentials. The name picks the built-in provider of that name, or one registered with Extend.
type Factory ¶
type Factory func(credentials Credentials) Provider
Factory builds a Provider from the credentials configured under its name.
type Faked ¶
type Faked struct {
// contains filtered or unexported fields
}
Faked is a faked driver: the user it answers with, and every redirect it was asked for, so a test can check what the handler sent the person off with.
func Fake ¶
Fake replaces a driver on the default manager with one that returns the given user at the callback, for tests. Nothing needs configuring, and any field left blank gets a test value, so Fake("github", User{}) is a whole person. What comes back records every redirect, for asserting on.
func (*Faked) AssertNotRedirected ¶
AssertNotRedirected fails the test if a redirect happened that every condition accepts. With no conditions, any redirect fails it.
func (*Faked) AssertRedirected ¶
AssertRedirected fails the test unless a redirect happened that every condition accepts.
func (*Faked) AssertRedirectedCount ¶
AssertRedirectedCount fails the test unless exactly count redirects happened.
type Flow ¶
type Flow struct {
// contains filtered or unexported fields
}
Flow is a driver bound to one request. It is a value: every method that shapes it returns a changed copy, so nothing leaks into the next request.
func (Flow) AuthURL ¶
AuthURL is Redirect without the redirecting, for a handler that sends the browser itself. It still starts the handshake for this browser.
func (Flow) Client ¶
Client is an HTTP client that presents the token to the provider's API and refreshes it when it can.
func (Flow) RedirectURL ¶
RedirectURL overrides the callback for this request.
func (Flow) RefreshToken ¶
RefreshToken trades a refresh token for a live token. Persist what comes back: it may carry a new refresh token, and the old one may be retired.
func (Flow) Stateless ¶
Stateless skips the handshake, for a callback this server did not start. Nothing is kept between the two steps, so PKCE is off and WithState has nowhere to go.
func (Flow) User ¶
User finishes the flow at the callback and returns the person the provider vouches for, with their token.
func (Flow) UserFromToken ¶
UserFromToken asks the provider about a token the application already holds.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager is a configured set of drivers. Configure sets up the default one; New makes another, for tests or a multi-tenant application.
func (*Manager) Driver ¶
Driver picks a configured driver. An unknown name is not an error here but on every call that follows, so a handler has one place to check.
type OAuth2 ¶
type OAuth2 struct {
Credentials Credentials
Endpoint oauth2.Endpoint
// Scopes are the defaults, for credentials that name none.
Scopes []string
// ProfileURL is fetched with the token, and Map turns the document into a
// User. Raw is filled in when Map leaves it empty, or when there is no
// Map at all.
ProfileURL string
Map func(raw map[string]any) User
WithoutPKCE bool
}
OAuth2 is a provider described by its endpoints and a profile mapping, which is all most providers need.
type Provider ¶
type Provider interface {
Config() *oauth2.Config
User(ctx context.Context, client *http.Client, token *oauth2.Token) (*User, error)
}
Provider is what a driver is made of: the oauth2 configuration it was issued, and how to read the person behind a token. The flow around those two things is this package's.
The client already presents the token, so a provider reads its profile with a plain Get.
func Bitbucket ¶
func Bitbucket(credentials Credentials) Provider
Bitbucket reads the user document and the primary address from the emails endpoint, which is the only place Bitbucket puts it.
func Facebook ¶
func Facebook(credentials Credentials) Provider
Facebook reads the Graph API's me document.
func GitHub ¶
func GitHub(credentials Credentials) Provider
GitHub reads the profile and, when the profile hides the address, the primary verified one from the emails endpoint.
func GitLab ¶
func GitLab(credentials Credentials) Provider
GitLab signs in against gitlab.com. For a self-hosted instance, Extend with an OAuth2 that points at it.
func Google ¶
func Google(credentials Credentials) Provider
Google signs in through OpenID Connect. For a refresh token, redirect with With(map[string]string{"access_type": "offline", "prompt": "consent"}).
func LinkedIn ¶
func LinkedIn(credentials Credentials) Provider
LinkedIn signs in through OpenID Connect.
func OpenID ¶
func OpenID(credentials Credentials, issuer string) Provider
OpenID is a provider for any OpenID Connect issuer. Endpoints and signing keys come from discovery, and the identity from an ID token verified for its signature, issuer, audience, expiry and nonce.
sociable.Extend("okta", func(c sociable.Credentials) sociable.Provider {
return sociable.OpenID(c, "https://acme.okta.com")
})
func Twitch ¶
func Twitch(credentials Credentials) Provider
Twitch reads the Helix users document, which needs the client id on the request as well as the token.
type Redirect ¶
type Redirect struct {
Scopes []string
Params map[string]string
State map[string]string
RedirectURL string
}
Redirect is one call to Redirect or AuthURL on a faked driver, as the handler shaped it.
type Redirector ¶
type Redirector interface {
Redirect(w http.ResponseWriter, r *http.Request, callback, state string) error
}
Redirector is a provider that sends the browser itself, for a sign-in that is not an authorization URL: a widget, a form. It receives the callback URL and the state the callback must bring back.
type Session ¶
type Session interface {
Put(w http.ResponseWriter, r *http.Request, key, value string) error
Pull(w http.ResponseWriter, r *http.Request, key string) (string, error)
}
Session keeps the handshake between the redirect and the callback. The default is an encrypted cookie; an application with a session of its own hands that over instead.
The value is the handshake as plain JSON, PKCE verifier included, so a Session must keep it server-side or encrypt it. It must also be bound to the browser that started the flow, which is what makes the state check mean anything.
type Unprotected ¶
type Unprotected interface {
UsesPKCE() bool
}
Unprotected is a provider that cannot do PKCE. Every provider that stays silent does it.
type User ¶
type User struct {
// ID is the provider's stable identifier for this person. Store this,
// not the email, which can change.
ID string
Nickname string
Name string
Email string
EmailVerified bool
Avatar string
// Raw is the provider's own document: the profile, or the ID token's
// claims.
Raw map[string]any
// Token carries the access token, the refresh token when the provider
// issued one, and the expiry.
Token *oauth2.Token
// ApprovedScopes are the scopes the provider granted, which need not be
// the ones that were asked for.
ApprovedScopes []string
// State is what the call site put in with WithState.
State map[string]string
}
User is who the provider vouches for.