Documentation
¶
Overview ¶
Package token — the immutable claim set every concrete token format decodes into and every issuer renders from.
Package token — the copy-on-write setters that build a ClaimsValue.
Package token — ranges 0.2.13.* (the domain's verdicts) and 0.3.44.* (what is specific to the two concrete formats) — ADR 0042, declared here since ADR 0160.
Package token — declares the sentinel *errs.Error verdicts an issuer or a verifier returns, and the refusals specific to the two concrete formats the engine in internal/service/security/token implements (ADR 0160). Each var's name equals its errs.Define Reason in SCREAMING_SNAKE form.
The Public strings are the half a third party reads (ADR 0005 §4). They name WHICH check refused the token and never echo a value from it: not the audience, not the issuer, not the key id, not a claim. A rejection message that quotes the token's own contents back is a mirror an attacker can query.
Package token declares the security-token domain: the Issuer and Verifier ports, the immutable ClaimsValue every concrete format decodes into, and the typed verdicts a caller matches on. Concrete formats — JWT/JWS compact (RFC 7519) and PASETO v4 — live in internal/service/security/token; this package owns only the contract, so a caller can hold a Verifier without knowing which wire format produced it.
There is no registry, and that is the security decision ¶
Every other pluggable SDK domain resolves an implementation through a process-wide registry keyed on a name. A token registry would be keyed on the "alg" header — a field the ATTACKER writes. Resolving the verifying algorithm from that field is algorithm confusion, the class of bug where an RSA/EC public key (public by definition) is fed to HMAC-SHA-256 as a shared secret and the forgery verifies. So the algorithm is bound at construction, by a constructor that only accepts the ONE key type that algorithm can use, and the header is only ever compared against that binding — never consulted to choose it. See ADR 0042.
Algorithm is a uint8 enum rather than a string for the same reason: the unsecured "none" algorithm of RFC 7519 §6 has no representation in this type, so no call site can request it and no configuration can enable it. A token whose header says "none" is refused with AlgorithmNone.
Index ¶
- Constants
- Variables
- func IsRegisteredClaim(name string) bool
- type Algorithm
- type ClaimsValue
- func (c ClaimsValue) Audience() []string
- func (c ClaimsValue) Expiry() time.Time
- func (c ClaimsValue) GoString() string
- func (c ClaimsValue) ID() string
- func (c ClaimsValue) IsZero() bool
- func (c ClaimsValue) IssuedAt() time.Time
- func (c ClaimsValue) Issuer() string
- func (c ClaimsValue) NotBefore() time.Time
- func (c ClaimsValue) PrivateNames() []string
- func (c ClaimsValue) PrivateRaw(name string) (raw []byte, found bool)
- func (c ClaimsValue) String() string
- func (c ClaimsValue) Subject() string
- func (c ClaimsValue) WithAudience(aud ...string) ClaimsValue
- func (c ClaimsValue) WithExpiry(exp time.Time) ClaimsValue
- func (c ClaimsValue) WithID(jti string) ClaimsValue
- func (c ClaimsValue) WithIssuedAt(iat time.Time) ClaimsValue
- func (c ClaimsValue) WithIssuer(iss string) ClaimsValue
- func (c ClaimsValue) WithNotBefore(nbf time.Time) ClaimsValue
- func (c ClaimsValue) WithPrivateRaw(name string, raw []byte) (claims ClaimsValue, err error)
- func (c ClaimsValue) WithSubject(sub string) ClaimsValue
- type Issuer
- type Verifier
Constants ¶
const ( // ClaimIssuer is "iss" — who minted the token. ClaimIssuer string = "iss" // ClaimSubject is "sub" — who the token is about. ClaimSubject string = "sub" // ClaimAudience is "aud" — who the token is FOR. Checked, never assumed. ClaimAudience string = "aud" // ClaimExpiry is "exp" — the instant after which the token is dead. ClaimExpiry string = "exp" // ClaimNotBefore is "nbf" — the instant before which it is not yet alive. ClaimNotBefore string = "nbf" // ClaimIssuedAt is "iat" — when it was minted. ClaimIssuedAt string = "iat" // ClaimID is "jti" — a unique id for the token. ClaimID string = "jti" )
Registered claim names (RFC 7519 §4.1). PASETO v4 reuses the same seven names, with a different value encoding — see internal/service/security/token.
const CodeAlgorithmMismatch errs.Code = 0x00_02_0D_03 // 0.2.13.3
CodeAlgorithmMismatch identifies a token whose algorithm header names an algorithm other than the one the verifier is bound to. This is the code an algorithm-confusion attempt produces.
const CodeAlgorithmNone errs.Code = 0x00_02_0D_02 // 0.2.13.2
CodeAlgorithmNone identifies a token presenting the unsecured "none" algorithm of RFC 7519 §6 (in any capitalisation). It is always refused and there is no configuration that enables it.
const CodeAudienceMismatch errs.Code = 0x00_02_0D_08 // 0.2.13.8
CodeAudienceMismatch identifies an authenticated token whose "aud" claim does not contain the audience the verifier was configured with.
const CodeClaimNameInvalid errs.Code = 0x00_02_0D_0F // 0.2.13.15
CodeClaimNameInvalid identifies a private claim whose name is empty or shadows one of the seven registered claim names.
const CodeDuplicateMember errs.Code = 0x00_03_2C_07 // 0.3.44.7
CodeDuplicateMember identifies a JOSE header or claims object carrying the same member name twice. Go's encoding/json silently keeps the last one, so two readers of the same bytes could disagree about what the token says (RFC 8725 §2.6).
const CodeExpired errs.Code = 0x00_02_0D_05 // 0.2.13.5
CodeExpired identifies an authenticated token whose "exp" claim is in the past, after the configured leeway.
const CodeExpiryRequired errs.Code = 0x00_02_0D_07 // 0.2.13.7
CodeExpiryRequired identifies a token carrying no "exp" claim where the profile requires one — the default. It is also raised by an issuer asked to mint a token with neither an explicit expiry nor a configured lifetime.
CodeFooterMismatch identifies a PASETO token whose footer is not the one the verifier expects — including a footer present where none was configured.
const CodeHeaderUnsupported errs.Code = 0x00_03_2C_01 // 0.3.44.1
CodeHeaderUnsupported identifies a JOSE header the engine will not act on: a non-empty "crit" (RFC 7515 §4.1.11 makes an unrecognised critical parameter a mandatory rejection), or a "typ" that does not match the one the verifier requires.
const CodeIssueFailed errs.Code = 0x00_02_0D_0E // 0.2.13.14
CodeIssueFailed identifies an issuer that could not render the claims it was given — a claim the target format cannot express, or a signing fault.
const CodeIssuerMismatch errs.Code = 0x00_02_0D_09 // 0.2.13.9
CodeIssuerMismatch identifies an authenticated token whose "iss" claim is not the issuer the verifier was configured with.
const CodeKeyIDAmbiguous errs.Code = 0x00_03_2C_04 // 0.3.44.4
CodeKeyIDAmbiguous identifies a "kid" carried by more candidates than the verifier will try. Trying a bounded number of candidates is the rotation path; trying an unbounded number is a CPU amplifier a publisher controls.
const CodeKeyIDMissing errs.Code = 0x00_03_2C_03 // 0.3.44.3
CodeKeyIDMissing identifies a token presented to a key-set verifier with no "kid" header. A set verifier selects by id; it does not try every key it holds.
const CodeKeyNotFound errs.Code = 0x00_03_2C_02 // 0.3.44.2
CodeKeyNotFound identifies a token whose "kid" names no key in the JWK Set the verifier was built from.
const CodeKeyUnsuitable errs.Code = 0x00_02_0D_0C // 0.2.13.12
CodeKeyUnsuitable identifies a key that cannot be used for the algorithm it was handed to: the wrong length, the wrong curve, or a key type for which this domain implements no algorithm.
const CodeLifetimeTooLong errs.Code = 0x00_02_0D_10 // 0.2.13.16
CodeLifetimeTooLong identifies an authenticated token whose exp-iat span exceeds the maximum lifetime the verifier accepts.
const CodeMalformed errs.Code = 0x00_02_0D_01 // 0.2.13.1
CodeMalformed identifies a token whose structure could not be read at all: the wrong number of segments, a segment that is not unpadded base64url, a header or payload that is not a JSON object.
const CodeNotYetValid errs.Code = 0x00_02_0D_06 // 0.2.13.6
CodeNotYetValid identifies an authenticated token whose "nbf" claim is in the future, after the configured leeway.
const CodePolicyMisconfigured errs.Code = 0x00_02_0D_0D // 0.2.13.13
CodePolicyMisconfigured identifies an issuer or verifier built with a configuration it cannot honour — a negative or absurd leeway, a negative lifetime, a bound below the floor. Every call is refused without touching the token (ADR 0031).
const CodeSchemeUnsupported errs.Code = 0x00_03_2C_06 // 0.3.44.6
CodeSchemeUnsupported identifies a PASETO version+purpose the engine does not implement: any local (encrypted) purpose, and every version other than v4. See internal/service/security/token/CLAUDE.md for why v4.local is absent.
const CodeSignatureInvalid errs.Code = 0x00_02_0D_04 // 0.2.13.4
CodeSignatureInvalid identifies a token whose signature or MAC tag did not authenticate under the bound key.
const CodeTooDeep errs.Code = 0x00_02_0D_0B // 0.2.13.11
CodeTooDeep identifies a claims payload whose JSON nesting exceeds the configured depth bound.
const CodeTooLarge errs.Code = 0x00_02_0D_0A // 0.2.13.10
CodeTooLarge identifies an input past a declared size bound: the token string, the decoded header, or the number of claims.
const MaxPrivateClaims int = 64
MaxPrivateClaims caps how many application claims one token may carry. The number is a bound, not a budget: a claim set is an authorisation statement, and a token that needs more than this many members is describing a database row rather than a subject. The cap keeps a hostile payload's decode cost linear and bounded, which is the same reason the wire size is capped.
Variables ¶
var ( // Malformed is returned when the input cannot be read as a token: the // wrong segment count, a segment that is not unpadded base64url, a header // or payload that is not a JSON object. Malformed = errs.Static(CodeMalformed, "MALFORMED", "The token is malformed", "core/security/token: token structure unreadable — segment count, base64url decode, or JSON shape", 401, 65) // AlgorithmNone is returned for a token presenting the unsecured "none" // algorithm (RFC 7519 §6), in any capitalisation. It is unconditional: // this domain has no Algorithm value for "none" and no option enabling it. AlgorithmNone = errs.Static(CodeAlgorithmNone, "ALGORITHM_NONE", "The token declares the unsecured none algorithm", "core/security/token: alg=none refused unconditionally; there is no configuration that accepts it", 401, 77) // AlgorithmMismatch is returned when the token's algorithm header names // something other than the algorithm the verifier is bound to. It is the // verdict an algorithm-confusion attempt produces, and it is reached // BEFORE any key is handed to any primitive. AlgorithmMismatch = errs.Static(CodeAlgorithmMismatch, "ALGORITHM_MISMATCH", "The token algorithm is not the one this verifier accepts", "core/security/token: header alg differs from the bound algorithm; the header never selects a key", 401, 77) // SignatureInvalid is returned when the signature or MAC tag did not // authenticate the token under the bound key. It is deliberately the same // verdict for "wrong key" and "tampered bytes": the two are the same event. SignatureInvalid = errs.Static(CodeSignatureInvalid, "SIGNATURE_INVALID", "The token signature is not valid", "core/security/token: signature or MAC tag did not verify under the bound key", 401, 77) // Expired is returned when an AUTHENTICATED token's exp claim is in the // past. Reaching it means the signature already verified — claim verdicts // are never reported for a token that failed authentication. Expired = errs.Static(CodeExpired, "EXPIRED", "The token has expired", "core/security/token: exp is in the past after leeway; signature had already verified", 401, 77) // NotYetValid is returned when an authenticated token's nbf claim is in // the future after leeway. NotYetValid = errs.Static(CodeNotYetValid, "NOT_YET_VALID", "The token is not valid yet", "core/security/token: nbf is in the future after leeway", 401, 77) // ExpiryRequired is returned when a token carries no exp claim and the // profile requires one — which is the default, because RFC 7519 §4.1.4 // makes exp OPTIONAL and a token that never expires is a password. ExpiryRequired = errs.Static(CodeExpiryRequired, "EXPIRY_REQUIRED", "The token carries no expiry", "core/security/token: exp absent and the profile requires it; set AllowMissingExpiry to opt out", 401, 77) // AudienceMismatch is returned when an authenticated token's aud claim // does not contain this recipient. A correctly signed token addressed to // another API is not a valid token here (RFC 8725 §3.9). AudienceMismatch = errs.Static(CodeAudienceMismatch, "AUDIENCE_MISMATCH", "The token is not addressed to this audience", "core/security/token: configured audience absent from the aud claim", 401, 77) // IssuerMismatch is returned when an authenticated token's iss claim is // not the configured issuer (RFC 8725 §3.8). IssuerMismatch = errs.Static(CodeIssuerMismatch, "ISSUER_MISMATCH", "The token issuer is not the expected one", "core/security/token: iss differs from the configured issuer", 401, 77) // TooLarge is returned when an input passes a declared size bound before // any parsing happens — the token string, the decoded header, the claim // count. The bound is checked FIRST so a hostile input never funds the // work of discovering that it is hostile. TooLarge = errs.Static(CodeTooLarge, "TOO_LARGE", "The token exceeds the accepted size", "core/security/token: input past a declared bound (token length, header size, or claim count)", 401, 65) // TooDeep is returned when the claims payload nests deeper than the // configured bound. The depth is measured by a linear scan of the raw // bytes, before encoding/json ever sees them. TooDeep = errs.Static(CodeTooDeep, "TOO_DEEP", "The token claims are nested too deeply", "core/security/token: claim nesting past MaxClaimDepth, measured before JSON decoding", 401, 65) // KeyUnsuitable is returned when a key cannot serve the algorithm it was // handed to: wrong length, wrong curve, or a key type for which this // domain implements nothing. KeyUnsuitable = errs.Static(CodeKeyUnsuitable, "KEY_UNSUITABLE", "The key cannot be used for this token algorithm", "core/security/token: key type, curve or length does not match the bound algorithm", 0, 78) // PolicyMisconfigured is returned by every call to an issuer or verifier // built with a configuration it cannot honour. Like its resilience // namesake it is permanent, not transient: the fix is at the construction // site, never a retry (ADR 0031). PolicyMisconfigured = errs.Static(CodePolicyMisconfigured, "POLICY_MISCONFIGURED", "The token policy is misconfigured", "core/security/token: issuer or verifier built with a configuration it cannot honour; fields name the knob", 0, 78) // IssueFailed is returned when an issuer cannot render the claims it was // given — a claim the target format cannot express, or a signing fault. IssueFailed = errs.Static(CodeIssueFailed, "ISSUE_FAILED", "The token could not be issued", "core/security/token: claims unrenderable in the target format, or the signing primitive failed", 0, 78) // ClaimNameInvalid is returned by ClaimsValue.WithPrivateRaw for an empty // name or one shadowing a registered claim. ClaimNameInvalid = errs.Static(CodeClaimNameInvalid, "CLAIM_NAME_INVALID", "The claim name is empty or reserved", "core/security/token: private claim name empty or colliding with a registered claim name", 0, 65) // LifetimeTooLong is returned when an authenticated token's exp-iat span // exceeds the maximum the verifier accepts. A long-lived bearer token is // a credential with no revocation story, so a recipient is allowed to say // no to one even when its issuer said yes. LifetimeTooLong = errs.Static(CodeLifetimeTooLong, "LIFETIME_TOO_LONG", "The token lifetime exceeds what this recipient accepts", "core/security/token: exp-iat span past MaxLifetime", 401, 77) // HeaderUnsupported is returned for a JOSE header the engine will not act // on: a non-empty "crit", or a "typ" other than the required one. // // A non-empty "crit" is a rejection and not a warning because RFC 7515 // §4.1.11 says so: the parameters it names MUST be understood, the engine // understands none of them, and "ignore what you do not understand" is how // a security-relevant extension gets silently dropped. HeaderUnsupported = errs.Static(CodeHeaderUnsupported, "HEADER_UNSUPPORTED", "The token header carries parameters this verifier will not accept", "service/security/token: non-empty crit, or typ differs from the required value", 401, 77) // KeyNotFound is returned when the token's kid names no key in the set. KeyNotFound = errs.Static(CodeKeyNotFound, "KEY_NOT_FOUND", "No key in the key set matches this token", "service/security/token: kid absent from the JWK Set", 401, 77) // KeyIDMissing is returned when a key-set verifier is handed a token with // no kid header. Selecting by id is the contract; trying every key in the // set instead would turn a large published JWKS into a work multiplier and // would quietly accept a token the publisher never routed to that key. KeyIDMissing = errs.Static(CodeKeyIDMissing, "KEY_ID_MISSING", "The token carries no key identifier", "service/security/token: set verifier requires a kid header; use a single-key verifier when tokens carry none", 401, 77) // KeyIDAmbiguous is returned when more keys carry the kid than the // verifier will try. jwk.Set refuses to pick between duplicate kids // because nothing distinguishes them; a signature does distinguish them, // so the engine tries the candidates — but a bounded number of them. KeyIDAmbiguous = errs.Static(CodeKeyIDAmbiguous, "KEY_ID_AMBIGUOUS", "Too many keys share this token key identifier", "service/security/token: candidate count past MaxKeyCandidates; a bounded rotation window is the supported case", 401, 77) // one, including a footer present where the verifier expects none. The // footer is authenticated but is not returned through the Verifier port, // so accepting an arbitrary one would authenticate data and then drop it. FooterMismatch = errs.Static(CodeFooterMismatch, "FOOTER_MISMATCH", "The token footer is not the expected one", "service/security/token: PASETO footer differs from the configured value, or is present with none configured", 401, 77) // SchemeUnsupported is returned for a PASETO version+purpose the engine // does not implement — every local (encrypted) purpose, and every version // other than v4. It is returned instead of Malformed so an operator can // tell "this is not a token" from "this is a token I did not build for". SchemeUnsupported = errs.Static(CodeSchemeUnsupported, "SCHEME_UNSUPPORTED", "This token version and purpose are not supported", "service/security/token: only PASETO v4.public is implemented; v4.local needs primitives the SDK keeps in third-party", 401, 77) // DuplicateMember is returned when a JOSE header or a claims object // carries the same member name twice. encoding/json would silently keep // the last, so a token could say one thing to this reader and another to // the next (RFC 8725 §2.6). DuplicateMember = errs.Static(CodeDuplicateMember, "DUPLICATE_MEMBER", "The token repeats a member name", "service/security/token: duplicate JSON member in the header or the claims object", 401, 65) )
Functions ¶
func IsRegisteredClaim ¶
IsRegisteredClaim reports whether name is one of the seven registered claim names this domain models itself.
Types ¶
type Algorithm ¶
type Algorithm uint8
Algorithm is the signature algorithm a token issuer or verifier is bound to. The zero value is AlgorithmUnknown and is never usable.
The set is closed and deliberately small: an algorithm reaches this enum only when the SDK's crypto domain already ships the primitive behind it. "none" is absent by construction.
const ( // AlgorithmUnknown is the zero value — the algorithm nobody chose. Every // constructor refuses it rather than defaulting to one. AlgorithmUnknown Algorithm = iota // AlgorithmHS256 is JOSE "HS256": HMAC-SHA-256 over a 256-bit shared // secret (RFC 7518 §3.2). Symmetric — a verifier can also mint tokens. AlgorithmHS256 // AlgorithmES256 is JOSE "ES256": ECDSA on NIST P-256 with SHA-256 and the // fixed-width R||S signature encoding of RFC 7518 §3.4. AlgorithmES256 // AlgorithmEdDSA is JOSE "EdDSA": Ed25519 over the JWS signing input // (RFC 8037 §3.1). AlgorithmEdDSA // AlgorithmPasetoV4Public is PASETO v4.public: Ed25519 over the // pre-authentication encoding of the version header, payload and footer. // It is a distinct Algorithm from AlgorithmEdDSA even though both use // Ed25519, because the bytes that get signed are not the same bytes. AlgorithmPasetoV4Public )
func (Algorithm) Known ¶
Known reports whether a names an algorithm this domain implements. It is the guard every constructor runs before binding a key, so an out-of-range value is refused at construction rather than at the first verification.
func (Algorithm) String ¶
String reports the wire name of a — the JOSE "alg" header value for the JWS algorithms, and the version+purpose header for PASETO. An unknown Algorithm renders "unknown", which matches no wire name, so a zero value can never be mistaken for a real header on either side of a comparison.
type ClaimsValue ¶
type ClaimsValue struct {
// contains filtered or unexported fields
}
ClaimsValue is one token's claim set: the seven registered claims as typed fields plus any number of application ("private") claims held as their raw JSON encoding.
It is an immutable value. Accessors copy every slice and map out, the With* methods return a modified copy rather than mutating the receiver, and every field is unexported so encoding/json cannot reach the contents by reflection — a claim set routinely carries personal data, and the only way it should ever be serialised is through a format package that knows what it is doing.
Private claims are RAW JSON, not decoded values: this is the core layer, and "what shape is a scope claim" is the caller's question, not the domain's. Decode one with pkg/v1/security/token.PrivateClaim.
func NewClaimsValue ¶
func NewClaimsValue() ClaimsValue
NewClaimsValue returns an empty claim set to build on. The zero ClaimsValue is equally valid and equally empty; this constructor exists so a call site reads as a statement of intent rather than a struct literal.
pkg/v1/security/token re-exports it as the shorter NewClaims, since the Value suffix is a core-layer naming rule (KTN-STRUCT-ROLE) and not a consumer's concern.
func (ClaimsValue) Audience ¶
func (c ClaimsValue) Audience() []string
Audience returns a copy of the "aud" claim, nil when the token carried none.
func (ClaimsValue) Expiry ¶
func (c ClaimsValue) Expiry() time.Time
Expiry reports the "exp" claim. The zero Time means the claim was ABSENT, never "expired at the epoch" — a verifier decides what to do about absence (see VerifierConfig.AllowMissingExpiry in internal/service/security/token).
func (ClaimsValue) GoString ¶
func (c ClaimsValue) GoString() string
GoString implements fmt.GoStringer so %#v stays redacted too — fmt bypasses String for Go-syntax formatting and would otherwise dump every field.
func (ClaimsValue) ID ¶
func (c ClaimsValue) ID() string
ID reports the "jti" claim, empty when the token carried none.
func (ClaimsValue) IsZero ¶
func (c ClaimsValue) IsZero() bool
IsZero reports whether c carries no claim at all.
func (ClaimsValue) IssuedAt ¶
func (c ClaimsValue) IssuedAt() time.Time
IssuedAt reports the "iat" claim; the zero Time means it was absent.
func (ClaimsValue) Issuer ¶
func (c ClaimsValue) Issuer() string
Issuer reports the "iss" claim, empty when the token carried none.
func (ClaimsValue) NotBefore ¶
func (c ClaimsValue) NotBefore() time.Time
NotBefore reports the "nbf" claim; the zero Time means it was absent.
func (ClaimsValue) PrivateNames ¶
func (c ClaimsValue) PrivateNames() []string
PrivateNames returns the application claim names, sorted, so a caller enumerating them gets a stable order rather than Go's randomised map order.
func (ClaimsValue) PrivateRaw ¶
func (c ClaimsValue) PrivateRaw(name string) (raw []byte, found bool)
PrivateRaw returns a copy of the raw JSON encoding of the named application claim. The second result reports presence, so a claim explicitly set to JSON null is distinguishable from a claim that was never sent.
func (ClaimsValue) String ¶
func (c ClaimsValue) String() string
String implements fmt.Stringer and renders the SHAPE of the claim set, never a value: "token.Claims{registered:3 aud:1 private:2}".
A claim set is the output of an authentication decision and routinely holds a subject id, an email, a tenant, a scope list. Rendering any of it would put that in whichever log line happened to use %v — which is exactly how claim contents end up in a log aggregator that was never scoped to hold them. The counts are enough to tell "the token had no audience" from "the audience did not match", which is what a %v is usually reaching for.
func (ClaimsValue) Subject ¶
func (c ClaimsValue) Subject() string
Subject reports the "sub" claim, empty when the token carried none.
func (ClaimsValue) WithAudience ¶
func (c ClaimsValue) WithAudience(aud ...string) ClaimsValue
WithAudience returns a copy of c whose "aud" claim is exactly aud. Passing no argument clears the claim; the slice is copied, so a later mutation of the caller's backing array cannot reach the claim set.
func (ClaimsValue) WithExpiry ¶
func (c ClaimsValue) WithExpiry(exp time.Time) ClaimsValue
WithExpiry returns a copy of c carrying exp. The zero Time clears the claim, which a verifier reads as "no expiry was sent" — not as "expired".
func (ClaimsValue) WithID ¶
func (c ClaimsValue) WithID(jti string) ClaimsValue
WithID returns a copy of c carrying jti. The empty string clears it.
func (ClaimsValue) WithIssuedAt ¶
func (c ClaimsValue) WithIssuedAt(iat time.Time) ClaimsValue
WithIssuedAt returns a copy of c carrying iat. The zero Time clears it.
func (ClaimsValue) WithIssuer ¶
func (c ClaimsValue) WithIssuer(iss string) ClaimsValue
WithIssuer returns a copy of c carrying iss. The empty string clears it.
func (ClaimsValue) WithNotBefore ¶
func (c ClaimsValue) WithNotBefore(nbf time.Time) ClaimsValue
WithNotBefore returns a copy of c carrying nbf. The zero Time clears it.
func (ClaimsValue) WithPrivateRaw ¶
func (c ClaimsValue) WithPrivateRaw(name string, raw []byte) (claims ClaimsValue, err error)
WithPrivateRaw returns a copy of c carrying the application claim name with the raw JSON encoding raw.
It refuses three things rather than accommodating them:
- an empty name, which no JSON object member can carry meaningfully;
- one of the seven registered names (IsRegisteredClaim) — a second "exp" that the temporal validator never reads is a claim an application would trust and nothing would enforce;
- more than MaxPrivateClaims members, so the decode cost of a hostile payload stays bounded.
raw is copied and is NOT validated as JSON here: the format package that produced it already parsed it, and re-parsing in the value type would put a second, differently-strict JSON reader on the trusted path.
func (ClaimsValue) WithSubject ¶
func (c ClaimsValue) WithSubject(sub string) ClaimsValue
WithSubject returns a copy of c carrying sub. The empty string clears it.
type Issuer ¶
type Issuer interface {
// Issue renders claims as a token string, or returns a typed verdict
// (IssueFailed / ExpiryRequired / KeyUnsuitable) when it cannot.
Issue(claims ClaimsValue) (string, error)
}
Issuer mints a signed token carrying claims. Implementations are bound to exactly one Algorithm and one signing key at construction, MUST be safe for concurrent use, and MUST NOT take an algorithm or a key from the claims.
IFACE-PLUGIN: constructors in internal/service/security/token hand instances back behind this interface; the concrete types stay unexported.
type Verifier ¶
type Verifier interface {
// Verify authenticates token and returns its claims, or one of the typed
// verdicts declared in this package.
Verify(token string) (ClaimsValue, error)
}
Verifier authenticates a token and returns its claims. Implementations are bound to exactly one Algorithm and one verification key at construction and MUST be safe for concurrent use.
The contract has three parts, and all three are security requirements:
- The signature is checked BEFORE any claim is read, so a caller never acts on — or logs — the contents of an unauthenticated token.
- The token's own algorithm header is compared against the binding and is never used to select a key, an algorithm, or a code path.
- Failure returns exactly one typed verdict and a zero ClaimsValue. There is no "invalid, but here are the claims anyway" path.
IFACE-PLUGIN: constructors in internal/service/security/token hand instances back behind this interface; the concrete types stay unexported.