Available plugin hooks
======================

This page shows the list of hooks that you can use in your :doc:`custom plugins <plugincustom>`. Read the :doc:`plugincustom` page for full details on how to create and enable custom plugins.

.. contents::
   :local:
   :depth: 1

OpenID Connect Issuer hooks
---------------------------

Discovery endpoint (/.well-known/openid-configuration)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

oidcGenerateMetadata
~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.23.0

This hook is triggered when LemonLDAP::NG is building the OpenID Connect
discovery document (``/.well-known/openid-configuration``). It allows plugins
to add custom metadata fields to the discovery response.

The hook's parameter is a hash reference of the metadata document (which you can modify).

Sample code::

   use constant hook => {
       oidcGenerateMetadata => 'addMetadata',
   };

   sub addMetadata {
       my ( $self, $req, $metadata ) = @_;
       $metadata->{my_custom_endpoint} = 'https://example.com/custom';
       push @{ $metadata->{grant_types_supported} }, 'my_custom_grant';
       return PE_OK;
   }

Authorization endpoint (/oauth2/authorize)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

oidcGotRequest
~~~~~~~~~~~~~~

.. versionadded:: 2.0.10

This hook is triggered when LemonLDAP::NG received an authorization request on the ``/oauth2/authorize`` endpoint.

The hook's parameter is a hash containing the authorization request parameters.

Sample code::

   use constant hook => {
       oidcGotRequest               => 'addScopeToRequest',
   };

   sub addScopeToRequest {
       my ( $self, $req, $oidc_request ) = @_;
       $oidc_request->{scope} = $oidc_request->{scope} . " my_hooked_scope";

       return PE_OK;
   }

oidcGenerateCode
~~~~~~~~~~~~~~~~

.. versionadded:: 2.0.12

This hook is triggered when LemonLDAP::NG is about to generate an Authorization Code for a Relying Party.

The hook's parameters are:

* A hash of the parameters for the OIDC Authorize request, which you can modify
* the configuration key of the relying party which will receive the token
* A hash of the session keys for the (internal) Authorization Code session

Sample code::

   use constant hook => {
       oidcGenerateCode              => 'modifyRedirectUri',
   };

   sub modifyRedirectUri {
       my ( $self, $req, $oidc_request, $rp, $code_payload ) = @_;
       my $original_uri = $oidc_request->{redirect_uri};
       $oidc_request->{redirect_uri} = "$original_uri?hooked=1";
       return PE_OK;
   }

oidcResolveScope
~~~~~~~~~~~~~~~~

.. versionadded:: 2.0.12

This hook is triggered when LemonLDAP::NG is resolving scopes (on authorization and token endpoints).

The hook's parameters are:

* An array ref of currently granted scopes, which you can modify
* The configuration key of the requested RP

Sample code::

   use constant hook => {
       oidcResolveScope          => 'addHardcodedScope',
   };

   sub addHardcodedScope{
       my ( $self, $req, $scopeList, $rp ) = @_;
       push @{$scopeList}, "myscope";
       return PE_OK;
   }

oidcValidateRedirectUri
~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.22.0

This hook lets you override the built-in URI validation logic

The hook's parameters are:

* The configuration key of the requested RP
* The redirect URI to validate
* The name of the endpoint that received the URI, such as

  * ``authorization``
  * ``end_session``

* A state object used to return the result of validation. Its subkeys are

  * ``result``: boolean result of the URI validation

.. warning::

    This hook must return ``PE_DONE`` in order to indicate that it performed
    URI validation. The result of validation must be set in ``$state->{result}``.

Sample code ::

    use URI;
    use constant hook => { oidcValidateRedirectUri => 'allowLocalhost' };

    sub allowLocalhost {
        my ( $self, $req, $rp, $uri, $endpoint, $state ) = @_;

        # If this RP is used by devs, allow all localhost urls
        if ( $rp =~ /^dev-/ ) {

            my $parsed_uri = URI->new($uri);
            if ( $parsed_uri->host eq "localhost" ) {

                # Force validation to succeed even if the URI
                # is not declared in LemonLDAP::NG config
                $state->{result} = 1;
                return PE_DONE;
            }
        }

        # Returning PE_OK means that we defer the decision to
        # LemonLDAP::NG's built-in logic, which is to only allow
        # explicitely declared URIs

        return PE_OK;
    }

oidcGenerateAuthorizationResponse
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.23.0

This hook is triggered when LemonLDAP::NG is about to send an authorization
response to a Relying Party (after the ``/oauth2/authorize`` endpoint completes).
It allows you to modify the response parameters before they are sent, for example
to implement JARM (JWT Secured Authorization Response Mode).

The hook's parameters are:

* A hash reference of the OIDC authorization request parameters
* The configuration key of the relying party
* A hash reference of response parameters that will be sent (can be modified)

The response_params hash may contain keys such as ``code``, ``state``,
``id_token``, ``access_token``, ``token_type``, ``expires_in``, ``scope``,
and ``session_state``, depending on the flow used.

Sample code::

   use constant hook => {
       oidcGenerateAuthorizationResponse => 'wrapResponse',
   };

   sub wrapResponse {
       my ( $self, $req, $oidc_request, $rp, $response_params ) = @_;

       # Add a custom parameter to the response
       $response_params->{custom_param} = "custom_value";

       return PE_OK;
   }


