├── src ├── HappyrAuth0Bundle.php ├── Security │ ├── Auth0UserProviderInterface.php │ ├── Passport │ │ └── Auth0Badge.php │ ├── Auth0EntryPoint.php │ └── Authentication │ │ └── Auth0Authenticator.php ├── Resources │ └── config │ │ └── services.yml ├── DependencyInjection │ ├── HappyrAuth0Extension.php │ └── Configuration.php └── Model │ └── UserInfo.php ├── LICENSE ├── composer.json ├── CHANGELOG.md └── README.md /src/HappyrAuth0Bundle.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface Auth0UserProviderInterface 15 | { 16 | /** 17 | * @throws UserNotFoundException if the user is not found 18 | * 19 | * @return UserInterface 20 | */ 21 | public function loadByUserModel(UserInfo $userInfo); 22 | } 23 | -------------------------------------------------------------------------------- /src/Security/Passport/Auth0Badge.php: -------------------------------------------------------------------------------- 1 | user = $user; 17 | } 18 | 19 | public function getUserInfo(): UserInfo 20 | { 21 | return $this->user; 22 | } 23 | 24 | public function isResolved(): bool 25 | { 26 | return true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | # default configuration for services in *this* file 3 | _defaults: 4 | autowire: true # Automatically injects dependencies in your services. 5 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 6 | 7 | Auth0\SDK\Configuration\SdkConfiguration: 8 | shared: false 9 | 10 | Auth0\SDK\Auth0: 11 | arguments: 12 | $configuration: '@Auth0\SDK\Configuration\SdkConfiguration' 13 | 14 | Auth0\SDK\API\Management: 15 | arguments: 16 | $configuration: '@Auth0\SDK\Configuration\SdkConfiguration' 17 | 18 | Auth0\SDK\API\Authentication: 19 | arguments: 20 | $configuration: '@Auth0\SDK\Configuration\SdkConfiguration' 21 | 22 | Happyr\Auth0Bundle\Security\Authentication\Auth0Authenticator: ~ 23 | auth0.authenticator: '@Happyr\Auth0Bundle\Security\Authentication\Auth0Authenticator' 24 | 25 | Happyr\Auth0Bundle\Security\Auth0EntryPoint: ~ 26 | auth0.entry_point: '@Happyr\Auth0Bundle\Security\Auth0EntryPoint' 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Happyr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happyr/auth0-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Symfony integration with auth0", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Tobias Nyholm", 9 | "email": "tobias.nyholm@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=7.4", 14 | "auth0/auth0-php": "^8.0.0", 15 | "psr/cache": "^1.0 || ^2.0 || ^3.0", 16 | "psr/log": "^1.0", 17 | "symfony/config": "^5.2", 18 | "symfony/framework-bundle": "^5.2", 19 | "symfony/security-bundle": "^5.3.3", 20 | "symfony/security-core": "^5.3" 21 | }, 22 | "require-dev": { 23 | "nyholm/nsa": "^1.3", 24 | "nyholm/psr7": "^1.1", 25 | "nyholm/symfony-bundle-test": "^1.8", 26 | "php-http/message-factory": "^1.0.2", 27 | "symfony/http-client": "^5.2", 28 | "symfony/phpunit-bridge": "^5.2" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Happyr\\Auth0Bundle\\": "src" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Happyr\\Auth0Bundle\\Tests\\": "tests" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Security/Auth0EntryPoint.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Auth0EntryPoint implements AuthenticationEntryPointInterface 16 | { 17 | private Auth0 $auth0; 18 | private UrlGeneratorInterface $urlGenerator; 19 | private string $loginCheckRoute; 20 | private string $targetPathParameter; 21 | 22 | public function __construct(Auth0 $auth0, UrlGeneratorInterface $urlGenerator, string $loginCheckRoute, string $targetPathParameter) 23 | { 24 | $this->auth0 = $auth0; 25 | $this->urlGenerator = $urlGenerator; 26 | $this->loginCheckRoute = $loginCheckRoute; 27 | $this->targetPathParameter = $targetPathParameter; 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function start(Request $request, AuthenticationException $authException = null) 34 | { 35 | $redirectUrl = $this->urlGenerator->generate($this->loginCheckRoute, [ 36 | $this->targetPathParameter => $request->getUri(), 37 | ], UrlGeneratorInterface::ABSOLUTE_URL); 38 | 39 | return new RedirectResponse( 40 | $this->auth0->login($redirectUrl, ['ui_locales' => $request->getLocale()]) 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. 4 | 5 | ## 0.8.1 6 | 7 | ### Fixed 8 | 9 | - Compatibility with Auth0 8.0.0 10 | 11 | ## 0.8.0 12 | 13 | ### Added 14 | 15 | - Updated `UserInfo` to reflect the [Auth0 user model](https://auth0.com/docs/users/user-profile-structure) 16 | 17 | ### Changed 18 | 19 | - [BC break] The `UserInfo::getUserId()` does not use `"sub"` anymore. It goes directly to `"user_id"`. Consider using `UserInfo::getLoginIdentifier()` instead 20 | - [BC break] The configuration has moved around to directly pass config values to the SDK. See readme or dump configuration reference. 21 | 22 | ### Removed 23 | 24 | - [BC break] Removed `ManagementFactory`. 25 | - [BC break] DI container parameters 26 | 27 | ## 0.7.0 28 | 29 | - Removed most things and added support for Symfony 5.2 30 | 31 | ## 0.6.2 32 | 33 | - Better check type in SSOToken::getRoles() 34 | 35 | ## 0.6.1 36 | 37 | - Removed use of deprecated code 38 | 39 | ## 0.6.0 40 | 41 | - Adding Symfony 5 support 42 | 43 | ## 0.5.3 44 | 45 | - Dont warn about deprecation notices when Symfony is calling the getRoles() 46 | 47 | ## 0.5.2 48 | 49 | - Added support for custom SSO domains 50 | 51 | ## 0.5.1 52 | 53 | - Added extra checks so we dont access array keys that do not exist 54 | 55 | ## 0.5.0 56 | 57 | - Added correct language parameter to Universal SSO 58 | - Removed code not used 59 | - Added PHP7.1 type hints 60 | - Removed fluid functions 61 | 62 | ## 0.4.0 63 | 64 | - Set Management to a lazy service 65 | - Added support for Sf 4.3 66 | - Removed support for Sf 2.8 67 | - Removed `UserInfo::getSub()` 68 | - Added scope to `SSOProvicer` 69 | 70 | ## 0.3.0 71 | 72 | - Make sure we can access `Token` from Auth0. 73 | - Make sure `$auth0Data` is actually a `Token`. 74 | - Added options for `scope` and `audience`. 75 | 76 | ## 0.2.3 77 | 78 | - Require 6.0.0-alpha.2 79 | 80 | ## 0.2.2 81 | 82 | - Handle exceptions better 83 | 84 | ## 0.2.1 85 | 86 | - Make sure we do not store an empty access_token in cache 87 | - Added previous exception to SSOProvider 88 | - Fixed deserialisation of isAuthenticated in SSOToken. 89 | 90 | ## 0.2.0 91 | 92 | ### Changed 93 | 94 | We removed our custom API implementation and started to use the official Auth0 API client. 95 | 96 | ### Added 97 | 98 | We added better Management API 99 | -------------------------------------------------------------------------------- /src/Security/Authentication/Auth0Authenticator.php: -------------------------------------------------------------------------------- 1 | locator = $locator; 34 | $this->loginCheckRoute = $loginCheckRoute; 35 | } 36 | 37 | public static function getSubscribedServices() 38 | { 39 | return [ 40 | Auth0::class, 41 | HttpUtils::class, 42 | AuthenticationSuccessHandlerInterface::class, 43 | AuthenticationFailureHandlerInterface::class, 44 | '?'.Auth0UserProviderInterface::class, 45 | ]; 46 | } 47 | 48 | public function supports(Request $request): ?bool 49 | { 50 | return $request->attributes->get('_route') === $this->loginCheckRoute; 51 | } 52 | 53 | public function authenticate(Request $request): PassportInterface 54 | { 55 | $auth0 = $this->get(Auth0::class); 56 | 57 | try { 58 | if (null === $auth0->getCredentials()) { 59 | if (null === $auth0->getExchangeParameters()) { 60 | throw new AuthenticationException('Missing auth0 code exchange parameters'); 61 | } 62 | 63 | $redirectUri = $this->get(HttpUtils::class)->generateUri($request, $this->loginCheckRoute); 64 | $auth0->exchange($redirectUri); 65 | } 66 | 67 | $auth0User = $auth0->getUser(); 68 | if (!is_array($auth0User)) { 69 | throw new AuthenticationException('Could not get user data from Auth0'); 70 | } 71 | 72 | $userModel = UserInfo::create($auth0User); 73 | } catch (Auth0Exception $e) { 74 | throw new AuthenticationException($e->getMessage(), (int) $e->getCode(), $e); 75 | } 76 | 77 | $userProviderCallback = null; 78 | if (null !== $up = $this->get(Auth0UserProviderInterface::class)) { 79 | $userProviderCallback = static function () use ($up, $userModel) { 80 | return $up->loadByUserModel($userModel); 81 | }; 82 | } 83 | 84 | return new SelfValidatingPassport(new UserBadge($userModel->getLoginIdentifier(), $userProviderCallback), [new Auth0Badge($userModel)]); 85 | } 86 | 87 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response 88 | { 89 | return $this->get(AuthenticationSuccessHandlerInterface::class)->onAuthenticationSuccess($request, $token); 90 | } 91 | 92 | public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response 93 | { 94 | return $this->get(AuthenticationFailureHandlerInterface::class)->onAuthenticationFailure($request, $exception); 95 | } 96 | 97 | /** 98 | * @template T of object 99 | * @psalm-param class-string $service 100 | * 101 | * @return T|null 102 | */ 103 | private function get(string $service) 104 | { 105 | if ($this->locator->has($service)) { 106 | return $this->locator->get($service); 107 | } 108 | 109 | return null; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/DependencyInjection/HappyrAuth0Extension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 34 | 35 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 36 | $loader->load('services.yml'); 37 | 38 | $this->configureSdkConfiguration($configuration, $container, $config['sdk'] ?? []); 39 | $this->configureFirewall($container, $config['firewall'] ?? []); 40 | } 41 | 42 | private function configureSdkConfiguration(Configuration $configuration, ContainerBuilder $container, array $config) 43 | { 44 | $sdkConfigurationDefinition = $container->getDefinition(SdkConfiguration::class); 45 | $sdkConfigurationDefinition->setArgument('$configuration', null); 46 | 47 | foreach ($config as $key => $value) { 48 | if (null !== $value && $configuration->isArgumentObject($key)) { 49 | $value = new Reference($value); 50 | } 51 | 52 | $sdkConfigurationDefinition->setArgument('$'.$key, $value); 53 | } 54 | } 55 | 56 | private function configureFirewall(ContainerBuilder $container, array $config) 57 | { 58 | if (!$config['enabled']) { 59 | $container->removeDefinition(Auth0Authenticator::class); 60 | $container->removeDefinition(Auth0EntryPoint::class); 61 | 62 | return; 63 | } 64 | 65 | if (!(null === $config['success_handler'] xor null === $config['default_target_path'])) { 66 | throw new \LogicException('You must define either "happyr_auth0.firewall.default_target_path" or "happyr_auth0.firewall.success_handler". Exactly one of them, not both.'); 67 | } 68 | 69 | if (!(null === $config['failure_handler'] xor null === $config['failure_path'])) { 70 | throw new \LogicException('You must define either "happyr_auth0.firewall.failure_path" or "happyr_auth0.firewall.failure_handler". Exactly one of them, not both.'); 71 | } 72 | 73 | if (null === $successHandler = $config['success_handler']) { 74 | $def = $container->setDefinition($successHandler = 'happyr_auth0.success_handler', new ChildDefinition('security.authentication.success_handler')); 75 | $def->replaceArgument(1, ['default_target_path' => $config['default_target_path']]); 76 | } 77 | 78 | if (null === $failureHandler = $config['failure_handler']) { 79 | $def = $container->setDefinition($failureHandler = 'happyr_auth0.failure_handler', new ChildDefinition('security.authentication.failure_handler')); 80 | $def->replaceArgument(2, ['failure_path' => $config['failure_path']]); 81 | } 82 | 83 | $def = $container->getDefinition(Auth0EntryPoint::class); 84 | $def->setArgument('$loginCheckRoute', $config['check_route']); 85 | $def->setArgument('$targetPathParameter', $config['target_path_parameter']); 86 | 87 | $def = $container->getDefinition(Auth0Authenticator::class); 88 | $def->setArgument('$loginCheckRoute', $config['check_route']); 89 | $def->addTag('container.service_subscriber', ['key' => AuthenticationFailureHandlerInterface::class, 'id' => $failureHandler]); 90 | $def->addTag('container.service_subscriber', ['key' => AuthenticationSuccessHandlerInterface::class, 'id' => $successHandler]); 91 | 92 | if (!empty($config['user_provider'])) { 93 | $def->addTag('container.service_subscriber', ['key' => Auth0UserProviderInterface::class, 'id' => $config['user_provider']]); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | argumentObjects); 20 | } 21 | 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function getConfigTreeBuilder() 26 | { 27 | $treeBuilder = new TreeBuilder('happyr_auth0'); 28 | /** @var ArrayNodeDefinition $root */ 29 | $root = $treeBuilder->getRootNode(); 30 | 31 | $this->addAuth0SdkConfiguration($root); 32 | 33 | $root 34 | ->children() 35 | ->arrayNode('firewall')->canBeEnabled() 36 | ->children() 37 | ->scalarNode('check_route')->isRequired()->info('The route where the user ends up after authentication. Ie, the callback route.')->cannotBeEmpty()->end() 38 | ->scalarNode('failure_path')->defaultNull()->info('The path or route where user is redirected on authentication failure.')->end() 39 | ->scalarNode('failure_handler')->defaultNull()->info('A service implementing AuthenticationFailureHandlerInterface.')->end() 40 | ->scalarNode('default_target_path')->defaultNull()->info('The path or route where user is redirected on authentication success.')->end() 41 | ->scalarNode('success_handler')->defaultNull()->info('A service implementing AuthenticationSuccessHandlerInterface.')->end() 42 | ->scalarNode('user_provider')->defaultNull()->info('A service implementing Auth0UserProviderInterface. If none provided, the user provider of the firewall will be used.')->end() 43 | ->scalarNode('target_path_parameter')->defaultValue('_target_path')->info('Name of the query parameter where we store the target path.')->end() 44 | ->end() 45 | ->end() 46 | 47 | ->end(); 48 | 49 | return $treeBuilder; 50 | } 51 | 52 | private function addAuth0SdkConfiguration(ArrayNodeDefinition $rootNode): void 53 | { 54 | $sdkNode = $rootNode 55 | ->children() 56 | ->arrayNode('sdk') 57 | ->info('This node can be configured using key specified in the SDK documentation: https://github.com/auth0/auth0-PHP#configuration-options (only configuration parameter is forbidden).') 58 | ->children(); 59 | 60 | $sdkConfigurationRefClass = new \ReflectionClass(SdkConfiguration::class); 61 | $constructor = $sdkConfigurationRefClass->getConstructor(); 62 | 63 | foreach ($constructor->getParameters() as $parameter) { 64 | if ('configuration' === $parameter->getName()) { 65 | continue; 66 | } 67 | 68 | switch (true) { 69 | case $parameter->getType() instanceof \ReflectionNamedType && 'array' === $parameter->getType()->getName(): 70 | $node = $sdkNode 71 | ->arrayNode($parameter->getName()) 72 | ->scalarPrototype() 73 | ; 74 | break; 75 | case $parameter->getType() instanceof \ReflectionNamedType && 'bool' === $parameter->getType()->getName(): 76 | $node = $sdkNode 77 | ->booleanNode($parameter->getName()) 78 | ; 79 | break; 80 | case $parameter->getType() instanceof \ReflectionNamedType && 'int' === $parameter->getType()->getName(): 81 | $node = $sdkNode 82 | ->integerNode($parameter->getName()) 83 | ; 84 | break; 85 | default: 86 | if ($parameter->getType() instanceof \ReflectionNamedType && !$parameter->getType()->isBuiltin()) { 87 | $this->argumentObjects[] = $parameter->getName(); 88 | } 89 | 90 | $node = $sdkNode 91 | ->scalarNode($parameter->getName()) 92 | ; 93 | break; 94 | } 95 | 96 | $node->defaultValue($parameter->isOptional() ? $parameter->getDefaultValue() : null); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Model/UserInfo.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class UserInfo implements \ArrayAccess 16 | { 17 | /** 18 | * @var array the raw data from the API 19 | */ 20 | private $data; 21 | 22 | private function __construct(array $data) 23 | { 24 | $this->data = $data; 25 | } 26 | 27 | public static function create(array $data): UserInfo 28 | { 29 | return new self($data); 30 | } 31 | 32 | public function __toString() 33 | { 34 | if (!empty($this->data['email'])) { 35 | return (string) $this->data['email']; 36 | } 37 | 38 | if (!empty($this->data['name'])) { 39 | return (string) $this->data['name']; 40 | } 41 | 42 | if (!empty($this->data['nickname'])) { 43 | return (string) $this->data['nickname']; 44 | } 45 | 46 | return ''; 47 | } 48 | 49 | public function offsetExists($offset): bool 50 | { 51 | return array_key_exists($offset, $this->data); 52 | } 53 | 54 | public function offsetGet($offset) 55 | { 56 | return $this->data[$offset]; 57 | } 58 | 59 | public function offsetSet($offset, $value): void 60 | { 61 | throw new \LogicException('The UserInfo object is read only'); 62 | } 63 | 64 | public function offsetUnset($offset): void 65 | { 66 | throw new \LogicException('The UserInfo object is read only'); 67 | } 68 | 69 | public function isEmailVerified(): bool 70 | { 71 | return $this->data['email_verified'] ?? false; 72 | } 73 | 74 | public function getEmail(): ?string 75 | { 76 | return $this->data['email'] ?? null; 77 | } 78 | 79 | public function gePhoneNumber(): ?string 80 | { 81 | return $this->data['phone_number'] ?? null; 82 | } 83 | 84 | public function isBlocked(): ?bool 85 | { 86 | return $this->data['blocked'] ?? null; 87 | } 88 | 89 | public function getUsername(): ?string 90 | { 91 | return $this->data['username'] ?? null; 92 | } 93 | 94 | public function getClientId(): ?string 95 | { 96 | return $this->data['clientID'] ?? null; 97 | } 98 | 99 | public function getUpdatedAt(): ?\DateTimeImmutable 100 | { 101 | return isset($this->data['updated_at']) ? new \DateTimeImmutable($this->data['updated_at']) : null; 102 | } 103 | 104 | public function getName(): ?string 105 | { 106 | return $this->data['name'] ?? $this->data['nickname'] ?? null; 107 | } 108 | 109 | public function getGivenName(): ?string 110 | { 111 | return $this->data['given_name'] ?? null; 112 | } 113 | 114 | public function getFamilyName(): ?string 115 | { 116 | return $this->data['family_name'] ?? null; 117 | } 118 | 119 | public function getPicture(): ?string 120 | { 121 | return $this->data['picture'] ?? null; 122 | } 123 | 124 | public function getNickname(): ?string 125 | { 126 | return $this->data['nickname'] ?? null; 127 | } 128 | 129 | public function getLastIp(): ?string 130 | { 131 | return $this->data['last_ip'] ?? null; 132 | } 133 | 134 | public function getMultifactor(): ?string 135 | { 136 | return $this->data['multifactor'] ?? null; 137 | } 138 | 139 | public function getLoginsCount(): int 140 | { 141 | return $this->data['logins_count'] ?? 0; 142 | } 143 | 144 | public function getIdentities(): array 145 | { 146 | return $this->data['identities'] ?? []; 147 | } 148 | 149 | public function getCreatedAt(): ?\DateTimeInterface 150 | { 151 | return isset($this->data['created_at']) ? new \DateTimeImmutable($this->data['created_at']) : null; 152 | } 153 | 154 | public function getLastLoginAt(): ?\DateTimeInterface 155 | { 156 | return isset($this->data['last_login']) ? new \DateTimeImmutable($this->data['last_login']) : null; 157 | } 158 | 159 | public function getLastPasswordResetAt(): ?\DateTimeInterface 160 | { 161 | return isset($this->data['last_password_reset']) ? new \DateTimeImmutable($this->data['last_password_reset']) : null; 162 | } 163 | 164 | /** 165 | * This is the user id from Auth0. If the user is logged in with a social provider 166 | * this could be null. 167 | */ 168 | public function getUserId(): ?string 169 | { 170 | return $this->data['user_id'] ?? null; 171 | } 172 | 173 | /** 174 | * This is a unique id for this user or login method. An Auth0 user may have 175 | * multiple "login identifiers". So this is "more unique" than the user id. 176 | */ 177 | public function getLoginIdentifier(): string 178 | { 179 | return $this->getUserId() ?? $this->data['sub']; 180 | } 181 | 182 | public function getRoles(): array 183 | { 184 | return $this->data['roles'] ?? []; 185 | } 186 | 187 | public function getAppMetadata(string $name, $default = null) 188 | { 189 | if (!isset($this->data['app_metadata'])) { 190 | return $default; 191 | } 192 | 193 | if (!array_key_exists($name, $this->data['app_metadata'])) { 194 | return $default; 195 | } 196 | 197 | return $this->data['app_metadata'][$name]; 198 | } 199 | 200 | public function getUserMetadata(string $name, $default = null) 201 | { 202 | if (!isset($this->data['user_metadata'])) { 203 | return $default; 204 | } 205 | 206 | if (!array_key_exists($name, $this->data['user_metadata'])) { 207 | return $default; 208 | } 209 | 210 | return $this->data['user_metadata'][$name]; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auth0 integration with Symfony 2 | 3 | [![Latest Version](https://img.shields.io/github/release/Happyr/auth0-bundle.svg?style=flat-square)](https://github.com/Happyr/auth0-bundle/releases) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/happyr/auth0-bundle.svg?style=flat-square)](https://packagist.org/packages/happyr/auth0-bundle) 6 | 7 | Integrate the new authentication system from Symfony 5.2 with Auth0. 8 | 9 | ### Installation 10 | 11 | Install with Composer: 12 | 13 | ```bash 14 | composer require happyr/auth0-bundle 15 | ``` 16 | 17 | Enable the bundle in bundles.php 18 | 19 | ```php 20 | return [ 21 | // ... 22 | Happyr\Auth0Bundle\HappyrAuth0Bundle::class => ['all' => true], 23 | ]; 24 | ``` 25 | 26 | Add your credentials and basic settings. 27 | 28 | ```yaml 29 | // config/packages/happyr_auth0.yaml 30 | happyr_auth0: 31 | # In the sdk node, you can provide every settings provided by the auth0/auth0-PHP library 32 | # (https://github.com/auth0/auth0-PHP#configuration-options). 33 | # Only the "configuration" argument is not authorized. 34 | # For every parameter that reference an object, you must provide a service name. 35 | sdk: 36 | domain: '%env(AUTH0_DOMAIN)%' 37 | clientId: '%env(AUTH0_CLIENT_ID)%' 38 | clientSecret: '%env(AUTH0_SECRET)%' 39 | tokenCache: 'cache.app' # will reference the @cache.app service automatically 40 | managementTokenCache: 'cache.app' 41 | cookieSecret: '%kernel.secret%' # To encrypt cookie values 42 | scope: 43 | - openid # "openid" is required. 44 | - profile 45 | - email 46 | ``` 47 | 48 | You are now up and running and can use services `Auth0\SDK\Auth0`, `Auth0\SDK\API\Authentication`, 49 | `Auth0\SDK\API\Management` and `Auth0\SDK\Configuration\SdkConfiguration`. 50 | 51 | If you want to integrate with the authentication system there are a bit more configuration you may do. 52 | 53 | ## Authentication 54 | 55 | Start by telling Symfony what entrypoint we use and add `auth0.authenticator` as 56 | "custom authenticator". This will make Symfony aware of the Auth0Bundle and how to 57 | use it. 58 | 59 | ```yaml 60 | // config/packages/security.yml 61 | security: 62 | enable_authenticator_manager: true # Use the new authentication system 63 | 64 | # Example user provider 65 | providers: 66 | users: 67 | entity: 68 | class: 'App\Entity\User' 69 | property: 'auth0Id' 70 | 71 | firewalls: 72 | default: 73 | pattern: ^/.* 74 | 75 | # Specify the entrypoint 76 | entry_point: auth0.entry_point 77 | 78 | # Add custom authenticator 79 | custom_authenticators: 80 | - auth0.authenticator 81 | 82 | # Example logout path 83 | logout: 84 | path: default_logout 85 | target: _user_logout 86 | invalidate_session: true 87 | ``` 88 | 89 | Next we need to configure the behavior of the bundle. 90 | 91 | ```yaml 92 | // config/packages/happyr_auth0.yaml 93 | happyr_auth0: 94 | # ... 95 | 96 | firewall: 97 | # If a request comes into route default_login_check, we will intercept 98 | # it and redirect the user to auth0. 99 | check_route: default_login_check 100 | 101 | # The path or route where to redirect users on failure 102 | failure_path: default_logout 103 | 104 | # The default path or route to redirect users after login 105 | default_target_path: user_dashboard 106 | ``` 107 | 108 | The `failure_path` and `default_target_path` will use `Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler` 109 | and `Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler` 110 | to handle redirects. 111 | 112 | You may use your own handlers by specifying the service ids: 113 | 114 | ```yaml 115 | // config/packages/happyr_auth0.yaml 116 | happyr_auth0: 117 | # ... 118 | 119 | firewall: 120 | # If a request comes into route default_login_check, we will intercept 121 | # it and redirect the user to auth0. 122 | check_route: default_login_check 123 | 124 | failure_handler: App\Security\AuthenticationHandler\MyFailureHandler 125 | success_handler: App\Security\AuthenticationHandler\MySuccessHandler 126 | ``` 127 | 128 | ### Custom user provider 129 | 130 | If you want to use a custom UserProvider that fetches a user with more data than 131 | just the Auth0 id, then you may create a service that implement `Happyr\Auth0Bundle\Security\Auth0UserProviderInterface`. 132 | 133 | Then configure the bundle to use that service: 134 | 135 | ```yaml 136 | // config/packages/happyr_auth0.yaml 137 | happyr_auth0: 138 | # ... 139 | 140 | firewall: 141 | # .. 142 | user_provider: App\UserProvider\Auth0UserProvider 143 | ``` 144 | 145 | ## Troubleshooting 146 | 147 | Make sure you have csrf_protection enabled. 148 | 149 | ```yaml 150 | framework: 151 | csrf_protection: 152 | enabled: true 153 | ``` 154 | 155 | ## Example configuration 156 | 157 | Below is an example configuration. We use the `Psr6Store` to store all data in Redis 158 | and the session key in cookies. We also define to use the `MemoryStore` when testing. 159 | 160 | ```yaml 161 | 162 | happyr_auth0: 163 | sdk: 164 | domain: '%env(AUTH0_DOMAIN)%' 165 | clientId: '%env(AUTH0_CLIENT_ID)%' 166 | clientSecret: '%env(AUTH0_SECRET)%' 167 | # Use custom domain for universal login 168 | customDomain: '%env(AUTH0_LOGIN_DOMAIN)%' 169 | cookieSecret: '%kernel.secret%' 170 | tokenCache: 'cache.redis' 171 | managementTokenCache: 'cache.redis' 172 | transientStorage: 'auth0.storage.transient' 173 | sessionStorage: 'auth0.storage.session' 174 | scope: 175 | - openid # "openid" is required. 176 | - profile 177 | - email 178 | firewall: 179 | check_route: default_login_check 180 | failure_path: default_logout 181 | default_target_path: startpage 182 | 183 | services: 184 | # Create a new SdkConfiguration service to be able to create 185 | # auth0.storage.cookie_* services without circular references 186 | 187 | auth0.sdk_cookie_config: 188 | class: Auth0\SDK\Configuration\SdkConfiguration 189 | arguments: 190 | - domain: '%env(AUTH0_DOMAIN)%' 191 | clientId: '%env(AUTH0_CLIENT_ID)%' 192 | clientSecret: '%env(AUTH0_SECRET)%' 193 | customDomain: '%env(AUTH0_LOGIN_DOMAIN)%' 194 | cookieSecret: '%kernel.secret%' 195 | 196 | auth0.storage.cookie_transient: 197 | class: Auth0\SDK\Store\CookieStore 198 | factory: ['@auth0.sdk_cookie_config', 'getTransientStorage'] 199 | 200 | auth0.storage.cookie_session: 201 | class: Auth0\SDK\Store\CookieStore 202 | factory: ['@auth0.sdk_cookie_config', 'getSessionStorage'] 203 | 204 | auth0.storage.transient: 205 | class: Auth0\SDK\Store\Psr6Store 206 | arguments: ['@auth0.storage.cookie_transient', '@cache.redis'] 207 | 208 | auth0.storage.session: 209 | class: Auth0\SDK\Store\Psr6Store 210 | arguments: ['@auth0.storage.cookie_session', '@cache.redis'] 211 | 212 | when@test: 213 | services: 214 | test.auth0.session_storage: 215 | class: Auth0\SDK\Store\MemoryStore 216 | 217 | test.auth0.transient_storage: 218 | class: Auth0\SDK\Store\MemoryStore 219 | 220 | happyr_auth0: 221 | sdk: 222 | transientStorage: test.auth0.transient_storage 223 | sessionStorage: test.auth0.session_storage 224 | ``` 225 | --------------------------------------------------------------------------------