Created
September 12, 2022 06:28
-
-
Save Glideh/acd2ee681799e55c574661ad14a00c54 to your computer and use it in GitHub Desktop.
Azure authentication guard for Symfony based on thenetworg/oauth2-azure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Security; | |
use Firebase\JWT\JWT; | |
use Firebase\JWT\SignatureInvalidException; | |
use Symfony\Component\Cache\Adapter\AbstractAdapter; | |
use Symfony\Component\Cache\Adapter\ApcuAdapter; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\Response; | |
use Symfony\Component\HttpKernel\Exception\HttpException; | |
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |
use Symfony\Component\Security\Core\Exception\AuthenticationException; | |
use Symfony\Component\Security\Core\User\UserInterface; | |
use Symfony\Component\Security\Core\User\UserProviderInterface; | |
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; | |
use Symfony\Contracts\Cache\ItemInterface; | |
use TheNetworg\OAuth2\Client\Provider\Azure; | |
class AzureAuthenticator extends AbstractGuardAuthenticator | |
{ | |
// Update with your own Application ID URI | |
private $audience = 'api://xxxxx-xxxx-xxxxxxx-xxxxx-xxxxxxx'; | |
private $tokenHeader = 'Authorization'; | |
/** @var Azure $provider */ | |
private $provider; | |
/** @var AbstractAdapter $cache */ | |
private $cache; | |
private $refreshDelayStandard = 3600 * 24; // One day | |
private $refreshDelayShort = 60 * 5; // 5 minutes | |
public function __construct() | |
{ | |
$this->provider = new Azure(); | |
$this->cache = new ApcuAdapter('unique-string-for-app'); | |
} | |
// One day cached keys | |
private function getKeys($force = false) | |
{ | |
return $this->cache->get('microsoft-keys', function (ItemInterface $item) { | |
$item->expiresAfter($this->refreshDelayStandard); | |
return $this->provider->getJwtVerificationKeys(); | |
}, $force ? INF : 1.0); | |
} | |
// 5 minutes cached keys | |
private function getKeysShort() | |
{ | |
return $this->cache->get('microsoft-keys-short', function (ItemInterface $item) { | |
$item->expiresAfter($this->refreshDelayShort); | |
// Forces the one day cache refresh | |
return $this->getKeys(true); | |
}); | |
} | |
public function getCredentials(Request $request) | |
{ | |
$accessToken = explode(' ', $request->headers->get($this->tokenHeader))[1]; | |
// 2 attempts | |
foreach ([0, 1] as $try) { | |
$firstTry = $try === 0; | |
try { | |
// First tries to use the "one day" cached keys, then falls back on the "5 minutes" cache | |
// (if signature check failed) which forces the "one day" cache refresh | |
$keys = $firstTry ? $this->getKeys() : $this->getKeysShort(); | |
return (array)JWT::decode($accessToken, $keys); | |
} catch (SignatureInvalidException $e) { | |
if ($firstTry) continue; | |
throw new HttpException(403, $e->getMessage(), $e); | |
} catch (\UnexpectedValueException | \InvalidArgumentException | \DomainException $e) { | |
throw new HttpException(403, $e->getMessage(), $e); | |
} | |
} | |
} | |
public function start(Request $request, AuthenticationException $authException = null) | |
{ | |
return new Response('Auth header required', 401); | |
} | |
public function supports(Request $request) | |
{ | |
return $request->headers->has($this->tokenHeader); | |
} | |
public function getUser($credentials, UserProviderInterface $userProvider) | |
{ | |
// If we are using upn as the username | |
return $userProvider->loadUserByUsername($credentials['upn']); | |
} | |
public function checkCredentials($credentials, UserInterface $user) | |
{ | |
if ($credentials['aud'] !== $this->audience) throw new AuthenticationException('Wrong audience'); | |
return true; | |
} | |
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) | |
{ | |
} | |
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) | |
{ | |
} | |
public function supportsRememberMe() | |
{ | |
return false; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
security: | |
providers: | |
# ... | |
login: | |
entity: | |
class: App\Entity\User | |
property: login | |
firewalls: | |
# ... | |
api: | |
pattern: ^/ | |
provider: login | |
stateless: true | |
guard: | |
authenticators: | |
- App\Security\AzureAuthenticator | |
# ... |
Do you maybe have an updated version of this for Symfony 6/7?
Sorry I don't have that
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is for Symfony >2 <6 and can be easily adapted for Symfony 6
Requires thenetworg/oauth2-azure