Token endpoint (/oauth2/token)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

oidcGotClientCredentialsGrant
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.0.12

This hook is triggered when LemonLDAP::NG successfully authorized a :ref:`Client Credentials Grant <client-credentials-grant>`.

The hook's parameters are:

* A hash of the current session info
* the configuration key of the relying party which is being identified

.. versionchanged:: 2.21.0
   You can use ``$info->{_scope}`` to update granted scopes in this hook

Sample code::

   use constant hook => {
       oidcGotClientCredentialsGrant => 'addSessionVariable',
   };

   sub addSessionVariable {
       my ( $self, $req, $info, $rp ) = @_;
       $info->{is_client_credentials} = 1;

       return PE_OK;
   }

oidcGotOnlineRefresh
~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.0.15

This hook is triggered when LemonLDAP::NG handles a Refresh Token grant for an
online session (user session still exists).

The hook's parameters are:

* the configuration key of the relying party which received the grant
* A hash of session data for the (internal) Refresh Token session
* A hash of the user's session data

.. versionchanged:: 2.23.0

   The hook now supports ``PE_SENDRESPONSE`` to return a custom response.

Sample code::

   use constant hook => {
       oidcGotOnlineRefresh          => 'logRefresh',
   };

   sub logRefresh {
       my ( $self, $req, $rp, $refreshInfo, $sessionInfo ) = @_;
       my $uid = $sessionInfo->{uid};
       $self->userLogger->info("OIDC application $rp requested a new access token for $uid");
       return PE_OK;
   }

oidcGotOfflineRefresh
~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.0.15

This hook is triggered when LemonLDAP::NG handles a Refresh Token grant for an
offline session (user session no longer exists).

The hook's parameters are:

* the configuration key of the relying party which received the grant
* A hash of session data for the (internal) Refresh Token session, which also
  contains user attributes

.. versionchanged:: 2.23.0

   The hook now supports ``PE_SENDRESPONSE`` to return a custom response.

Sample code::

   use constant hook => {
       oidcGotOfflineRefresh          => 'logRefreshOffline',
   };

   sub logRefreshOffline {
       my ( $self, $req, $rp, $refreshInfo ) = @_;
       my $uid = $refreshInfo->{uid};
       $self->userLogger->info("OIDC application $rp used offline access for $uid");
       return PE_OK;
   }

oidcGotTokenExchange
~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.0.16

This hook is triggered when LemonLDAP::NG handles an OAuth 2.0 Token Exchange request.

It allows you to implement your own custom Token Exchange flow.

You can look for parameters in ``$req``. If you find a combination of parameters that you support, set ``$req->response`` and return ``PE_SENDRESPONSE``. Else, return ``PE_OK`` to continue or ``PE_ERROR`` to fail.

Sample code::

    use constant hook => { oidcGotTokenExchange => 'tokenExchange', };

    sub tokenExchange {
        my ( $self, $req, $rp ) = @_;
        my $subject_token = $req->param('subject_token');
        my $subject_token_type = $req->param('subject_token_type');

        if ( #... ) {
            $req->response(
                $self->p->sendJSONresponse(
                    $req,
                    {
                        access_token => ...,
                        issued_token_type => ...,
                        token_type => ...,
                    }
                )
            );
            return PE_SENDRESPONSE;
        }
        return PE_OK;
    }

oidcGenerateTokenResponse
~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.19.0

This hook is triggered when LemonLDAP::NG has generated id_token and access_token
and is about to send the token response.

.. versionchanged:: 2.23.0

   This hook is now also called during refresh token grants.
   A sixth parameter ``$grant_type`` (``'authorization_code'`` or ``'refresh_token'``)
   is passed to distinguish between grant types.
   The hook now supports ``PE_SENDRESPONSE`` to return a custom response.

The hook's parameters are:

* the configuration key of the relying party which will receive the tokens
* the prepared response before JSON encoding
* the OIDC session content (code session or refresh session)
* the user's session content
* the grant type string (``'authorization_code'`` or ``'refresh_token'``)

Sample code::

    use constant hook => {
        oidcGenerateTokenResponse      => 'addTokenToResponse',
    };

    sub addTokenToResponse {
        my ( $self, $req, $rp, $tokensResponse, $oidcSession, $userSession, $grant_type ) = @_;
        $tokensResponse->{mySpecialToken} = 'randomValue';
        return PE_OK;
    }


oidcGotTokenRequest
~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.23.0

This hook is triggered when the token endpoint receives a request, before
processing any grant type. It allows plugins to:

* Modify request parameters for any grant type (authorization_code, refresh_token, etc.)
* Log or audit all token requests
* Handle custom grant types such as CIBA or Device Authorization Grant (RFC 8628)

The hook's parameters are:

* the configuration key of the relying party
* the grant_type string that was received

The hook should return:

* ``PE_OK`` to continue normal processing (use this when modifying parameters or logging)
* ``PE_SENDRESPONSE`` to take over the response (plugin must set ``$req->response``)

