NAME
Airlock::Factor::Upstream - Accept a second factor the identity provider has already checked
VERSION
version 0.001
SYNOPSIS
my $upstream = Airlock::Factor::Upstream->new( max_age => 300 );
# the host app passes what the ID token said
$airlock->approve( $user_code, subject => {
id => $claims->{sub},
amr => $claims->{amr},
acr => $claims->{acr},
auth_time => $claims->{auth_time},
} );
DESCRIPTION
When the host application logs people in through an identity provider that already does multi-factor authentication, asking again would be noise. This factor holds when the subject carries the right amr or acr and, if max_age is set, authenticated recently enough.
It needs no proof from the person. When it does not hold, the host application sends the person back to the identity provider with "reauth_params".
accept_amr
amr values of which one is enough. Default mfa, otp, hwk.
accept_acr
acr values of which one is enough. Empty by default, because what an acr value means is defined by each identity provider.
max_age
Optional. Seconds since auth_time after which the authentication is too old. Left out, the age of the authentication does not matter and only amr and acr decide.
Set, it needs an auth_time to measure: a subject without one never passes, whatever its amr says. That is deliberate — an unknown age is not a young one — but it means that against an identity provider which omits auth_time the factor silently never holds. Check that yours sends it before setting this.
now
Coderef returning the current epoch. For tests.
clock_skew
my $seconds = $upstream->clock_skew; # 60
How far the identity provider's clock may run ahead of this one, in seconds. Sixty, not configurable; override the method in a subclass to change it.
It bends one way only. An auth_time up to clock_skew seconds in the future is taken as now, because a provider whose clock is fast would otherwise be unusable. The "max_age" edge gets no such tolerance: with the default skew and max_age => 300, an authentication passes while its apparent age is between -60 and 300 seconds. A provider running fast therefore gets a shorter effective window, never a longer one, which is the safe direction.
needs_proof
False. The person does nothing here; the identity provider already asked.
verify
my $ok = $upstream->verify( $subject );
True when the subject carries one of "accept_amr" or one of "accept_acr" and, if "max_age" is set, has an auth_time within it. The proof argument the role passes is ignored.
reauth_params
my $params = $upstream->reauth_params; # { max_age => 0, acr_values => '...' }
Parameters to add to the OIDC authorization request that sends the person back to the identity provider for a fresh, strong authentication.
max_age => 0 is what OpenID Connect Core gives for "authenticate again whatever happened". Not every provider honours it: authentik 2026.8.3 tests the value for truth and so discards exactly the zero, letting the existing session through. "reauth_params" in Airlock::Upstream::Authentik sends prompt=login instead. If your provider is not authentik, send one request with max_age => 0 against a live session before you trust this.
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-airlock/issues.
IRC
Join #kubernetes on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.