Documentation
¶
Overview ¶
Package oidc verifies an OpenID Connect token into an identity.
v, err := oidc.New(ctx, oidc.Config{
Issuer: "https://login.example.org",
Audience: "fileshare",
})
tok, err := v.Verify(ctx, raw) // from an Authorization: Bearer header
fmt.Println(tok.Subject, tok.Username, tok.Groups)
A token is a signed statement by somebody else about who is asking. Almost all of the work is refusing the ones that are not, so the refusals are the package:
⛔ alg: none. A token that says it is unsigned is not a token; it is a JSON object somebody typed.
⛔ An HMAC algorithm against a public key. HS256 takes a shared secret, and a verifier that treats the issuer's PUBLIC key as that secret can be handed a token anybody forged with it -- the key is public. Only the signature algorithms the issuer actually published are accepted, and HMAC is never one of them here.
⛔ An issuer that merely starts with the right string. "iss" is compared whole: https://login.example.org.evil.test starts with the right thing.
⛔ A missing audience. A token minted for another service is a valid token; it is simply not addressed to this one, and a server that skips the check accepts every token the issuer ever signed.
⛔ A key fetched over cleartext HTTP, except from loopback. The JWKS is what decides every signature; over a link somebody can rewrite, so is everything else.
What it is not ¶
There is no login flow here: no redirect, no code exchange, no client secret, no cookies. This is the resource-server half -- something arrives with a token, and this says who that is. The half that gets people a token belongs to whatever is talking to them, and putting both in one package makes the security question twice as large for everybody who needed one of them.
Index ¶
- type Config
- type Token
- func (t *Token) Audience() []string
- func (t *Token) Claim(name string, v any) error
- func (t *Token) Email() string
- func (t *Token) EmailVerified() bool
- func (t *Token) Expiry() (time.Time, bool)
- func (t *Token) Groups() []string
- func (t *Token) Has(name string) bool
- func (t *Token) IssuedAt() (time.Time, bool)
- func (t *Token) Issuer() string
- func (t *Token) NotBefore() (time.Time, bool)
- func (t *Token) Subject() string
- func (t *Token) Username() string
- type Verifier
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Issuer is the "iss" a token must carry, exactly. It is also where
// discovery starts, unless JWKSURL is given.
Issuer string
// Audience is the "aud" a token must be addressed to -- this service's
// client id at the provider.
Audience string
// JWKSURL skips discovery, for a provider that does not publish
// /.well-known/openid-configuration or one behind something that does not
// forward it.
JWKSURL string
// UsernameClaim is which claim names the person. Default
// "preferred_username", falling back to "email" and then "sub" -- see
// [Token.Username], where the fallback is explained.
UsernameClaim string
// GroupsClaim is which claim carries their groups. Default "groups".
GroupsClaim string
// ClockSkew is how much a clock may differ before a token is early or
// late. Default one minute; a provider and a server that disagree by more
// than that have a problem worth fixing rather than tolerating.
ClockSkew time.Duration
// MinRefresh is how long to wait before fetching the key set again after
// a key was not found. Default one minute: keys rotate, and a token with
// an unknown kid must not be a way to make this server hammer the
// issuer.
MinRefresh time.Duration
// Client is the HTTP client for discovery and the key set. A caller with
// a proxy, a private CA or a timeout of its own passes one.
Client *http.Client
// Now overrides the clock, for tests.
Now func() time.Time
}
Config is what a verifier needs to know. Issuer and Audience are required: a verifier without them is one that accepts every token anybody ever signed.
type Token ¶
type Token struct {
// contains filtered or unexported fields
}
A Token is a verified token: what the issuer said about who is asking.
Every method reads the claims that arrived. Nothing here re-checks anything -- a Token exists only because Verifier.Verify returned it -- and nothing here reaches the network.
func (*Token) Audience ¶
Audience is every "aud" in the token. It is a list in the specification and a string in most tokens, and both spellings mean the same thing.
func (*Token) Claim ¶
Claim reads any other claim into v, which is how a deployment gets at something this package has no opinion about.
func (*Token) Email ¶
Email is "email", which a provider may or may not send and may or may not have verified -- see Token.EmailVerified.
func (*Token) EmailVerified ¶
EmailVerified is "email_verified".
⛔ An unverified email is a string the person typed. Matching people by one lets somebody claim to be anybody whose address they know, at any provider that does not check.
func (*Token) Groups ¶
Groups is the groups claim, which providers spell differently and some do not send at all.
func (*Token) Has ¶
Has reports whether a claim is present at all, which is different from present and empty.
func (*Token) Subject ¶
Subject is "sub": the issuer's own identifier for the person, stable across name changes and the only one that is promised to be.
func (*Token) Username ¶
Username is the name to call this person, from the configured claim.
The default order is preferred_username, then email, then sub. It is a fallback rather than a requirement because providers differ about which they send -- and it ends at sub, which every token has and which is the one the issuer promises is stable. A deployment that maps people by name should say which claim it means rather than take what arrives.
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
A Verifier checks tokens from one issuer for one audience.
It is safe for concurrent use, and it holds the key set: build one at startup and keep it, rather than one per request.
func New ¶
New reads the provider's configuration and prepares to verify.
Discovery happens HERE rather than at the first token, for the same reason a database is pinged at startup: a provider that is not answering is a server that cannot authenticate anybody, and it should say so before it listens.