Sample code::

   use constant hook => {
       oidcGotTokenRequest => 'handleTokenRequest',
   };

   sub handleTokenRequest {
       my ( $self, $req, $rp, $grant_type ) = @_;

       # Log all token requests
       $self->logger->info("Token request for RP $rp with grant_type $grant_type");

       # Handle custom grant types
       if ( $grant_type eq 'urn:openid:params:grant-type:ciba' ) {
           # Handle CIBA grant
           $req->response( ... );
           return PE_SENDRESPONSE;
       }
       elsif ( $grant_type eq 'urn:ietf:params:oauth:grant-type:device_code' ) {
           # Handle Device Authorization Grant (RFC 8628)
           $req->response( ... );
           return PE_SENDRESPONSE;
       }

       # Continue with normal processing for standard grant types
       return PE_OK;
   }

Token generation
^^^^^^^^^^^^^^^^

oidcGenerateAccessToken
~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.0.12

This hook is triggered when LemonLDAP::NG is generating a JWT-formatted Access Token.

The hook's parameters are:

* A hash of the claims to be contained in the Access Token
* the configuration key of the relying party which will receive the token

.. versionadded:: 2.22.0

* A hash of additional JWT headers

Sample code::

   use constant hook => {
       oidcGenerateAccessToken          => 'addClaimToAccessToken',
   };

   sub addClaimToAccessToken {
       my ( $self, $req, $payload, $rp, $extra_headers ) = @_;
       $payload->{"access_token_hook"} = 1;
       return PE_OK;
   }

oidcGenerateRefreshToken
~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.23.0

This hook is triggered when LemonLDAP::NG is generating a Refresh Token.
It allows plugins to add data to the refresh token session or reject the
token generation by returning a non-PE_OK value.

The hook's parameters are:

* A hash of the refresh token session data (which you can modify)
* the configuration key of the relying party
* a boolean indicating if this is an offline refresh token (true) or online (false)

Sample code::

   use constant hook => {
       oidcGenerateRefreshToken => 'addDataToRefreshToken',
   };

   sub addDataToRefreshToken {
       my ( $self, $req, $refresh_info, $rp, $offline ) = @_;
       if ( my $data = $req->data->{my_data} ) {
           $refresh_info->{my_data} = $data;
       }
       # $offline is true for offline_access tokens
       $refresh_info->{is_offline} = $offline ? 1 : 0;
       return PE_OK;
   }

oidcGenerateIDToken
~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.0.10

This hook is triggered when LemonLDAP::NG is generating an ID Token.

The hook's parameters are:

* A hash of the claims to be contained in the ID Token
* the configuration key of the relying party which will receive the token

.. versionadded:: 2.22.0

* A hash of session information
* A hash of additional JWT headers

Sample code::

   use constant hook => {
       oidcGenerateIDToken          => 'addClaimToIDToken',
   };

   sub addClaimToIDToken {
       my ( $self, $req, $payload, $rp, $sessionInfo, $extra_headers ) = @_;
       $payload->{"id_token_hook"} = 1;
       return PE_OK;
   }


UserInfo endpoint (/oauth2/userinfo)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

oidcGenerateUserInfoResponse
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.0.10

This hook is triggered when LemonLDAP::NG is about to send a UserInfo response to a relying party on the `/oauth2/userinfo` endpoint, or to compute a list of claims to be added in an ID or Access Token.

The hook's parameter is a hash containing all the claims that are about to be released.

.. versionchanged:: 2.0.15
   Added the hash of current session data

Sample code::

   use constant hook => {
       oidcGenerateUserInfoResponse => 'addClaimToUserInfo',
   };

   sub addClaimToUserInfo {
       my ( $self, $req, $userinfo, $rp, $session_data) = @_;
       my $scope = $session_data->{_scope};
       $userinfo->{"userinfo_hook"} = 1;
       return PE_OK;
   }


Introspection endpoint (/oauth2/introspect)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

oidcGenerateIntrospectionResponse
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.23.0

This hook is triggered when LemonLDAP::NG is building the token introspection response.
It allows plugins to modify the introspection response.

The hook's parameters are:

* A hash of the introspection response (which you can modify)
* the configuration key of the relying party making the introspection request
* A hash of the access token session data

Sample code::

   use constant hook => {
       oidcGenerateIntrospectionResponse => 'modifyIntrospection',
   };

   sub modifyIntrospection {
       my ( $self, $req, $response, $rp, $token_data ) = @_;
       if ( my $data = $token_data->{my_data} ) {
           $response->{my_claim} = $data;
       }
       return PE_OK;
   }


Dynamic Registration (/oauth2/register)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

oidcGotRegistrationRequest
~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.23.0

This hook is triggered when LemonLDAP::NG receives an OAuth2 dynamic client
registration request. It is only called when
``oidcServiceAllowDynamicRegistration`` is enabled.

It allows custom plugins to filter or reject specific registration requests.

The hook receives the client registration metadata. To take over the response
(for example to reject the request), set ``$req->response`` and return
``PE_SENDRESPONSE``.

The hook should return:

* ``PE_OK`` to allow the registration to proceed normally
* ``PE_SENDRESPONSE`` to return ``$req->response`` as the final response

