Skip to content
Snippets Groups Projects
Select Git revision
  • 3faa7dd5ea373fedc6fb635e18118aa67b2c2d3d
  • main default
  • keycloak-deprecate
  • remove-jwt-easy
  • ci-update
  • v0.1.15
  • v0.1.14
  • v0.1.13
  • v0.1.12
  • v0.1.11
  • v0.1.10
  • v0.1.9
  • v0.1.8
  • v0.1.7
  • v0.1.6
  • v0.1.5
  • v0.1.4
  • v0.1.3
  • v0.1.2
  • v0.1.1
  • v0.1.0
21 results

LocalTokenValidator.php

Blame
    • Reiter, Christoph's avatar
      3faa7dd5
      Switch to the OIDC discover protocol for the provider config · 3faa7dd5
      Reiter, Christoph authored
      The goal is to support every OIDC server that implements the discover
      protocol (Keycloak for example). This allows us to fetch all the required
      information at runtime without the user having to keep the settings
      in sync with the used server. The config and public keys are cached for
      one hour.
      
      While in theory this works with non-keycloak it isn't tested yet, and we
      still need keycloak specific settings for the API docs auth because we only
      support keycloak with our frontend web components which we inject into the
      openapi docs.
      
      Fixes #3
      3faa7dd5
      History
      Switch to the OIDC discover protocol for the provider config
      Reiter, Christoph authored
      The goal is to support every OIDC server that implements the discover
      protocol (Keycloak for example). This allows us to fetch all the required
      information at runtime without the user having to keep the settings
      in sync with the used server. The config and public keys are cached for
      one hour.
      
      While in theory this works with non-keycloak it isn't tested yet, and we
      still need keycloak specific settings for the API docs auth because we only
      support keycloak with our frontend web components which we inject into the
      openapi docs.
      
      Fixes #3
    LocalTokenValidator.php 3.07 KiB
    <?php
    
    declare(strict_types=1);
    
    namespace Dbp\Relay\AuthBundle\Authenticator;
    
    use Dbp\Relay\AuthBundle\OIDC\OIDError;
    use Dbp\Relay\AuthBundle\OIDC\OIDProvider;
    use Jose\Component\Core\JWKSet;
    use Jose\Easy\Load;
    use Jose\Easy\Validate;
    
    class LocalTokenValidator extends TokenValidatorBase
    {
        private $oidProvider;
        private $leewaySeconds;
    
        public function __construct(OIDProvider $oidProvider, int $leewaySeconds)
        {
            $this->oidProvider = $oidProvider;
            $this->leewaySeconds = $leewaySeconds;
        }
    
        /**
         * Validates the token locally using the public JWK of the OIDC server.
         *
         * This is faster because everything can be cached, but tokens/sessions revoked on the OIDC server
         * will still be considered valid as long as they are not expired.
         *
         * @return array the token
         *
         * @throws TokenValidationException
         */
        public function validate(string $accessToken): array
        {
            try {
                $jwks = $this->oidProvider->getJWKs();
                $providerConfig = $this->oidProvider->getProviderConfig();
            } catch (OIDError $e) {
                throw new TokenValidationException($e->getMessage());
            }
    
            $issuer = $providerConfig->getIssuer();
            // Allow the same algorithms that the introspection endpoint allows
            $algs = $providerConfig->getIntrospectionEndpointSigningAlgorithms();
            // The spec doesn't allow this, but just to be sure
            assert(!in_array('none', $algs, true));
    
            // Checks not needed/used here:
            // * sub(): This is the keycloak user ID by default, nothing we know beforehand
            // * jti(): Nothing we know beforehand
            // * aud(): The audience needs to be checked afterwards with checkAudience()
            try {
                $keySet = JWKSet::createFromKeyData($jwks);
                $validate = Load::jws($accessToken);
                $validate = $validate
                    ->algs($algs)
                    ->keyset($keySet)
                    ->exp($this->leewaySeconds)
                    ->iat($this->leewaySeconds)
                    ->nbf($this->leewaySeconds)
                    ->iss($issuer);
                assert($validate instanceof Validate);
                $jwtResult = $validate->run();
            } catch (\Exception $e) {
                throw new TokenValidationException('Token validation failed: '.$e->getMessage());
            }
    
            $jwt = $jwtResult->claims->all();
    
            // XXX: Keycloak will add extra data to the token returned by introspection, mirror this behaviour here
            // to avoid breakage when switching between local/remote validation.
            // https://github.com/keycloak/keycloak/blob/8225157a1cecef30034530aa/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java#L59
            if (isset($jwt['preferred_username'])) {
                $jwt['username'] = $jwt['preferred_username'];
            }
            if (!isset($jwt['username'])) {
                $jwt['username'] = null;
            }
            if (isset($jwt['azp'])) {
                $jwt['client_id'] = $jwt['azp'];
            }
            $jwt['active'] = true;
    
            return $jwt;
        }
    }