Documentation
¶
Overview ¶
Package oidc makes a Tern app an OAuth 2.0 authorization server and OpenID Connect provider: other apps (or your own frontends, mobile apps and services) sign users in with it, and your APIs accept its tokens.
provider := oidc.New(oidc.Options{
Issuer: "https://auth.example.com",
Clients: []oidc.Client{{
ID: "wiki", Secret: os.Getenv("WIKI_CLIENT_SECRET"), Name: "Team wiki",
RedirectURIs: []string{"https://wiki.example.com/auth/example/callback"},
}},
Store: store, // oidc.NewSQLStore(db)
Authenticate: func(ctx *kit.Ctx) (string, error) {
return sessions.Get(ctx).GetString("user"), nil // "" when signed out
},
LoginURL: "/login",
Claims: func(ctx context.Context, subject string, scopes []string) (map[string]any, error) {
u, err := users.Get(ctx, subject)
return map[string]any{"email": u.Email, "email_verified": true, "name": u.Name}, err
},
})
app, err := ternApp(kit.WithPlugins(sessions.New(secret), provider))
It serves discovery (/.well-known/openid-configuration), keys (/oauth/jwks), and the authorize, token, userinfo, revocation and introspection endpoints under /oauth. Supported: the authorization code flow with PKCE (required for public clients), refresh tokens with rotation and reuse detection, and the client credentials grant. Access and ID tokens are RS256-signed JWTs.
Index ¶
- Variables
- type AccessToken
- type AuthCode
- type Client
- type MemoryStore
- func (s *MemoryStore) AccessRevoked(_ context.Context, jti string) (bool, error)
- func (s *MemoryStore) Consent(_ context.Context, sub, client string) ([]string, error)
- func (s *MemoryStore) GetRefresh(_ context.Context, h string) (RefreshToken, error)
- func (s *MemoryStore) RevokeAccess(_ context.Context, jti string, exp time.Time) error
- func (s *MemoryStore) RevokeFamily(_ context.Context, family string) error
- func (s *MemoryStore) RevokeRefresh(_ context.Context, h string) error
- func (s *MemoryStore) SaveCode(_ context.Context, h string, c AuthCode) error
- func (s *MemoryStore) SaveConsent(_ context.Context, sub, client string, scopes []string) error
- func (s *MemoryStore) SaveRefresh(_ context.Context, h string, t RefreshToken) error
- func (s *MemoryStore) SaveSigningKey(_ context.Context, pem []byte) error
- func (s *MemoryStore) SigningKey(context.Context) ([]byte, error)
- func (s *MemoryStore) TakeCode(_ context.Context, h string) (AuthCode, error)
- func (s *MemoryStore) UseRefresh(_ context.Context, h string) (bool, error)
- type Options
- type Plugin
- type RefreshToken
- type SQLStore
- func (s *SQLStore) AccessRevoked(ctx context.Context, jti string) (bool, error)
- func (s *SQLStore) Consent(ctx context.Context, sub, client string) ([]string, error)
- func (s *SQLStore) GetRefresh(ctx context.Context, h string) (RefreshToken, error)
- func (s *SQLStore) RevokeAccess(ctx context.Context, jti string, exp time.Time) error
- func (s *SQLStore) RevokeFamily(ctx context.Context, family string) error
- func (s *SQLStore) RevokeRefresh(ctx context.Context, h string) error
- func (s *SQLStore) SaveCode(ctx context.Context, h string, c AuthCode) error
- func (s *SQLStore) SaveConsent(ctx context.Context, sub, client string, scopes []string) error
- func (s *SQLStore) SaveRefresh(ctx context.Context, h string, t RefreshToken) error
- func (s *SQLStore) SaveSigningKey(ctx context.Context, pem []byte) error
- func (s *SQLStore) SigningKey(ctx context.Context) ([]byte, error)
- func (s *SQLStore) TakeCode(ctx context.Context, h string) (AuthCode, error)
- func (s *SQLStore) UseRefresh(ctx context.Context, h string) (bool, error)
- type Store
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidToken = errors.New("oidc: invalid access token")
ErrInvalidToken is returned for missing, malformed, expired or revoked tokens.
var ErrNotFound = errors.New("oidc: not found")
ErrNotFound is returned by a Store for missing or expired entries.
Functions ¶
This section is empty.
Types ¶
type AccessToken ¶
type AccessToken struct {
Subject string
ClientID string
Scopes []string
Expires time.Time
ID string
}
AccessToken is a verified access token.
func (*AccessToken) HasScope ¶
func (t *AccessToken) HasScope(scope string) bool
HasScope reports whether the token was granted scope.
type AuthCode ¶
type AuthCode struct {
ClientID string `json:"clientId"`
RedirectURI string `json:"redirectUri"`
Subject string `json:"subject"`
Scopes []string `json:"scopes"`
Nonce string `json:"nonce,omitempty"`
CodeChallenge string `json:"codeChallenge,omitempty"`
AuthTime time.Time `json:"authTime"`
Expires time.Time `json:"expires"`
}
AuthCode is an issued authorization code.
type Client ¶
type Client struct {
ID string
// Secret authenticates confidential clients (server-side apps). Leave
// it empty and set Public for apps that can't keep a secret, such as
// single-page and mobile apps; they must use PKCE.
Secret string
Public bool
// Name is shown on the consent screen.
Name string
// RedirectURIs are the exact URLs codes may be sent to.
RedirectURIs []string
// Trusted clients, such as your own apps, skip the consent screen.
Trusted bool
// GrantTypes the client may use; the default is authorization_code and
// refresh_token. Add client_credentials for service accounts.
GrantTypes []string
// Scopes the client may request; the default is every supported scope.
Scopes []string
}
Client is an application allowed to use the provider.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is a Store in memory, for development and tests.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore returns an empty MemoryStore.
func (*MemoryStore) AccessRevoked ¶
AccessRevoked implements Store.
func (*MemoryStore) GetRefresh ¶
func (s *MemoryStore) GetRefresh(_ context.Context, h string) (RefreshToken, error)
GetRefresh implements Store.
func (*MemoryStore) RevokeAccess ¶
RevokeAccess implements Store.
func (*MemoryStore) RevokeFamily ¶
func (s *MemoryStore) RevokeFamily(_ context.Context, family string) error
RevokeFamily implements Store.
func (*MemoryStore) RevokeRefresh ¶
func (s *MemoryStore) RevokeRefresh(_ context.Context, h string) error
RevokeRefresh implements Store.
func (*MemoryStore) SaveConsent ¶
SaveConsent implements Store.
func (*MemoryStore) SaveRefresh ¶
func (s *MemoryStore) SaveRefresh(_ context.Context, h string, t RefreshToken) error
SaveRefresh implements Store.
func (*MemoryStore) SaveSigningKey ¶
func (s *MemoryStore) SaveSigningKey(_ context.Context, pem []byte) error
SaveSigningKey implements Store.
func (*MemoryStore) SigningKey ¶
func (s *MemoryStore) SigningKey(context.Context) ([]byte, error)
SigningKey implements Store.
func (*MemoryStore) UseRefresh ¶
UseRefresh implements Store.
type Options ¶
type Options struct {
// Issuer is the provider's public URL, such as
// "https://auth.example.com", without a trailing slash. Tokens carry
// it, and clients check it.
Issuer string
Clients []Client
// Authenticate returns the signed-in user's subject (a stable ID), or
// "" when nobody is signed in.
Authenticate func(ctx *kit.Ctx) (string, error)
// LoginURL is where signed-out users go, with ?next=<the authorize
// URL> to come back to.
LoginURL string
// Claims returns the user's claims for ID tokens and userinfo, such as
// email, email_verified, name and picture. scopes lets you leave out
// what wasn't asked for. It may be nil.
Claims func(ctx context.Context, subject string, scopes []string) (map[string]any, error)
// Store keeps codes, refresh tokens, consents and the signing key. The
// default is a MemoryStore, which forgets everything on restart.
Store Store
// Scopes the provider supports besides openid, profile, email and
// offline_access, such as "api:read".
Scopes []string
// Path is where the endpoints are; the default is "/oauth".
Path string
// Lifetimes; the defaults are an hour for access and ID tokens, 30
// days for refresh tokens and a minute for codes.
AccessTokenTTL, IDTokenTTL, RefreshTokenTTL, CodeTTL time.Duration
// SigningKey is an RSA key in PEM. Without it, a key is created and
// kept in the Store.
SigningKey []byte
}
Options configure the provider.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is the provider.
func (*Plugin) Bearer ¶
Bearer authenticates an API request by its "Authorization: Bearer" access token, and checks it has every scope given. It answers 401 or 403 errors with the WWW-Authenticate header clients expect (RFC 6750):
func GET(ctx *kit.Ctx) error {
token, err := provider.Bearer(ctx, "api:read")
if err != nil {
return err
}
return ctx.JSON(200, projectsOf(token.Subject))
}
type RefreshToken ¶
type RefreshToken struct {
ClientID string `json:"clientId"`
Subject string `json:"subject"`
Scopes []string `json:"scopes"`
Family string `json:"family"`
AuthTime time.Time `json:"authTime"`
Expires time.Time `json:"expires"`
Used bool `json:"used"`
}
RefreshToken is an issued refresh token. Tokens refreshed from one another share a Family; using an old one again revokes the family.
type SQLStore ¶
type SQLStore struct {
// contains filtered or unexported fields
}
SQLStore keeps the provider's state in SQLite or PostgreSQL. It creates its tables (prefixed tern_oidc_) on first use.
func NewSQLStore ¶
NewSQLStore returns a store in db.
func (*SQLStore) AccessRevoked ¶
AccessRevoked implements Store.
func (*SQLStore) GetRefresh ¶
GetRefresh implements Store.
func (*SQLStore) RevokeAccess ¶
RevokeAccess implements Store.
func (*SQLStore) RevokeFamily ¶
RevokeFamily implements Store.
func (*SQLStore) RevokeRefresh ¶
RevokeRefresh implements Store.
func (*SQLStore) SaveConsent ¶
SaveConsent implements Store.
func (*SQLStore) SaveRefresh ¶
SaveRefresh implements Store.
func (*SQLStore) SaveSigningKey ¶
SaveSigningKey implements Store.
func (*SQLStore) SigningKey ¶
SigningKey implements Store.
type Store ¶
type Store interface {
SaveCode(ctx context.Context, hash string, c AuthCode) error
// TakeCode returns and deletes a code, so it can only be used once.
TakeCode(ctx context.Context, hash string) (AuthCode, error)
SaveRefresh(ctx context.Context, hash string, t RefreshToken) error
GetRefresh(ctx context.Context, hash string) (RefreshToken, error)
// UseRefresh marks a token used and reports whether it was unused.
UseRefresh(ctx context.Context, hash string) (bool, error)
RevokeRefresh(ctx context.Context, hash string) error
RevokeFamily(ctx context.Context, family string) error
// RevokeAccess records a revoked access token until it expires.
RevokeAccess(ctx context.Context, jti string, expires time.Time) error
AccessRevoked(ctx context.Context, jti string) (bool, error)
SaveConsent(ctx context.Context, subject, clientID string, scopes []string) error
Consent(ctx context.Context, subject, clientID string) ([]string, error)
// SigningKey returns the stored signing key (PEM), or ErrNotFound.
SigningKey(ctx context.Context) ([]byte, error)
SaveSigningKey(ctx context.Context, pem []byte) error
}
Store keeps the provider's state. Codes and tokens are passed hashed, so a leaked store does not leak usable tokens. Use NewSQLStore in production; MemoryStore forgets everything on restart.