Sample code::

   use constant hook => {
       oidcGotRegistrationRequest => 'filterRegistration',
   };

   sub filterRegistration {
       my ( $self, $req, $client_metadata ) = @_;

       # Only allow native apps with loopback URIs
       my $redirect_uris = $client_metadata->{redirect_uris};
       return PE_OK unless $redirect_uris && ref($redirect_uris) eq 'ARRAY';

       foreach my $uri (@$redirect_uris) {
           unless ( $uri =~
               /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?(?:\/.*)?$/i )
           {
               $req->response(
                   $self->p->sendError( $req, 'invalid_request', 400 ) );
               return PE_SENDRESPONSE;
           }
       }

       return PE_OK;
   }

oidcRegisterClient
~~~~~~~~~~~~~~~~~~

.. versionadded:: 2.23.0

This hook is triggered just before saving the new RP configuration during
OAuth2 dynamic client registration.

It allows custom plugins to modify the new RP's configuration before it is
saved. The hook receives a ``$newRp`` hash reference with the same structure
as ``getOidcRpConfig``:

* ``confKey``: the configuration key for the new RP
* ``options``: hash of RP options (includes ``oidcRPMetaDataOptionsClientID``,
  ``oidcRPMetaDataOptionsClientSecret``, etc.)
* ``attributes``: hash of exported variables
* ``macros``: hash of macros
* ``scopeRules``: hash of scope rules
* ``extraClaims``: hash of extra claims

The ``$client_metadata`` is also passed as a second parameter for read-only
context. Modifying it has no effect on the saved configuration.

Sample code::

   use constant hook => {
       oidcRegisterClient => 'supportPublicClients',
   };

   sub supportPublicClients {
       my ( $self, $req, $newRp, $client_metadata ) = @_;
       my $token_endpoint_auth_method =
           $client_metadata->{token_endpoint_auth_method} || 'client_secret_basic';

       # Support public clients (no secret)
       if ( $token_endpoint_auth_method eq 'none' ) {
           $self->logger->info("Registering public client");
           delete $newRp->{options}->{oidcRPMetaDataOptionsClientSecret};
       }

       return PE_OK;
   }


Dynamic RP configuration
^^^^^^^^^^^^^^^^^^^^^^^^

getOidcRpConfig
~~~~~~~~~~~~~~~

.. versionadded:: 2.17

This hook is triggered when processing a request for a Client ID that is not
registered as an OIDC RP.

You can use this hook to inject configuration on-demand. Update `$config` with
the RP configuration ::

   use constant hook => {
      getOidcRpConfig => 'getRpFromDB',
   };

   sub getRpFromDB {
       my ( $self, $req, $client_id, $config ) = @_;

       # Lookup $entityID in some DB

       # Update the provided $config reference
       %$config = (
           confKey     => # a unique configuration key
           attributes  => # hashref of attributes to return
           options     => # hashref of options
           macros      => # hashref of macros to compute
           scopeRules  => # hashref of scope rules
           extraClaims => # hashref of additional scopes to consider
       );

       # If you don't set a TTL, the returned configuration is considered
       # permanently valid. Even if nothing was returned.
       $config->{ttl} = 3600;

       # If you return a non-OK status, the hook will be called again
       # on the next attempt to resolve the same entityID
       return PE_OK;
   }


OpenID Connect Authentication Hooks
-----------------------------------

oidcGenerateAuthenticationRequest
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.15

This hook is triggered when LemonLDAP::NG is building the Authentication Request that will be sent to an OpenID Provider

The hook's parameters are:

* The configuration key of the OP
* A hash reference of request parameters that will be added to the OP's ``authorization_endpoint``.

Sample code::

   use constant hook => {
       oidcGenerateAuthenticationRequest  => 'genAuthRequest',
   };

   sub genAuthRequest {
       my ( $self, $req, $op, $authorize_request_params ) = @_;

       $authorize_request_params->{my_param} = "my value";
       return PE_OK;
   }

.. versionchanged:: 2.23.0

