knpuniversity / oauth2-client-bundle

Easily talk to an OAuth2 server for social functionality in Symfony
https://symfonycasts.com
MIT License
787 stars 145 forks source link

Azure authentication : how to use Microsoft Graph Api (for profile picture) ? #377

Closed eved42 closed 2 years ago

eved42 commented 2 years ago

Hello,

I use your bundle with a Symfony 6.1 project for a Azure authentication. The user authentication is working well and now I would like to get the Microsoft profile picture of the user. Is there a simple way to do that ?

In the documentation of the TheNetworg Azure AD provider, I see that it is possible to use Microsoft Graph API to get the user data. But I don't understand how I should use it with your bundle.

I think I should do something in AzureAuthenticator > authenticate(), can someone help me please ?

weaverryan commented 2 years ago

Hi there!

The purpose of this bundle, basically, is to help you get the "access token" via OAuth. If you have user authentication working, then it means that you should have code in your authenticator that looks something like this:

$accessToken = $this->fetchAccessToken($client);
$azureUser = $client->fetchUserFromToken($accessToken);

That $accessToken is the same as the $token variable you see in the docs - https://github.com/thenetworg/oauth2-azure#microsoft-graph - and you can freely use it to make API requests to the Azure API using whatever tool you like for making API requests. If you want to access the underlying $provider that you see in those docs, you can call $client-> getOAuth2Provider() from this bundle.

I hope that helps!

eved42 commented 2 years ago

@weaverryan Thank you for you answer, is was very helpful!

If we want to use Microsoft Graph, we can't use the same access token than the authentication, it's a specific token. I managed to make it works, here is my code :

I store the profile photo into the session so I injected RequestStack in the authenticator.

// src/Security/AzureAuthenticator.php

// ...
use Symfony\Component\HttpFoundation\RequestStack;

class AzureAuthenticator extends OAuth2Authenticator implements AuthenticationEntrypointInterface
{
  private $clientRegistry;
  private $em;
  private $router;
  private $requestStack;

  public function __construct(ClientRegistry $clientRegistry, EntityManagerInterface $em, RouterInterface $router, RequestStack $requestStack)
  {
    $this->clientRegistry = $clientRegistry;
    $this->em = $em;
    $this->router = $router;
    $this->requestStack = $requestStack;
  }

  // ...

  public function authenticate(Request $request): Passport
  {
    $client = $this->clientRegistry->getClient('azure');
    $accessToken = $this->fetchAccessToken($client);

    return new SelfValidatingPassport(
      new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {
        // Get user data from Azure AD
    $azureUser = $client->fetchUserFromToken($accessToken);

        // Get the corresponding user account in the database
        $user = $this->em->getRepository(User::class)->findOneBy(['email' => $azureUser->getUpn()]);

    // User doesn't exist -> creation
        if (!$user) {
      $user = new User();
      $user->setFirstname($azureUser->getFirstName());
      $user->setLastname($azureUser->getLastName());
      $user->setEmail($azureUser->getUpn());
      $this->em->persist($user);
      $this->em->flush();
    }

    // Get the user profile photo (need to use Microsoft Graph API)
    $provider = $client->getOAuth2Provider();
    $baseGraphUri = $provider->getRootMicrosoftGraphUri(null);  // https://graph.microsoft.com
    $provider->urlAPI  = $baseGraphUri;
    $provider->resource = $baseGraphUri;

    // Swap the Azure AD access token for a Microsoft Graph token       
    $graphAccessToken = $provider->getAccessToken('refresh_token', [
      'refresh_token' => $accessToken->getRefreshToken(),
      'resource' => $baseGraphUri
    ]);

    // Get photo
    $photoData = $provider->get($baseGraphUri.'/v1.0/me/photo', $graphAccessToken);
    $photo = $provider->get($baseGraphUri.'/v1.0/me/photo/$value', $graphAccessToken);
    $src = 'data:'.$photoData["@odata.mediaContentType"].';base64,'.base64_encode($photo);  // for <img src="$src" />

    // Store into session
    $session = $this->requestStack->getSession();
    $session->set('azure.user-img-src', $src);

        return $user;
      })
    );
  }

  // ...