Airlock
view release on metacpan or search on metacpan
/Makefile
/Makefile.old
/MANIFEST*
!MANIFEST.SKIP
/META.*
/MYMETA.*
/resume
/AGENTS.md
# Claude Code â commit: skills/, agents/, hooks/, settings.json
# Ignore: local overrides, credentials, session data
.claude/*.local.*
.claude/local/
.claude/.credentials.json
.claude/statsig/
.claude/todos/
.claude/projects/
# karr materialized task view -- never commit
tasks/
config.yml
.karr
# skilletor: committe Config + Ignore-Block
docs/superpowers/plans/2026-10-02-airlock-phase-1.md view on Meta::CPAN
}
],
"users": [
{
"username": "plain",
"enabled": true,
"email": "plain@example.org",
"emailVerified": true,
"firstName": "Plain",
"lastName": "User",
"credentials": [
{ "type": "password", "value": "plain-password", "temporary": false }
]
},
{
"username": "otp",
"enabled": true,
"email": "otp@example.org",
"emailVerified": true,
"firstName": "Otp",
"lastName": "User",
"credentials": [
{ "type": "password", "value": "otp-password", "temporary": false },
{
"type": "otp",
"secretData": "{\"value\":\"12345678901234567890\"}",
"credentialData": "{\"subType\":\"totp\",\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\"}"
}
]
}
]
}
lib/Airlock/Upstream/Authentik.pm view on Meta::CPAN
=item *
C<amr> and C<auth_time> are the same in the ID token and the access token.
(Introspection would say the same, but it needs a confidential client; the
public one the test uses gets C<< { active: false } >> and nothing else.)
=back
Two qualifications on C<auth_time>, both read in authentik's source rather
than provoked here. A client-credentials or token-exchange grant sets it to
the minting time, not to a login â harmless for this factor, which refuses
anything without an C<amr>, but it is not the session's time there. And when
authentik finds no login event for a session, it falls back to the current
time, so an old authentication can look new: that direction B<fails open>, and
C<max_age> cannot catch it.
=head2 Asking for a fresh authentication
L<Airlock::Factor::Upstream/reauth_params> returns C<< max_age => 0 >>, which
is what OpenID Connect says for "authenticate this person again". B<authentik
t/authentik/README.md view on Meta::CPAN
defaults. This is the difference to Keycloak, which says nothing until
`t/keycloak/setup.pl` has run.
- **`acr` is one constant string** whatever happened, so `mfa_acr` stays empty
in `Airlock::Upstream::Authentik`. Sending `acr_values` changes nothing.
- **`auth_time` is when the session began** for the grants Airlock meets â the
authorization code and the device code both take it from the session's login
event â and it survives a refresh and further authorization requests.
`max_age` on the factor therefore measures the age of the authentication,
which is what a caller wants: a token minted now from a ten-minute-old
session is refused by `max_age => 300`. Two things it is not: for a
client-credentials or token-exchange grant `auth_time` is the minting time
(those carry no `amr`, so the factor refuses them anyway), and when authentik
finds no login event for a session it falls back to the current time, which
makes an old authentication look new. That last one fails open; it was read
in authentik's source, not provoked here.
- **`max_age=0` is the one value authentik throws away**, because its check is
on the truth of the number and zero is not true. Every other value works, and
so does `prompt=login`. Measured against a session six seconds old:
| sent | result |
|---|---|
t/keycloak/realm.json view on Meta::CPAN
}
],
"users": [
{
"username": "plain",
"enabled": true,
"email": "plain@example.org",
"emailVerified": true,
"firstName": "Plain",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "plain-password",
"temporary": false
}
]
},
{
"username": "otp",
"enabled": true,
"email": "otp@example.org",
"emailVerified": true,
"firstName": "Otp",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "otp-password",
"temporary": false
},
{
"type": "otp",
"secretData": "{\"value\":\"12345678901234567890\"}",
"credentialData": "{\"subType\":\"totp\",\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\"}"
}
( run in 3.897 seconds using v1.01-cache-2.11-cpan-3f962061493 )