Parameters specified in *Extra parameters for authorization code request* will
be exposed to this hook as well. If they start with a ``[`` or a ``{``, they
will be JSON-decoded automatically. LemonLDAP::NG will also automatically
JSON-encode and URL-encode them before adding them to the authorization
request.


oidcGenerateTokenRequest
^^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.15

This hook is triggered when LemonLDAP::NG is building the Token Request from that will be sent to an OpenID Provider

The hook's parameters are:

* The configuration key of the OP
* A hash reference of request parameters that will be sent in the body of the request to the ``token_endpoint``.

Sample code::

   use constant hook => {
       oidcGenerateTokenRequest => 'genTokenRequest',
   };

   sub genTokenRequest {
       my ( $self, $req, $op, $token_request_params) = @_;

       $token_request_params->{my_param} = "my value";
       return PE_OK;
   }

oidcGotIDToken
^^^^^^^^^^^^^^

.. versionadded:: 2.0.15

This hook is triggered after LemonLDAP::NG successfully received and decoded the ID Token from an external OpenID Provider

The hook's parameters are:

* The configuration key of the OP
* A hash reference of the decoded ID Token payload

Sample code::

   use constant hook => {
       oidcGotIDToken  => 'modifyIDToken',
   };

   sub modifyIDToken {
       my ( $self, $req, $op, $id_token_payload_hash ) = @_;

       # do some post-processing on the `sub` claim
       $id_token_payload_hash->{sub} = lc($id_token_payload_hash->{sub});
       return PE_OK;
   }

oidcGotUserInfo
^^^^^^^^^^^^^^^

.. versionadded:: 2.0.15

This hook is triggered after LemonLDAP::NG successfully received the UserInfo response from an external OpenID Provider

The hook's parameters are:

* The configuration key of the OP
* A hash reference of decoded UserInfo payload

Sample code::

   use constant hook => {
       oidcGotUserInfo  => 'modifyUserInfo',
   };

   sub modifyUserInfo {
       my ( $self, $req, $op, $userinfo_content ) = @_;

       # Custom attribute processing
       $userinfo_content->{my_attribute} = 1;
       return PE_OK;
   }

oidcGotAuthenticationResponse
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.23.0

This hook is triggered when LemonLDAP::NG receives an authentication response
(authorization callback) from an external OpenID Provider. It allows you to
process or modify the callback parameters before they are used, for example
to handle JARM (JWT Secured Authorization Response Mode) responses.

The hook's parameters are:

* A hash reference containing all callback parameters extracted from the request.
  You can read or modify these values. Only the values in this hash will be used
  by LemonLDAP::NG (not the raw request parameters).

Keys in the callback_params hash:

* ``state``: The state parameter (string or hashref, see below)
* ``code``: The authorization code
* ``error``: Error code if the authorization failed
* ``error_description``: Human-readable error description
* ``error_uri``: URI with more information about the error
* ``iss``: Issuer identifier (RFC 9207)

**State handling:**

* If ``state`` is a **scalar** (string), LemonLDAP::NG will call ``extractState``
  to restore session data from the state token.
* If ``state`` is a **hashref**, LemonLDAP::NG assumes your hook has already
  extracted the state and will use it directly. The hashref must contain:
  ``_oidcOPCurrent``, ``_oidcNonce``, and optionally ``urldc``.

This allows hooks that decode wrapped responses (like JARM) to call
``extractState`` themselves when they need the OP configuration to verify
the JWT signature.

Sample code::

   use constant hook => {
       oidcGotAuthenticationResponse => 'processCallback',
   };

   sub processCallback {
       my ( $self, $req, $callback_params ) = @_;

       # Check if this is a JARM response
       my $jarm_response = $req->param('response');
       if ($jarm_response) {
           # Decode JWT (without verification to get claims first)
           my $claims = decode_jwt($jarm_response);
           my $state = $claims->{state};

           # Extract state to get OP configuration (required before verification)
           $self->extractState($req, $state);
           my $op = $req->data->{_oidcOPCurrent};

           # Now verify the JWT with OP's keys
           # ... verification code ...

           # Set callback params from JWT claims
           # Keep state as scalar - LLNG will skip extractState since we already called it
           # Alternatively, pass extracted state as hashref to skip extractState
           $callback_params->{state} = {
               _oidcOPCurrent => $req->data->{_oidcOPCurrent},
               _oidcNonce     => $req->data->{_oidcNonce},
               urldc          => $req->data->{_url},
           };
           $callback_params->{code} = $claims->{code};
       }
       return PE_OK;
   }


SAML service hooks
------------------

getSamlConfig
^^^^^^^^^^^^^

.. versionadded:: 2.0.16

This hook is triggered when an entityID that is not registered in SAML
configuration (IDP or SP) is trying to communicate with LemonLDAP::NG

You can use this hook to inject configuration on-demand. Update `$config` with
the IDP or SP information::

   use constant hook => {
      getSamlConfig => 'getConfFromDB',
   };

   sub getConfFromDB {
       my ( $self, $req, $entityID, $config ) = @_;

       # Lookup $entityID in some DB

       # Update the provided $config reference
       %$config = (
           sp_metadata => #XML metadata
           sp_confKey => #configuration key
           sp_attributes => #hashref of exported attributes
           sp_options => #hashref of options
           sp_macros => #hashref of macros

           idp_metadata => #XML metadata
           idp_attributes => #hashref of exported attributes
           idp_options => #hashref of options
       );

       # If you don't set a TTL, the returned configuration is considered
       # permanently valid. Even if nothing was returned.
       $config->{ttl} = 3600;

       # If you return a non-OK status, the hook will be called again
       # on the next attempt to resolve the same entityID
       return PE_OK;
   }


SAML Issuer hooks
-----------------

samlGotAuthnRequest
^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.10

This hook is triggered when LemonLDAP::NG has received a SAML login request

The hook's parameter is the Lasso::Login object

Sample code::

   use constant hook => {
      samlGotAuthnRequest => 'gotRequest',
   };

   sub gotRequest {
       my ( $self, $req, $login ) = @_;

       # Your code here
   }

samlBuildAuthnResponse
^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.10

This hook is triggered when LemonLDAP::NG is about to build a response to the SAML login request

The hook's parameter is the Lasso::Login object

Sample code::

   use constant hook => {
      samlBuildAuthnResponse => 'buildResponse',
   };

   sub buildResponse {
       my ( $self, $req, $login ) = @_;

       # Your code here
   }

samlGotLogoutRequest
^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.10

This hook is triggered when LemonLDAP::NG has received a SAML logout request

The hook's parameter is the Lasso::Logout object

Sample code::

   use constant hook => {
      samlGotLogoutRequest => 'gotLogout',
   };

   sub gotLogout {
       my ( $self, $req, $logout ) = @_;

       # Your code here
   }

samlGotLogoutResponse
^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.10

This hook is triggered when LemonLDAP::NG has received a SAML logout response

The hook's parameter is the Lasso::Logout object

Sample code::

   use constant hook => {
      samlGotLogoutResponse => 'gotLogoutResponse',
   };

   sub gotLogoutResponse {
       my ( $self, $req, $logout ) = @_;

       # Your code here
   }

samlBuildLogoutResponse
^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.10

This hook is triggered when LemonLDAP::NG is about to generate a SAML logout response

The hook's parameter is the Lasso::Logout object

Sample code::

   use constant hook => {
      samlBuildLogoutResponse => 'buildLogoutResponse',
   };

   sub buildLogoutResponse {
       my ( $self, $req, $logout ) = @_;

       # Your code here
   }

SAML Authentication hooks
-------------------------

samlGenerateAuthnRequest
^^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.15

This hook is triggered when LemonLDAP::NG is building a SAML authentication request for an external IDP

The hook's parameters are:

* The configuration key of the IDP
* The ``Lasso::Login`` object

Sample code::

   use constant hook => {
       samlGenerateAuthnRequest    => 'genRequest',
   };

   sub genRequest {
       my ( $self, $req, $idp, $login ) = @_;

       # Your code here
   }

samlGotAuthnResponse
^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.15

This hook is triggered after LemonLDAP::NG successfully validated a SAML authentication response from an IDP

The hook's parameters are:

* The configuration key of the IDP
* The ``Lasso::Login`` object

Sample code::

   use constant hook => {
       samlGotAuthnResponse    => 'gotResponse',
   };

   sub gotResponse {
       my ( $self, $req, $idp, $login ) = @_;

       # Your code here
   }


CAS Issuer hooks
-----------------

casGotRequest
^^^^^^^^^^^^^

.. versionadded:: 2.0.12

This hook is triggered when LemonLDAP::NG received an CAS authentication request on the `/cas/login` endpoint.

The hook's parameter is a hash containing the CAS request parameters.

Sample code::

   use constant hook => {
       casGotRequest                 => 'filterService'
   };

   sub filterService {
       my ( $self, $req, $cas_request ) = @_;
       if ( $cas_request->{service} eq "http://auth.sp.com/" ) {
           return PE_OK;
       }
       else {
           return 999;
       }
   }


casGenerateServiceTicket
^^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.12

This hook is triggered when LemonLDAP::NG is about to generate a Service Ticket for a CAS application

The hook's parameters are:

* A hash of the parameters for the CAS request, which you can modify
* the configuration key of the cas application which will receive the ticket
* A hash of the session keys for the (internal) CAS session

Sample code::

   use constant hook => {
       'casGenerateServiceTicket'    => 'changeRedirectUrl',
   };

   sub changeRedirectUrl {
       my ( $self, $req, $cas_request, $app, $Sinfos ) = @_;
       $cas_request->{service} .= "?hooked=1";
       return PE_OK;
   }


casGenerateValidateResponse
^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.12

This hook is triggered when LemonLDAP::NG is about to send a CAS response to an application on the `/cas/serviceValidate` endpoint.

The hook's parameters are:

* The username (CAS principal)
* A hash of modifiable attributes to be sent

Sample code::

   use constant hook => {
       casGenerateValidateResponse    => 'addAttributes',
   };

   sub addAttributes {
       my ( $self, $req, $username, $attributes ) = @_;
       $attributes->{hooked} = 1;
       return PE_OK;
   }


DBI Authentication hooks
------------------------

dbiVerifyPassword
^^^^^^^^^^^^^^^^^

.. versionadded:: 2.20.0

This hook is triggered when LemonLDAP::NG is verifying a password hash
:doc:`obtained from a database <authdbi>`.  It will only run if *Dynamic
hashing* is enabled

You can use this hook to implement a custom hashing scheme

The hook's parameters are:

* The password submitted by the user
* The stored value, usually a hash, from the database
* A state object used to return information to Auth::DBI. Its subkeys are

  * ``result``: boolean result of the password verification

.. warning::

    This hook must return ``PE_DONE`` in order to indicate that it performed
    password validation. The result of validation (good password or bad
    password) must be set in ``$state->{result}``.

Sample code::

    use constant hook => { dbiVerifyPassword => 'verify_password', };

    sub verify_password {
        my ( $self, $req, $submitted_password, $stored_hash, $state ) = @_;

        # Check if the stored hash is understood by this plugin
        # TODO: adjust the regexp to match your custom scheme
        if ( $stored_hash =~ /^\$custom\$/ ) {

            # TODO: use your own verification method here
            # my $is_correct_password = ...

            $state->{result} = $is_correct_password;

            # Return DONE to indicate that the verification was done.
            # Other plugins will not be called
            return PE_DONE;
        }

      # Returning OK means that this plugin hasn't done anything
      # processing will continue to other plugins and default LemonLDAP::NG code
        return PE_OK;
    }


dbiHashPassword
^^^^^^^^^^^^^^^

.. versionadded:: 2.20.0

This hook is triggered when LemonLDAP::NG is about to
:doc:`set or change the user's password in a database <authdbi>`.
It will only run if *Dynamic hashing* is enabled.

You can use this hook to implement a new algorithm that admins can
configure as the *Hash scheme* for new passwords.

The hook's parameters are:

* The *Hash scheme* configured by the admin
* The password to be stored
* A state object used to return information to Password::DBI. Its subkeys are

  * ``hashed_password``: the hashed value that will be stored in the database

.. warning::

    This hook must return ``PE_DONE`` in order to indicate that it successfully
    hashed the password password validation.

Sample code::

    use constant hook => { dbiHashPassword => 'hash_new_password', };

    sub hash_new_password {
        my ( $self, $req, $scheme, $new_password, $result ) = @_;

        # Check if the desired scheme is implemented by this plugin
        # TODO: replace by the name of your custom scheme
        if ( $scheme eq "MyCustomScheme" ) {

            # TODO: use your own hashing method here
            # my $hashed_password = ...

            $result->{hashed_password} = $hashed_password;

            # Return DONE to indicate that the hashing was done.
            # Other plugins will not be called
            return PE_DONE;
        }

        # Returning OK means that this plugin hasn't done anything
        # processing will continue to other plugins and default
        # LemonLDAP::NG code
        return PE_OK;
    }

Choice Authentication hooks
---------------------------

getAuthChoice
^^^^^^^^^^^^^

.. versionadded:: 2.20.0

This hook is triggered when LemonLDAP::NG selects an :doc:`authentication choice <authchoice>`.

It is only run if no built-in mechanism has succeeded, and lets you select an
authentication choice from custom conditions instead of displaying a choice to
the user.

The hook's only parameter is a context hash which you can modify. Its subkeys are

* ``choice``: set this key to the desired choice, or leave it empty to show the standard choice forms

Sample code::

    use constant hook => { getAuthChoice => 'autoChoice', };

    sub autoChoice {
        my ( $self, $req, $context ) = @_;

        # Pick a default choice for all users on a certain IP
        if ( $req->address eq "1.2.3.4" ) {
            $context->{choice} = "1_DEMO";
        }
        return PE_OK;
    }



Password change hooks
---------------------


.. _passwordBeforeChange:

passwordBeforeChange
^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.12

This hook is triggered when LemonLDAP::NG is about to change or reset a user's password.
Returning an error will cancel the password change operation.
You can use this hook to implement :ref:`custom password policies <pluginppolicy>`.

The hook's parameters are:

* The main user identifier (warning: can be an email during the password reset flow)
* The new password
* The old password, if relevant

Sample code::

    use constant hook => { passwordBeforeChange => 'blacklistPassword', };

    sub blacklistPassword {
        my ( $self, $req, $_user, $password, $old ) = @_;

        # Note: because $_user can be either a username or an email,
        # using an explicit attribute from sessionInfo is more reliable
        my $user = $req->sessionInfo->{ $self->conf->{whatToTrace} };

        if ( $password eq "12345" ) {
            $self->logger->error( "Password change refused for $user : "
                  . "I've got the same combination on my luggage" );
            return PE_PP_INSUFFICIENT_PASSWORD_QUALITY;
        }
        return PE_OK;
    }


passwordAfterChange
^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.0.12

This hook is triggered after LemonLDAP::NG has changed the user's password successfully in the underlying password database

The hook's parameters are:

* The main user identifier (warning: can be an email during the password reset flow)
* The new password
* The old password, if relevant

Sample code::

    use constant hook => { passwordAfterChange => 'logPasswordChange', };

    sub logPasswordChange {
        my ( $self, $req, $_user, $password, $old ) = @_;
        $old ||= "";

        # Note: because $_user can be either a username or an email,
        # using an explicit attribute from sessionInfo is more reliable
        my $user = $req->sessionInfo->{ $self->conf->{whatToTrace} };

        # This is just an example, don't actually log a password!
        $self->userLogger->info(
            "Password changed for $user: $old -> $password");
        return PE_OK;
    }


Second factor hooks
-------------------

sfBeforeVerify
^^^^^^^^^^^^^^

.. versionadded:: 2.18

This hook is called immediately before LemonLDAP::NG calls the `verify` method
of each second factor implementation in order to verify the code/response
submitted by the user. You can use it to run additional checks.

The hook's parameters are:

* An instance of the 2F module in use
* A hash of session information

Sample code::

    use constant hook => { sfBeforeVerify => 'ipHasChanged', };

    sub ipHasChanged {
        my ( $self, $req, $sfa, $session ) = @_;
        my $prefix = $sfa->prefix;

        if ($req->address ne $session->{ipAddr}) {
            $self->logger->error("Error when validating $prefix: IP has changed");
            return PE_ERROR;
        }

        return PE_OK;
    }

sfAfterVerify
^^^^^^^^^^^^^

.. versionadded:: 2.20

This hook is called after verifying a second factor, whether it was successful or not.
You can use it to change the verification status depending on some condition.

The hook's parameters are:

* An instance of the 2F module in use
* A hash of session information
* A hash containing the verification result, which you can modify. Its subkeys are:

  * ``authenticationLevel``: authentication level granted by the module
  * ``device``: if applicable, details of the 2FA device used (name, type, epoch, etc.)
  * ``result``: result code of this attempt
  * ``retries``: how many retries are left after this attempt

Sample code::

    use constant hook => { sfAfterVerify => 'ipExceptions', };

    sub ipExceptions {
        my ( $self, $req, $module, $session, $verify_result ) = @_;

        my $status_of_verification = $verify_result->{result};

        # Override authentication level for a particular IP
        if ( $status_of_verification == PE_OK and $req->address eq "127.0.0.1" )
        {
            $verify_result->{authenticationLevel} = 7;
        }

        # This special IP has infinite retries
        if (    $status_of_verification != PE_OK
            and $req->address eq "127.0.0.2"
            and $verify_result->{retries} == 0 )
        {
            $self->logger->info("Allowing more retries to 127.0.0.2");
            $verify_result->{retries} = 1;
        }
        return PE_OK;
    }


sfBeforeRetry
^^^^^^^^^^^^^

.. versionadded:: 2.19

If a second factor failed and should be retried, this hook is called just before retrying.
You can use it to run additional checks before retrying, or reporting the
failure to a third party.

The hook's parameters are:

* An instance of the 2F module in use

Sample code::

    use constant hook => { sfBeforeRetry  => 'allowedToRetry' };

    sub allowedToRetry {
        my ( $self, $req, $module ) = @_;

        # $module contains an instance of the 2FA module
        my $prefix = $module->prefix;

        # Use $req->sessionInfo to get current session attributes
        my $uid = $req->sessionInfo->{uid};

        if ( $uid eq "msmith" ) {
            $self->logger->error("User $uid not allowed to retry $prefix");
            return PE_ERROR;
        }
        return PE_OK;
    }

sfRegisterDevice
^^^^^^^^^^^^^^^^

.. versionadded:: 2.20

This hook is called immediately before registering a new second factor device
into the user's persistent session.
You can use it to abort the operation unless some condition is met, to modify
the stored data, or to change the authentication level obtained immediately
after registration.

The hook's parameters are:

* A hash of session information
* A hash containing the second factor device data (type, name, timestamp, and implementation-specific fields)
* A hash containing the current registration state, which you can modify to change the authentication level. Its subkeys are

  * ``authenticationLevel``: authentication level granted by the module
  * ``module``: the object of the current 2fa module. Can be used to retrieve the type or prefix of 2fa

Sample code::

    use constant hook => { sfRegisterDevice => 'specialDeviceNames' };

    sub specialDeviceNames {
        my ( $self, $req, $info, $device, $registration_state ) = @_;

        # If the device name given by the user contains the word "insecure",
        # downgrade the authentication level
        if ( $device->{name} =~ /insecure/i ) {
            $registration_state->{authenticationLevel} = 1;
        }

        # If the device name contains the word "invalid",
        # abort registration
        if ( $device->{name} =~ /invalid/i ) {
            return PE_ERROR;
        }

        return PE_OK;
    }


sfDeleteDevice
^^^^^^^^^^^^^^

.. versionadded:: 2.23

This hook is called immediately before removing a second factor device
into the user's persistent session.
You can use it to abort the operation unless some condition is met.

The hook's parameters are:

* A hash of session information
* The second factor type,
* Device registration date
* A hash containing the current registration state. Its subkeys are

  * ``module``: the object of the current 2fa module. Can be used to retrieve the type or prefix of 2fa

Return value:

  * ``PE_OK`` for success, any other value means an error. In case of error, you could specify a custom error message that would be returned by the SSO. To do so, just add an ``errorLabel`` key in ``$registration_state`` hash, with the value you want the SSO to return.

Sample code::

    use constant hook => { sfDeleteDevice => 'checkUserBefore2fDelete' };

    sub checkUserBefore2fDelete {
        my ( $self, $req, $info, $type, $epoch, $registration_state ) = @_;

        my $module = $registration_state->{module};
        my $mail = $info->{mail};

        # Don't allow 2nd factor removal if user have no mail
        unless ( $mail ) {
            $registration_state->{errorLabel} = "deleteHookError";
            return  PE_ERROR;
        }

        return PE_OK;
    }


Generic hooks
-------------

sendHtml
^^^^^^^^

.. versionadded:: 2.17

This hook is called whenever the LemonLDAP::NG portal is about to generate a page using its template engine

The hook's parameters are:

* A reference to the template name (it can be changed by plugins)
* A reference to the hash of Lemonldap::NG::Common::sendHtml arguments, which
  includes template parameters and response code

Sample code::

    use constant hook => { sendHtml => 'injectParam', };

    sub injectParam {
        my ( $self, $req, $tpl, $args ) = @_;

        # Add variable to the "menu.tpl" template
        if ( $$tpl eq "menu" ) {
            $args->{params}->{MY_PARAM} = "xx";
        }
        return PE_OK;
    }

unauthLogout
^^^^^^^^^^^^

.. versionadded:: 2.22.0

This hook is called just before any logout. It receive only one parameter, the
`̀`$req`` object.
