HOG v2.1.0¶
HOG v2.1.0 is an additive release: it adds six features behind new, optional configuration, and every v2.0.0 config keeps working unchanged. The headline is provider compatibility and multi-instance identity — verifying access tokens from providers that don't shape them like ID tokens, and handing an already-resolved identity to a second HOG instance without a second round trip to the IdP.
Highlights¶
- Access tokens from a dedicated key set. Verify
Authorization: Bearertokens signed by a key set other than the ID token'sjwks_uri— and accept tokens that carry nosubor no standardaudat all. - Subject from any claim. The session subject (
X-User-Id) can come from a configured claim instead of alwayssub, on both the cookie and the Bearer paths. - Group prefix strip. Trim a configured prefix off each rendered group value, so a directory's fully-qualified role names become a clean group set.
- Authorization deny-redirect. Send a denied
app-route request to a same-origin page instead of a bare403. - Identity assertion between instances. A HOG instance mints a short-lived, signed statement of the principal it resolved; a second HOG instance behind it verifies and trusts that instead of re-resolving the identity itself.
- A shipped session store.
plugins/statestore-valkeyis a ready-to-usesession.StateStorebacked by Valkey, kept in its own module so its dependency never reaches the core.
Access tokens from a dedicated key set¶
Not every provider signs access tokens the way it signs ID tokens. With
some, access tokens verify against a key set of their own (discovery's
jwks_access_token_uri, or an explicit bearer.jwksURL), carry the client
identity in a claim such as client_id rather than the standard aud
(bearer.audienceClaim), and omit sub entirely. A new bearer: block on
the IdP resource — jwksURL, audienceClaim, requireAudience,
signingAlgs — covers all of it; a token missing sub is no longer an
automatic rejection, provided the subject can be resolved from elsewhere
(see the next feature). See
configure authentication.
Subject claim¶
identity.subjectClaim names the claim that becomes Principal.Subject
(and, projected, X-User-Id), on both the cookie flow and the Bearer path.
It defaults to sub, reproducing today's behaviour exactly; set it to
something like uid for a token whose own subject lives elsewhere, or that
carries no sub at all. See
configure authentication.
Group prefix strip¶
identity.groups.strip removes the first matching prefix (case-insensitive)
from each rendered group value — useful when a directory's role names carry
a fully-qualified prefix that a policy shouldn't have to spell out every
time. An empty list, the default, changes nothing. See
configure authentication.
Authorization deny-redirect¶
access.onDeny.redirect lets an app route answer a denied authorization
check with a 302 to a same-origin page instead of the plain 403 forbidden
body — a friendlier landing for a browser than an error page. The redirect
target is validated as same-origin at config load, exactly like the login
flow's return_to; a service route always keeps the 403 regardless, so
an API client's response shape never changes. See
configure authorization.
Identity assertion between instances¶
identity.assertion lets one HOG instance mint a compact, Ed25519-signed
statement of the principal it resolved (subject, passport, groups) and a
second instance behind it verify and trust that statement instead of
calling userinfo a second time. The issuing side (assertion.issue) mints a
fresh, short-lived assertion (60 seconds by default, plus 30 seconds of
clock skew at the accepting end) for every request on a
route with the new forwardIdentity: true handler option; the accepting
side (assertion.accept) verifies the signature and issuer and, by
default, only lets a valid assertion enrich a principal a Bearer token has
already authenticated on the same request — a header alone authenticates
nothing unless requireBearer: false is set explicitly. See
configure authentication
and design: the identity assertion
for the trust model this implies between two deployments.
Valkey-backed session store¶
plugins/statestore-valkey is a complete session.StateStore
implementation backed by Valkey, shipped as a nested Go
module so github.com/valkey-io/valkey-go never becomes a dependency of the
core hog module. List it in Gateway.spec.plugins and reference
type: valkey from stateProvider to unlock silent token refresh with a
real, supported backend instead of writing your own. See
scaling and availability.
Configuration additions¶
| Field | Default | Purpose |
|---|---|---|
IdP.spec.bearer.jwksURL |
discovery's jwks_access_token_uri, else jwks_uri |
Key set for verifying Bearer access tokens. |
IdP.spec.bearer.audienceClaim |
— (checks aud) |
Claim compared against bearerAudience instead of aud. |
IdP.spec.bearer.requireAudience |
true |
false accepts an access token with no audience at all; rejected at config load if combined with audienceClaim. |
IdP.spec.bearer.signingAlgs |
the provider's advertised ID-token algorithms, or RS256 when it advertises none | Signature algorithms accepted for access tokens. |
Gateway.spec.identity.subjectClaim |
sub |
Claim that becomes the principal subject, on both the cookie and Bearer paths. |
Gateway.spec.identity.groups.strip |
[] |
Case-insensitive prefixes removed from each rendered group value. |
Route.spec.access.onDeny.redirect |
— (403) | Same-origin path an authorization denial redirects to, on app routes. |
Gateway.spec.identity.assertion.issue |
— | Mints a signed identity assertion for a proxied hop (name, keyId, key, ttl, header). |
Gateway.spec.identity.assertion.accept |
— | Verifies an inbound identity assertion (issuer, keys, header, requireBearer). |
handler.forwardIdentity (reverse-proxy, api) |
false |
Attach the minted identity assertion to this backend hop. |
stateProvider.type: valkey |
— | The shipped Valkey-backed session.StateStore, from plugins/statestore-valkey. |
Upgrade notes¶
The import path now carries the major version¶
The module is github.com/paulopiriquito/hog/v2. Go requires a major-version
suffix from v2 onwards, and without it the module proxy served only the v1 tags:
go get github.com/paulopiriquito/hog quietly resolved to v1.3.0, the
pre-rewrite code, which is why building a plugin against v2 did not work. This
release fixes that, so v2.1.0 is the first v2 that go get can actually fetch.
The v2.0.0 tag stays unfetchable; nothing can change that after the fact.
If you import HOG directly, for framework mode or for a plugin:
and add /v2 to your import paths. Nothing else changes: the package names,
the API and the configuration are the same. A configuration file is unaffected,
and so is anything built with hog-build, which resolves the core module from
the source you point it at rather than from the proxy.
The Valkey state store ships as a separate module on an unsuffixed path with its
own version line, so it is pinned as
github.com/paulopiriquito/hog/plugins/statestore-valkey@v1.0.0.
This release is additive: every field above is optional, defaults to today's behaviour, and an existing v2.0.0 config needs no changes to keep meaning exactly what it already means. There is nothing to migrate.
The JSON Schema (internal/configschema/hog.schema.json, published on this
site and baked into the binary — run hog schema to print it offline)
landed just after the v2.0.0 tag and is covered by this release along with
everything above — see
editor setup & schema validation if you
haven't wired it into your editor or CI yet.