Code Monkey home page Code Monkey logo

oauth2-client-bundle's Introduction

OAuth / Social Integration for Symfony: KnpUOAuth2ClientBundle

Easily integrate with an OAuth2 server (e.g. Facebook, GitHub) for:

  • "Social" authentication / login
  • "Connect with Facebook" type of functionality
  • Fetching access keys via OAuth2 to be used with an API
  • Doing OAuth2 authentication with Symfony Custom Authenticator (or Guard Authenticator for legacy applications)

This bundle integrates with league/oauth2-client.

This Bundle or HWIOAuthBundle?

In addition to this bundle, another OAuth bundle exists for Symfony: hwi/oauth-bundle. You might be wondering "why are there two popular OAuth bundles?".

Great question! Generally speaking, hwi/oauth-bundle gives you more features out-of-the-box, including social authentication and registration (called "connect"). But, it's also a bit harder to install. The knpuniversity/oauth2-client-bundle, takes more work to setup, but gives you more low-level control.

Not sure which to use? If you need OAuth (social) authentication & registration, try hwi/oauth-bundle. If you don't like it, come back!

Installation

Install the bundle library via Composer by running the following command:

composer require knpuniversity/oauth2-client-bundle

If you're using Symfony Flex, the bundle will be automatically enabled. For older apps, enable it in your AppKernel class.

Awesome! Now, you'll want to configure a client.

Configuring a Client

You'll need to configure one client for each OAuth2 server (GitHub, Facebook, etc) that you want to talk to.

Step 1) Download the Client Library

Choose the one you want from this list and install it via Composer:

OAuth2 Provider How to Install
Amazon composer require luchianenco/oauth2-amazon
AppID composer require jampire/oauth2-appid
Apple composer require patrickbussmann/oauth2-apple
Auth0 composer require riskio/oauth2-auth0
Azure composer require thenetworg/oauth2-azure
Bitbucket composer require stevenmaguire/oauth2-bitbucket
Box composer require stevenmaguire/oauth2-box
Buddy composer require buddy-works/oauth2-client
Buffer composer require tgallice/oauth2-buffer
CanvasLMS composer require smtech/oauth2-canvaslms
Clever composer require schoolrunner/oauth2-clever
DevianArt composer require seinopsys/oauth2-deviantart
DigitalOcean composer require chrishemmings/oauth2-digitalocean
Discord composer require wohali/oauth2-discord-new
Disqus composer require antalaron/oauth2-disqus
Dribbble composer require crewlabs/oauth2-dribbble
Dropbox composer require stevenmaguire/oauth2-dropbox
Drupal composer require chrishemmings/oauth2-drupal
Elance composer require stevenmaguire/oauth2-elance
Eve Online composer require evelabs/oauth2-eveonline
Eventbrite composer require stevenmaguire/oauth2-eventbrite
Facebook composer require league/oauth2-facebook
Fitbit composer require djchen/oauth2-fitbit
Foursquare composer require stevenmaguire/oauth2-foursquare
FusionAuth composer require jerryhopper/oauth2-fusionauth
Geocaching composer require surfoo/oauth2-geocaching
GitHub composer require league/oauth2-github
GitLab composer require omines/oauth2-gitlab
Google composer require league/oauth2-google
HeadHunter composer require alexmasterov/oauth2-headhunter
Heroku composer require stevenmaguire/oauth2-heroku
Instagram composer require league/oauth2-instagram
Jira composer require mrjoops/oauth2-jira
Keycloak composer require stevenmaguire/oauth2-keycloak
LinkedIn composer require league/oauth2-linkedin
MailRu composer require aego/oauth2-mailru
Microsoft composer require stevenmaguire/oauth2-microsoft
Mollie composer require mollie/oauth2-mollie-php
Odnoklassniki composer require aego/oauth2-odnoklassniki
Okta composer require foxworth42/oauth2-okta
Passage composer require malteschlueter/oauth2-passage
Paypal composer require stevenmaguire/oauth2-paypal
PSN composer require larabros/oauth2-psn
Salesforce composer require stevenmaguire/oauth2-salesforce
Slack composer require adam-paterson/oauth2-slack
Spotify composer require kerox/oauth2-spotify
SymfonyConnect composer require qdequippe/oauth2-symfony-connect
Strava composer require edwin-luijten/oauth2-strava
Stripe composer require adam-paterson/oauth2-stripe
Twitch Deprecated composer require depotwarehouse/oauth2-twitch
Twitch Helix composer require vertisan/oauth2-twitch-helix
Uber composer require stevenmaguire/oauth2-uber
Unsplash composer require hughbertd/oauth2-unsplash
Vimeo composer require saf33r/oauth2-vimeo
VKontakte composer require j4k/oauth2-vkontakte
Wave composer require qdequippe/oauth2-wave
Webflow composer require koalati/oauth2-webflow
Yahoo composer require hayageek/oauth2-yahoo
Yandex composer require aego/oauth2-yandex
Zendesk composer require stevenmaguire/oauth2-zendesk
Generic configure any unsupported provider

Don't see a provider you need in this list? Please, check the full list of third-party provider clients from league/oauth2-client. Otherwise, consider creating a generic client yourself.

Step 2) Configure the Provider

Awesome! Now, you'll configure your provider. For Facebook, this will look something like this:

# config/packages/knpu_oauth2_client.yaml
knpu_oauth2_client:
    clients:
        # the key "facebook_main" can be anything, it
        # will create a service: "knpu.oauth2.client.facebook_main"
        facebook_main:
            # this will be one of the supported types
            type: facebook
            client_id: '%env(OAUTH_FACEBOOK_ID)%'
            client_secret: '%env(OAUTH_FACEBOOK_SECRET)%'
            # the route that you're redirected to after
            # see the controller example below
            redirect_route: connect_facebook_check
            redirect_params: {}
            graph_api_version: v2.12

Notice the two '%env(var)%'calls? Add these in your .env file, or to the vault. These are the credentials for the OAuth provider. For Facebook, you'll get these by registering your app on developers.facebook.com:

# .env
# ...

OAUTH_FACEBOOK_ID=fb_id
OAUTH_FACEBOOK_SECRET=fb_secret

See the full configuration for all the supported "types" in the Configuration section.

The type is facebook because we're connecting to Facebook. You can see all the supported type values below in the Configuration section.

Step 3) Use the Client Service

Each client you configured now has its own service that can be used to communicate with the OAuth2 server.

To start the OAuth process, you'll need to create a route and controller that redirects to Facebook. Because we used the key facebook_main above, you can simply:

namespace App\Controller;

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class FacebookController extends AbstractController
{
    /**
     * Link to this controller to start the "connect" process
     *
     * @Route("/connect/facebook", name="connect_facebook_start")
     */
    public function connectAction(ClientRegistry $clientRegistry)
    {
        // will redirect to Facebook!
        return $clientRegistry
            ->getClient('facebook_main') // key used in config/packages/knpu_oauth2_client.yaml
            ->redirect([
                'public_profile', 'email' // the scopes you want to access
            ]);
    }

    /**
     * After going to Facebook, you're redirected back here
     * because this is the "redirect_route" you configured
     * in config/packages/knpu_oauth2_client.yaml
     *
     * @Route("/connect/facebook/check", name="connect_facebook_check")
     */
    public function connectCheckAction(Request $request, ClientRegistry $clientRegistry)
    {
        // ** if you want to *authenticate* the user, then
        // leave this method blank and create a Guard authenticator
        // (read below)

        /** @var \KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient $client */
        $client = $clientRegistry->getClient('facebook_main');

        try {
            // the exact class depends on which provider you're using
            /** @var \League\OAuth2\Client\Provider\FacebookUser $user */
            $user = $client->fetchUser();

            // do something with all this new power!
            // e.g. $name = $user->getFirstName();
            var_dump($user); die;
            // ...
        } catch (IdentityProviderException $e) {
            // something went wrong!
            // probably you should return the reason to the user
            var_dump($e->getMessage()); die;
        }
    }
}

Now, just go (or link to) /connect/facebook and watch the flow!

After completing the OAuth2 flow, the $client object can be used to fetch the user, the access token, or other things:

// get the user directly
$user = $client->fetchUser();

// OR: get the access token and then user
$accessToken = $client->getAccessToken();
$user = $client->fetchUserFromToken($accessToken);

// access the underlying "provider" from league/oauth2-client
$provider = $client->getOAuth2Provider();
// if you're using Facebook, then this works:
$longLivedToken = $provider->getLongLivedAccessToken($accessToken);

Authenticating with the new Symfony Authenticator

At this point, you now have a nice service that allows you to redirect your user to an OAuth server (e.g. Facebook) and fetch their access token and user information.

But often, you will want to actually authenticate that user: log them into your system. In that case, instead of putting all of the logic in connectCheckAction() as shown above, you'll leave that blank and create an authenticator which will hold similar logic.

Now you can use the new Symfony Authenticator system (available since Symfony 5.2, don't use it before this version) to login in your app. For legacy Symfony versions, use Guard Authenticator below.

Step 1) Using the new OAuth2Authenticator Class

namespace App\Security;

use App\Entity\User; // your user entity
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;

class MyFacebookAuthenticator extends OAuth2Authenticator implements AuthenticationEntrypointInterface
{
    private $clientRegistry;
    private $entityManager;
    private $router;

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

    public function supports(Request $request): ?bool
    {
        // continue ONLY if the current ROUTE matches the check ROUTE
        return $request->attributes->get('_route') === 'connect_facebook_check';
    }

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

        return new SelfValidatingPassport(
            new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {
                /** @var FacebookUser $facebookUser */
                $facebookUser = $client->fetchUserFromToken($accessToken);

                $email = $facebookUser->getEmail();

                // 1) have they logged in with Facebook before? Easy!
                $existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['facebookId' => $facebookUser->getId()]);

                if ($existingUser) {
                    return $existingUser;
                }

                // 2) do we have a matching user by email?
                $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $email]);

                // 3) Maybe you just want to "register" them by creating
                // a User object
                $user->setFacebookId($facebookUser->getId());
                $this->entityManager->persist($user);
                $this->entityManager->flush();

                return $user;
            })
        );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        // change "app_homepage" to some route in your app
        $targetUrl = $this->router->generate('app_homepage');

        return new RedirectResponse($targetUrl);
    
        // or, on success, let the request continue to be handled by the controller
        //return null;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        $message = strtr($exception->getMessageKey(), $exception->getMessageData());

        return new Response($message, Response::HTTP_FORBIDDEN);
    }
    
   /**
     * Called when authentication is needed, but it's not sent.
     * This redirects to the 'login'.
     */
    public function start(Request $request, AuthenticationException $authException = null): Response
    {
        return new RedirectResponse(
            '/connect/', // might be the site, where users choose their oauth provider
            Response::HTTP_TEMPORARY_REDIRECT
        );
    }
}

Step 2) Configuring the Security

Next, enable the new authenticator manager and then register your authenticator in security.yaml under the custom_authenticators section:

# app/config/packages/security.yaml
security:
    # ...
+   enable_authenticator_manager: true
  
    firewalls:
        # ...
        main:
        # ...
+           custom_authenticators:
+               - App\Security\MyFacebookAuthenticator

CAUTION You can also inject the individual client (e.g. FacebookClient) into your authenticator instead of the ClientRegistry. However, this may cause circular reference issues and degrades performance (because authenticators are instantiated on every request, even though you rarely need the FacebookClient to be created). The ClientRegistry lazily creates the client objects.

Authenticating with Guard

Create a Guard Authenticator. A SocialAuthenticator base class exists to help with a few things:

namespace App\Security;

use App\Entity\User; // your user entity
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class MyFacebookAuthenticator extends SocialAuthenticator
{
    private $clientRegistry;
    private $em;
    private $router;

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

    public function supports(Request $request)
    {
        // continue ONLY if the current ROUTE matches the check ROUTE
        return $request->attributes->get('_route') === 'connect_facebook_check';
    }

    public function getCredentials(Request $request)
    {
        // this method is only called if supports() returns true

        // For Symfony lower than 3.4 the supports method need to be called manually here:
        // if (!$this->supports($request)) {
        //     return null;
        // }

        return $this->fetchAccessToken($this->getFacebookClient());
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        /** @var FacebookUser $facebookUser */
        $facebookUser = $this->getFacebookClient()
            ->fetchUserFromToken($credentials);

        $email = $facebookUser->getEmail();

        // 1) have they logged in with Facebook before? Easy!
        $existingUser = $this->em->getRepository(User::class)
            ->findOneBy(['facebookId' => $facebookUser->getId()]);
        if ($existingUser) {
            return $existingUser;
        }

        // 2) do we have a matching user by email?
        $user = $this->em->getRepository(User::class)
            ->findOneBy(['email' => $email]);

        // 3) Maybe you just want to "register" them by creating
        // a User object
        $user->setFacebookId($facebookUser->getId());
        $this->em->persist($user);
        $this->em->flush();

        return $user;
    }

    /**
     * @return FacebookClient
     */
    private function getFacebookClient()
    {
        return $this->clientRegistry
            // "facebook_main" is the key used in config/packages/knpu_oauth2_client.yaml
            ->getClient('facebook_main');
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        // change "app_homepage" to some route in your app
        $targetUrl = $this->router->generate('app_homepage');

        return new RedirectResponse($targetUrl);
    
        // or, on success, let the request continue to be handled by the controller
        //return null;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        $message = strtr($exception->getMessageKey(), $exception->getMessageData());

        return new Response($message, Response::HTTP_FORBIDDEN);
    }

    /**
     * Called when authentication is needed, but it's not sent.
     * This redirects to the 'login'.
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        return new RedirectResponse(
            '/connect/', // might be the site, where users choose their oauth provider
            Response::HTTP_TEMPORARY_REDIRECT
        );
    }

    // ...
}

Next, register your authenticator in security.yaml under the guard section:

# app/config/packages/security.yaml
security:
    # ...
    firewalls:
        # ...
        main:
        # ...
+            guard:
+                authenticators:
+                    - App\Security\MyFacebookAuthenticator

For more details: see http://symfony.com/doc/current/cookbook/security/guard-authentication.html#step-2-configure-the-authenticator.

Authenticating any OAuth User

If you don't need to fetch/persist any information about the user, you can use the OAuthUserProvider service to quickly authenticate them in your application (if you're using Doctrine, use the normal entity user provider).

First, define the user provider in your security.yaml file:

security:
    providers:
        oauth:
            id: knpu.oauth2.user_provider

Then in your Guard authenticator, use the user provider to easily fetch the user:

public function getUser($credentials, UserProviderInterface $userProvider)
{
    return $userProvider->loadUserByUsername($this->getClient()->fetchUserFromToken($credentials)->getId());
}

The logged-in user will be an instance of KnpU\OAuth2ClientBundle\Security\User\OAuthUser and will have the roles ROLE_USER and ROLE_OAUTH_USER.

Storing and Refreshing Tokens

You have a couple of options to store access tokens for use at a later time:

  1. Store the AccessToken object (e.g. serializing into the session), this allows you to check expiry before refreshing:

    // Fetch and store the AccessToken
    $accessToken = $client->getAccessToken();
    $session->set('access_token', $accessToken);
    
    // Load the access token from the session, and refresh if required
    $accessToken = $session->get('access_token');
    
    if ($accessToken->hasExpired()) {
        $accessToken = $client->refreshAccessToken($accessToken->getRefreshToken());
    
        // Update the stored access token for next time
        $session->set('access_token', $accessToken);
    }
  2. Store the refresh token string (e.g. in the database user.refresh_token), this means you must always refresh. You can also store the access token and expiration and then avoid the refresh until the access token is actually expired:

    // Fetch the AccessToken and store the refresh token
    $accessToken = $client->getAccessToken();
    $user->setRefreshToken($accessToken->getRefreshToken());
    $entityManager->flush();
    
    // Get a new AccessToken from the refresh token, and store the new refresh token for next time
    $accessToken = $client->refreshAccessToken($user->getRefreshToken());
    $user->setRefreshToken($accessToken->getRefreshToken());
    $entityManager->flush();

Depending on your OAuth2 provider, you may need to pass some parameters when initially creating and/or refreshing the token:

// Some providers may require special parameters when creating the token in order to allow refreshing
$accessToken = $client->getAccessToken(['scopes' => 'offline_access']);

// They may also require special parameters when refreshing the token
$accessToken = $client->refreshAccessToken($accessToken->getRefreshtoken(), ['scopes' => 'offline_access']);

Configuration

Below is the configuration for all of the supported OAuth2 providers. Don't see the one you need? Use the generic provider to configure any provider:

# config/packages/knpu_oauth2_client.yaml
knpu_oauth2_client:
    # can be set to the service id of a service that implements Guzzle\ClientInterface
    # http_client: null

    # options to configure the default http client
    # http_client_options:
    #     timeout: 0
    #     # if you want to disable the proxy (e.g. local GitLab OAuth) - set it to "false"
    #     proxy: null
    #     Use only with proxy option set
    #     verify: false

    clients:
        # will create service: "knpu.oauth2.client.amazon"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\AmazonClient
        # composer require luchianenco/oauth2-amazon
        amazon:
            # must be "amazon" - it activates that type!
            type: amazon
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_AMAZON_CLIENT_ID)%'
            client_secret: '%env(OAUTH_AMAZON_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_amazon_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.appid"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\AppIdClient
        # composer require jampire/oauth2-appid
        appid:
            # must be "appid" - it activates that type!
            type: appid
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_APPID_CLIENT_ID)%'
            client_secret: '%env(OAUTH_APPID_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_appid_check
            redirect_params: {}
            # IBM App ID base URL. For example, "https://us-south.appid.cloud.ibm.com/oauth/v4". More details at https://cloud.ibm.com/docs/services/appid?topic=appid-getting-started
            base_auth_uri: '%env(OAUTH_APPID_BASE_AUTH_URI)%'
            # IBM App ID service tenant ID. For example, "1234-5678-abcd-efgh". More details at https://cloud.ibm.com/docs/services/appid?topic=appid-getting-started
            tenant_id: '%env(OAUTH_APPID_TENANT_ID)%'
            # Identity Provider code. Defaults to "saml". More details at https://cloud.ibm.com/docs/services/appid?topic=appid-getting-started
            # idp: '%env(OAUTH_APPID_IDP)%'
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.apple"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\AppleClient
        # composer require patrickbussmann/oauth2-apple
        apple:
            # must be "apple" - it activates that type!
            type: apple
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_APPLE_CLIENT_ID)%'
            # a route name you'll create
            redirect_route: connect_apple_check
            redirect_params: {}
            team_id: null
            key_file_id: null
            key_file_path: null
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.auth0"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\Auth0Client
        # composer require riskio/oauth2-auth0
        auth0:
            # must be "auth0" - it activates that type!
            type: auth0
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_AUTH0_CLIENT_ID)%'
            client_secret: '%env(OAUTH_AUTH0_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_auth0_check
            redirect_params: {}
            # Your custom/definite Auth0 domain, e.g. "login.mycompany.com". Set this if you use Auth0's Custom Domain feature. The "account" and "region" parameters will be ignored in this case.
            # custom_domain: null
            # Your Auth0 domain/account, e.g. "mycompany" if your domain is "mycompany.auth0.com"
            # account: null
            # Your Auth0 region, e.g. "eu" if your tenant is in the EU.
            # region: null
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.azure"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\AzureClient
        # composer require thenetworg/oauth2-azure
        azure:
            # must be "azure" - it activates that type!
            type: azure
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_AZURE_CLIENT_ID)%'
            # a route name you'll create
            redirect_route: connect_azure_check
            redirect_params: {}
            # The shared client secret if you don't use a certificate
            # client_secret: ''
            # The contents of the client certificate private key
            # client_certificate_private_key: '-----BEGIN RSA PRIVATE KEY-----\nMIIEog...G82ARGuI=\n-----END RSA PRIVATE KEY-----'
            # The hexadecimal thumbprint of the client certificate
            # client_certificate_thumbprint: 'B4A94A83092455AC4D3AC827F02B61646EAAC43D'
            # Domain to build login URL
            # url_login: 'https://login.microsoftonline.com/'
            # Oauth path to authorize against
            # path_authorize: '/oauth2/authorize'
            # Oauth path to retrieve a token
            # path_token: '/oauth2/token'
            # Oauth scope send with the request
            # scope: {}
            # The tenant to use, default is `common`
            # tenant: 'common'
            # Domain to build request URL
            # url_api: 'https://graph.windows.net/'
            # Oauth resource field
            # resource: null
            # The API version to run against
            # api_version: '1.6'
            # Send resource field with auth-request
            # auth_with_resource: true
            # The endpoint version to run against
            # default_end_point_version: '1.0'
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.bitbucket"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\BitbucketClient
        # composer require stevenmaguire/oauth2-bitbucket
        bitbucket:
            # must be "bitbucket" - it activates that type!
            type: bitbucket
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_BITBUCKET_CLIENT_ID)%'
            client_secret: '%env(OAUTH_BITBUCKET_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_bitbucket_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.box"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\BoxClient
        # composer require stevenmaguire/oauth2-box
        box:
            # must be "box" - it activates that type!
            type: box
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_BOX_CLIENT_ID)%'
            client_secret: '%env(OAUTH_BOX_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_box_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.buddy"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\BuddyClient
        # composer require buddy-works/oauth2-client
        buddy:
            # must be "buddy" - it activates that type!
            type: buddy
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_BUDDY_CLIENT_ID)%'
            client_secret: '%env(OAUTH_BUDDY_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_buddy_check
            redirect_params: {}
            # Base API URL, modify this for self-hosted instances
            # base_api_url: https://api.buddy.works
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.buffer"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\BufferClient
        # composer require tgallice/oauth2-buffer
        buffer:
            # must be "buffer" - it activates that type!
            type: buffer
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_BUFFER_CLIENT_ID)%'
            client_secret: '%env(OAUTH_BUFFER_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_buffer_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.canvas_lms"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\CanvasLMSClient
        # composer require smtech/oauth2-canvaslms
        canvas_lms:
            # must be "canvas_lms" - it activates that type!
            type: canvas_lms
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_CANVAS_LMS_CLIENT_ID)%'
            client_secret: '%env(OAUTH_CANVAS_LMS_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_canvas_lms_check
            redirect_params: {}
            # URL of Canvas Instance (e.g. https://canvas.instructure.com)
            canvas_instance_url: null
            # This can be used to help the user identify which instance of an application this token is for. For example, a mobile device application could provide the name of the device.
            # purpose: ''
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.clever"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\CleverClient
        # composer require schoolrunner/oauth2-clever
        clever:
            # must be "clever" - it activates that type!
            type: clever
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_CLEVER_CLIENT_ID)%'
            client_secret: '%env(OAUTH_CLEVER_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_clever_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.devian_art"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\DevianArtClient
        # composer require seinopsys/oauth2-deviantart
        devian_art:
            # must be "devian_art" - it activates that type!
            type: devian_art
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_DEVIAN_ART_CLIENT_ID)%'
            client_secret: '%env(OAUTH_DEVIAN_ART_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_devian_art_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.digital_ocean"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\DigitalOceanClient
        # composer require chrishemmings/oauth2-digitalocean
        digital_ocean:
            # must be "digital_ocean" - it activates that type!
            type: digital_ocean
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_DIGITAL_OCEAN_CLIENT_ID)%'
            client_secret: '%env(OAUTH_DIGITAL_OCEAN_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_digital_ocean_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.discord"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\DiscordClient
        # composer require wohali/oauth2-discord-new
        discord:
            # must be "discord" - it activates that type!
            type: discord
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_DISCORD_CLIENT_ID)%'
            client_secret: '%env(OAUTH_DISCORD_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_discord_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.disqus"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\DisqusClient
        # composer require antalaron/oauth2-disqus
        disqus:
            # must be "disqus" - it activates that type!
            type: disqus
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_DISQUS_CLIENT_ID)%'
            client_secret: '%env(OAUTH_DISQUS_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_disqus_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.dribbble"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\DribbbleClient
        # composer require crewlabs/oauth2-dribbble
        dribbble:
            # must be "dribbble" - it activates that type!
            type: dribbble
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_DRIBBBLE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_DRIBBBLE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_dribbble_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.dropbox"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\DropboxClient
        # composer require stevenmaguire/oauth2-dropbox
        dropbox:
            # must be "dropbox" - it activates that type!
            type: dropbox
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_DROPBOX_CLIENT_ID)%'
            client_secret: '%env(OAUTH_DROPBOX_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_dropbox_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.drupal"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\DrupalClient
        # composer require chrishemmings/oauth2-drupal
        drupal:
            # must be "drupal" - it activates that type!
            type: drupal
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_DRUPAL_CLIENT_ID)%'
            client_secret: '%env(OAUTH_DRUPAL_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_drupal_check
            redirect_params: {}
            # Drupal oAuth2 server URL
            base_url: '%env(OAUTH_DRUPAL_BASE_URL)%'
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.elance"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\ElanceClient
        # composer require stevenmaguire/oauth2-elance
        elance:
            # must be "elance" - it activates that type!
            type: elance
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_ELANCE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_ELANCE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_elance_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.eve_online"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\EveOnlineClient
        # composer require evelabs/oauth2-eveonline
        eve_online:
            # must be "eve_online" - it activates that type!
            type: eve_online
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_EVE_ONLINE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_EVE_ONLINE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_eve_online_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.eventbrite"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\EventbriteClient
        # composer require stevenmaguire/oauth2-eventbrite
        eventbrite:
            # must be "eventbrite" - it activates that type!
            type: eventbrite
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_EVENTBRITE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_EVENTBRITE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_eventbrite_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.facebook"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient
        # composer require league/oauth2-facebook
        facebook:
            # must be "facebook" - it activates that type!
            type: facebook
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_FACEBOOK_CLIENT_ID)%'
            client_secret: '%env(OAUTH_FACEBOOK_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_facebook_check
            redirect_params: {}
            graph_api_version: v2.12
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.fitbit"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\FitbitClient
        # composer require djchen/oauth2-fitbit
        fitbit:
            # must be "fitbit" - it activates that type!
            type: fitbit
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_FITBIT_CLIENT_ID)%'
            client_secret: '%env(OAUTH_FITBIT_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_fitbit_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.four_square"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\FoursquareClient
        # composer require stevenmaguire/oauth2-foursquare
        four_square:
            # must be "four_square" - it activates that type!
            type: four_square
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_FOUR_SQUARE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_FOUR_SQUARE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_four_square_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.fusion_auth"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\FusionAuthClient
        # composer require jerryhopper/oauth2-fusionauth
        fusion_auth:
            # must be "fusion_auth" - it activates that type!
            type: fusion_auth
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_FUSION_AUTH_CLIENT_ID)%'
            client_secret: '%env(OAUTH_FUSION_AUTH_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_fusion_auth_check
            redirect_params: {}
            # FusionAuth Server URL, no trailing slash
            auth_server_url: null
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.geocaching"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\GeocachingClient
        # composer require surfoo/oauth2-geocaching
        geocaching:
            # must be "geocaching" - it activates that type!
            type: geocaching
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_GEOCACHING_CLIENT_ID)%'
            client_secret: '%env(OAUTH_GEOCACHING_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_geocaching_check
            redirect_params: {}
            # dev, staging or production
            environment: production
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.github"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\GithubClient
        # composer require league/oauth2-github
        github:
            # must be "github" - it activates that type!
            type: github
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_GITHUB_CLIENT_ID)%'
            client_secret: '%env(OAUTH_GITHUB_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_github_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.gitlab"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\GitlabClient
        # composer require omines/oauth2-gitlab
        gitlab:
            # must be "gitlab" - it activates that type!
            type: gitlab
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_GITLAB_CLIENT_ID)%'
            client_secret: '%env(OAUTH_GITLAB_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_gitlab_check
            redirect_params: {}
            # Base installation URL, modify this for self-hosted instances
            # domain: https://gitlab.com
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.google"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\GoogleClient
        # composer require league/oauth2-google
        google:
            # must be "google" - it activates that type!
            type: google
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_GOOGLE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_GOOGLE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_google_check
            redirect_params: {}
            # Optional value for sending access_type parameter. More detail: https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters
            # access_type: null
            # Optional value for sending hd parameter. More detail: https://developers.google.com/identity/protocols/OpenIDConnect#hd-param
            # hosted_domain: null
            # Optional value for additional fields to be requested from the user profile. If set, these values will be included with the defaults. More details: https://developers.google.com/+/web/api/rest/latest/people
            # user_fields: {}
            # Optional value if you don't want or need to enable Google+ API access.
            # use_oidc_mode: false
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.headhunter"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\HeadHunterClient
        # composer require alexmasterov/oauth2-headhunter
        headhunter:
            # must be "headhunter" - it activates that type!
            type: headhunter
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_HEADHUNTER_CLIENT_ID)%'
            client_secret: '%env(OAUTH_HEADHUNTER_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_headhunter_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.heroku"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\HerokuClient
        # composer require stevenmaguire/oauth2-heroku
        heroku:
            # must be "heroku" - it activates that type!
            type: heroku
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_HEROKU_CLIENT_ID)%'
            client_secret: '%env(OAUTH_HEROKU_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_heroku_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.instagram"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\InstagramClient
        # composer require league/oauth2-instagram
        instagram:
            # must be "instagram" - it activates that type!
            type: instagram
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_INSTAGRAM_CLIENT_ID)%'
            client_secret: '%env(OAUTH_INSTAGRAM_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_instagram_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.jira"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\JiraClient
        # composer require mrjoops/oauth2-jira
        jira:
            # must be "jira" - it activates that type!
            type: jira
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_JIRA_CLIENT_ID)%'
            client_secret: '%env(OAUTH_JIRA_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_jira_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.keycloak"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\KeycloakClient
        # composer require stevenmaguire/oauth2-keycloak
        keycloak:
            # must be "keycloak" - it activates that type!
            type: keycloak
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_KEYCLOAK_CLIENT_ID)%'
            client_secret: '%env(OAUTH_KEYCLOAK_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_keycloak_check
            redirect_params: {}
            # Keycloak server URL
            auth_server_url: null
            # Keycloak realm
            realm: null
            # Optional: Encryption algorithm, i.e. RS256
            # encryption_algorithm: null
            # Optional: Encryption key path, i.e. ../key.pem
            # encryption_key_path: null
            # Optional: Encryption key, i.e. contents of key or certificate
            # encryption_key: null
            # Optional: The keycloak version to run against
            # version: '20.0.1'
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.linkedin"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\LinkedInClient
        # composer require league/oauth2-linkedin
        linkedin:
            # must be "linkedin" - it activates that type!
            type: linkedin
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_LINKEDIN_CLIENT_ID)%'
            client_secret: '%env(OAUTH_LINKEDIN_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_linkedin_check
            redirect_params: {}
            # Optional value to specify Linkedin's API version to use. As the time of writing, v1 is still used by default by league/oauth2-linkedin.
            # api_version: null
            # Optional value to specify fields to be requested from the profile. Since Linkedin's API upgrade from v1 to v2, fields and authorizations policy have been enforced. See https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin for more details.
            # fields: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.mail_ru"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\MailRuClient
        # composer require aego/oauth2-mailru
        mail_ru:
            # must be "mail_ru" - it activates that type!
            type: mail_ru
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_MAIL_RU_CLIENT_ID)%'
            client_secret: '%env(OAUTH_MAIL_RU_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_mail_ru_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.microsoft"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\MicrosoftClient
        # composer require stevenmaguire/oauth2-microsoft
        microsoft:
            # must be "microsoft" - it activates that type!
            type: microsoft
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_MICROSOFT_CLIENT_ID)%'
            client_secret: '%env(OAUTH_MICROSOFT_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_microsoft_check
            redirect_params: {}
            # Optional value for URL Authorize
            # url_authorize: null
            # Optional value for URL Access Token
            # url_access_token: null
            # Optional value for URL Resource Owner Details
            # url_resource_owner_details: null
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.mollie"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\MollieClient
        # composer require mollie/oauth2-mollie-php
        mollie:
            # must be "mollie" - it activates that type!
            type: mollie
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_MOLLIE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_MOLLIE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_mollie_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.odnoklassniki"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\OdnoklassnikiClient
        # composer require aego/oauth2-odnoklassniki
        odnoklassniki:
            # must be "odnoklassniki" - it activates that type!
            type: odnoklassniki
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_ODNOKLASSNIKI_CLIENT_ID)%'
            client_secret: '%env(OAUTH_ODNOKLASSNIKI_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_odnoklassniki_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.okta"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\OktaClient
        # composer require foxworth42/oauth2-okta
        okta:
            # must be "okta" - it activates that type!
            type: okta
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_OKTA_CLIENT_ID)%'
            client_secret: '%env(OAUTH_OKTA_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_okta_check
            redirect_params: {}
            # Issuer URI from Okta
            issuer: https://mycompany.okta.com/oauth2/default
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.passage"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\PassageClient
        # composer require malteschlueter/oauth2-passage
        passage:
            # must be "passage" - it activates that type!
            type: passage
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_PASSAGE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_PASSAGE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_passage_check
            redirect_params: {}
            # Passage sub domain. For example, from passage url "https://example.withpassage.com" only "example" is required.
            sub_domain: 'example'
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.paypal"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\PaypalClient
        # composer require stevenmaguire/oauth2-paypal
        paypal:
            # must be "paypal" - it activates that type!
            type: paypal
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_PAYPAL_CLIENT_ID)%'
            client_secret: '%env(OAUTH_PAYPAL_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_paypal_check
            redirect_params: {}
            # When true, client uses Paypal Sandbox URLs.
            # is_sandbox: false
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.psn"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\PsnClient
        # composer require larabros/oauth2-psn
        psn:
            # must be "psn" - it activates that type!
            type: psn
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_PSN_CLIENT_ID)%'
            client_secret: '%env(OAUTH_PSN_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_psn_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.salesforce"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\SalesforceClient
        # composer require stevenmaguire/oauth2-salesforce
        salesforce:
            # must be "salesforce" - it activates that type!
            type: salesforce
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_SALESFORCE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_SALESFORCE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_salesforce_check
            redirect_params: {}
            # Custom Salesforce domain. Default domain is https://login.salesforce.com
            # domain: ''
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.slack"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\SlackClient
        # composer require adam-paterson/oauth2-slack
        slack:
            # must be "slack" - it activates that type!
            type: slack
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_SLACK_CLIENT_ID)%'
            client_secret: '%env(OAUTH_SLACK_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_slack_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.spotify"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\SpotifyClient
        # composer require kerox/oauth2-spotify
        spotify:
            # must be "spotify" - it activates that type!
            type: spotify
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_SPOTIFY_CLIENT_ID)%'
            client_secret: '%env(OAUTH_SPOTIFY_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_spotify_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.symfony_connect"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\SymfonyConnectClient
        # composer require qdequippe/oauth2-symfony-connect
        symfony_connect:
            # must be "symfony_connect" - it activates that type!
            type: symfony_connect
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_SYMFONY_CONNECT_CLIENT_ID)%'
            client_secret: '%env(OAUTH_SYMFONY_CONNECT_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_symfony_connect_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.strava"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\StravaClient
        # composer require edwin-luijten/oauth2-strava
        strava:
            # must be "strava" - it activates that type!
            type: strava
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_STRAVA_CLIENT_ID)%'
            client_secret: '%env(OAUTH_STRAVA_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_strava_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.stripe"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\StripeClient
        # composer require adam-paterson/oauth2-stripe
        stripe:
            # must be "stripe" - it activates that type!
            type: stripe
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_STRIPE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_STRIPE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_stripe_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.twitch"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\TwitchClient
        # composer require depotwarehouse/oauth2-twitch
        twitch:
            # must be "twitch" - it activates that type!
            type: twitch
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_TWITCH_CLIENT_ID)%'
            client_secret: '%env(OAUTH_TWITCH_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_twitch_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.twitch_helix"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\TwitchHelixClient
        # composer require vertisan/oauth2-twitch-helix
        twitch_helix:
            # must be "twitch_helix" - it activates that type!
            type: twitch_helix
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_TWITCH_HELIX_CLIENT_ID)%'
            client_secret: '%env(OAUTH_TWITCH_HELIX_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_twitch_helix_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.uber"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\UberClient
        # composer require stevenmaguire/oauth2-uber
        uber:
            # must be "uber" - it activates that type!
            type: uber
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_UBER_CLIENT_ID)%'
            client_secret: '%env(OAUTH_UBER_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_uber_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.unsplash"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\UnsplashClient
        # composer require hughbertd/oauth2-unsplash
        unsplash:
            # must be "unsplash" - it activates that type!
            type: unsplash
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_UNSPLASH_CLIENT_ID)%'
            client_secret: '%env(OAUTH_UNSPLASH_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_unsplash_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.vimeo"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\VimeoClient
        # composer require saf33r/oauth2-vimeo
        vimeo:
            # must be "vimeo" - it activates that type!
            type: vimeo
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_VIMEO_CLIENT_ID)%'
            client_secret: '%env(OAUTH_VIMEO_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_vimeo_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.vkontakte"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\VKontakteClient
        # composer require j4k/oauth2-vkontakte
        vkontakte:
            # must be "vkontakte" - it activates that type!
            type: vkontakte
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_VKONTAKTE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_VKONTAKTE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_vkontakte_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.wave"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\WaveClient
        # composer require qdequippe/oauth2-wave
        wave:
            # must be "wave" - it activates that type!
            type: wave
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_WAVE_CLIENT_ID)%'
            client_secret: '%env(OAUTH_WAVE_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_wave_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.webflow"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\WebflowClient
        # composer require koalati/oauth2-webflow
        webflow:
            # must be "webflow" - it activates that type!
            type: webflow
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_WEBFLOW_CLIENT_ID)%'
            client_secret: '%env(OAUTH_WEBFLOW_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_webflow_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.yahoo"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\YahooClient
        # composer require hayageek/oauth2-yahoo
        yahoo:
            # must be "yahoo" - it activates that type!
            type: yahoo
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_YAHOO_CLIENT_ID)%'
            client_secret: '%env(OAUTH_YAHOO_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_yahoo_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.yandex"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\YandexClient
        # composer require aego/oauth2-yandex
        yandex:
            # must be "yandex" - it activates that type!
            type: yandex
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_YANDEX_CLIENT_ID)%'
            client_secret: '%env(OAUTH_YANDEX_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_yandex_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

        # will create service: "knpu.oauth2.client.zendesk"
        # an instance of: KnpU\OAuth2ClientBundle\Client\Provider\ZendeskClient
        # composer require stevenmaguire/oauth2-zendesk
        zendesk:
            # must be "zendesk" - it activates that type!
            type: zendesk
            # add and set these environment variables in your .env files
            client_id: '%env(OAUTH_ZENDESK_CLIENT_ID)%'
            client_secret: '%env(OAUTH_ZENDESK_CLIENT_SECRET)%'
            # a route name you'll create
            redirect_route: connect_zendesk_check
            redirect_params: {}
            # Your Zendesk subdomain
            subdomain: null
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

Configuring a Generic Provider

Is the OAuth server you want to connect with not here? No worries! You can configure a custom "provider" using the generic type.

1) Find / Create your Provider Library

First, see if your OAuth server already has a "provider library" that you can use: See Provider Client Libraries.

If you found one there, awesome! Install it. If not, you'll need to create your own Provider class. See the Provider Guide about this.

Either way, after this step, you should have a provider "class" (e.g. a class that extends AbstractProvider) that's ready to use!

2) Configuration

Now, just configure your provider like any other provider, but using the generic type:

# config/packages/knpu_oauth2_client.yaml
knpu_oauth2_client:
    clients:
        # will create service: "knpu.oauth2.client.foo_bar_oauth"
        # an instance of: KnpU\OAuth2ClientBundle\Client\OAuth2Client
        foo_bar_oauth:
            type: generic
            provider_class: Some\Class\FooBarProvider

            # optional: a class that extends OAuth2Client
            # client_class: Some\Custom\Client

            # optional: if your provider has custom constructor options
            # provider_options: {}

            # now, all the normal options!
            client_id: '%env(foo_bar_client_id)%'
            client_secret: '%env(foo_bar_client_secret)%'
            redirect_route: connect_facebook_check
            redirect_params: {}
            # whether to check OAuth2 "state": defaults to true
            # use_state: true

That's it! Now you'll have a knpu.oauth2.client.foo_bar_oauth service you can use.

Extending / Decorating Client Classes

Maybe you need some extra services inside your client class? No problem! You can decorate existing client class with your own implementation. All you need is a new class that implements OAuth2ClientInterface:

namespace App\Client;

use KnpU\OAuth2ClientBundle\Client\OAuth2ClientInterface;
use KnpU\OAuth2ClientBundle\Client\Provider\AzureClient;
use Symfony\Component\Cache\Adapter\AdapterInterface;

class CacheableAzureClient implements OAuth2ClientInterface
{
    private $client;
    private $cache;

    public function __construct(AzureClient $client, AdapterInterface $cache)
    {
        // ...
    }

    // override all public functions and call the method on the internal $this->client object
    // but add caching wherever you need it
}

And configure it:

# config/services.yaml
services:
    App\Client\CacheableAzureClient:
        decorates: knpu.oauth2.client.azure

Contributing

Of course, open source is fueled by everyone's ability to give just a little bit of their time for the greater good. If you'd like to see a feature, you can request it - but creating a pull request is an even better way to get things done.

Either way, please feel comfortable submitting issues or pull requests: all contributions and questions are warmly appreciated :).

oauth2-client-bundle's People

Contributors

aeimer avatar ajgarlag avatar akondas avatar alexander-schranz avatar alister avatar bocharsky-bw avatar brambaud avatar brianfreytag avatar ckrack avatar crayner avatar curry684 avatar doncallisto avatar emileperron avatar fatihkahveci avatar foxworth42 avatar jampire avatar jrushlow avatar lolmx avatar luchianenco avatar mollokhan avatar ndench avatar qdequippe avatar sadikoff avatar smatyas avatar soullivaneuh avatar tacman avatar thewilkybarkid avatar tristanbes avatar weaverryan avatar yozhef avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oauth2-client-bundle's Issues

OAuthException: (#12) bio field is deprecated for versions v2.8 and higher

Hi everyone.

I have the next error when I try to login with facebook.

[2018-02-18 12:36:58] request.CRITICAL: Uncaught PHP Exception League\OAuth2\Client\Provider\Exception\IdentityProviderException: "OAuthException: (#12) bio field is deprecated for versions v2.8 and higher" at /home/danielshobot/public_html/vendor/league/oauth2-facebook/src/Provider/Facebook.php line 156 {"exception":"[object] (League\OAuth2\Client\Provider\Exception\IdentityProviderException(code: 12): OAuthException: (#12) bio field is deprecated for versions v2.8 and higher at /home/shobot/public_html/vendor/league/oauth2-facebook/src/Provider/Facebook.php:156)"} []

Unit tests fail when additional dependencies are not installed

KnpU\OAuth2ClientBundle\Tests\FunctionalTest::testServicesAreUsable needs to skip services that are not installed.

MyMachine:knpoa alex$ composer require knpuniversity/oauth2-client-bundle
Using version ^0.5.0 for knpuniversity/oauth2-client-bundle
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)

  • Installing guzzlehttp/promises (1.0.3)
    Downloading: 100%
  • Installing psr/http-message (1.0)
    Downloading: 100%
  • Installing guzzlehttp/psr7 (1.2.2)
    Downloading: 100%
  • Installing guzzlehttp/guzzle (6.1.1)
    Downloading: 100%
  • Installing ircmaxell/security-lib (1.0.0)
    Downloading: 100%
  • Installing ircmaxell/random-lib (v1.1.0)
    Downloading: 100%
  • Installing league/oauth2-client (1.2.0)
    Downloading: 100%
  • Installing symfony/http-foundation (v3.0.2)
    Downloading: 100%
  • Installing symfony/routing (v3.0.2)
    Downloading: 100%
  • Installing symfony/dependency-injection (v3.0.2)
    Downloading: 100%
  • Installing symfony/polyfill-mbstring (v1.1.0)
    Downloading: 100%
  • Installing symfony/translation (v3.0.2)
    Downloading: 100%
  • Installing symfony/templating (v3.0.2)
    Downloading: 100%
  • Installing symfony/stopwatch (v3.0.2)
    Downloading: 100%
  • Installing symfony/polyfill-util (v1.1.0)
    Downloading: 100%
  • Installing symfony/polyfill-php56 (v1.1.0)
    Downloading: 100%
  • Installing symfony/security-core (v3.0.2)
    Downloading: 100%
  • Installing paragonie/random_compat (1.1.6)
    Loading from cache
  • Installing symfony/polyfill-php70 (v1.1.0)
    Downloading: 100%
  • Installing symfony/security-csrf (v3.0.2)
    Downloading: 100%
  • Installing psr/log (1.0.0)
    Loading from cache
  • Installing symfony/debug (v3.0.2)
    Downloading: 100%
  • Installing symfony/event-dispatcher (v3.0.2)
    Downloading: 100%
  • Installing symfony/http-kernel (v3.0.2)
    Downloading: 100%
  • Installing symfony/finder (v3.0.2)
    Downloading: 100%
  • Installing symfony/filesystem (v3.0.2)
    Downloading: 100%
  • Installing symfony/config (v3.0.2)
    Downloading: 100%
  • Installing symfony/class-loader (v3.0.2)
    Downloading: 100%
  • Installing symfony/asset (v3.0.2)
    Downloading: 100%
  • Installing doctrine/cache (v1.6.0)
    Loading from cache
  • Installing doctrine/lexer (v1.0.1)
    Loading from cache
  • Installing doctrine/annotations (v1.2.7)
    Downloading: 100%
  • Installing symfony/framework-bundle (v3.0.2)
    Downloading: 100%
  • Installing knpuniversity/oauth2-client-bundle (0.5.0)
    Downloading: 100%

symfony/routing suggests installing symfony/yaml (For using the YAML loader)
symfony/routing suggests installing symfony/expression-language (For using expression matching)
symfony/dependency-injection suggests installing symfony/yaml ()
symfony/dependency-injection suggests installing symfony/proxy-manager-bridge (Generate service proxies to lazy load them)
symfony/translation suggests installing symfony/yaml ()
symfony/security-core suggests installing symfony/validator (For using the user password constraint)
symfony/security-core suggests installing symfony/expression-language (For using the expression voter)
symfony/security-core suggests installing symfony/ldap (For using LDAP integration)
paragonie/random_compat suggests installing ext-libsodium (Provides a modern crypto API that can be used to generate random bytes.)
symfony/http-kernel suggests installing symfony/browser-kit ()
symfony/http-kernel suggests installing symfony/console ()
symfony/http-kernel suggests installing symfony/var-dumper ()
symfony/class-loader suggests installing symfony/polyfill-apcu (For using ApcClassLoader on HHVM)
symfony/framework-bundle suggests installing symfony/console (For using the console commands)
symfony/framework-bundle suggests installing symfony/form (For using forms)
symfony/framework-bundle suggests installing symfony/serializer (For using the serializer service)
symfony/framework-bundle suggests installing symfony/validator (For using validation)
symfony/framework-bundle suggests installing symfony/yaml (For using the debug:config and lint:yaml commands)
symfony/framework-bundle suggests installing symfony/property-info (For using the property_info service)
symfony/framework-bundle suggests installing symfony/process (For using the server:run, server:start, server:stop, and server:status commands)
Writing lock file
Generating autoload files

MyMachine:knpoa alex$ phpunit -c vendor/knpuniversity/oauth2-client-bundle/ --bootstrap vendor/autoload.php
PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

Configuration read from /private/var/www/tests/knpoa/vendor/knpuniversity/oauth2-client-bundle/phpunit.xml.dist

......................E

Time: 228 ms, Memory: 11.50Mb

There was 1 error:

  1. KnpU\OAuth2ClientBundle\Tests\FunctionalTest::testServicesAreUsable
    LogicException: Run composer require league/oauth2-facebook in order to use the "facebook" OAuth provider.

/private/var/www/tests/knpoa/vendor/knpuniversity/oauth2-client-bundle/DependencyInjection/KnpUOAuth2ClientExtension.php:113
/private/var/www/tests/knpoa/vendor/knpuniversity/oauth2-client-bundle/DependencyInjection/KnpUOAuth2ClientExtension.php:89
/private/var/www/tests/knpoa/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php:55
/private/var/www/tests/knpoa/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php:39
/private/var/www/tests/knpoa/vendor/symfony/dependency-injection/Compiler/Compiler.php:107
/private/var/www/tests/knpoa/vendor/symfony/dependency-injection/ContainerBuilder.php:540
/private/var/www/tests/knpoa/vendor/symfony/http-kernel/Kernel.php:477
/private/var/www/tests/knpoa/vendor/symfony/http-kernel/Kernel.php:117
/private/var/www/tests/knpoa/vendor/knpuniversity/oauth2-client-bundle/Tests/FunctionalTest.php:14

FAILURES!
Tests: 23, Assertions: 32, Errors: 1.

Symfony 4 support

Is this compatible with Symfony 4? If not, what work needs to be done to make it work? If yes, can it be added to composer.json so it will pass the compatiblity checks?

How to handle MissingAuthorizationCodeException in a user friendly way

Hi,

In case user clicks on cancel button on facebook permission popup then on development env i see MissingAuthorizationCodeException which is fine, but i need to display a friendly message on frontend rather than seeing a blank white screen on production. How can i handle this inside the FacebookAuthenticator as it does not seem to be going inside onAuthenticationFailure method?

Can't install in Symfony 2.7.33

Using version ^1.10 for knpuniversity/oauth2-client-bundle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

Problem 1
- Installation request for knpuniversity/oauth2-client-bundle ^1.10 -> satisfiable by knpuniversity/oauth2-client-bundle[1.10.0].
- Conclusion: remove symfony/symfony v2.7.33
- Conclusion: don't install symfony/symfony v2.7.33
- knpuniversity/oauth2-client-bundle 1.10.0 requires symfony/dependency-injection ^2.8|^3.0 -> satisfiable by symfony/dependency-injection[v2.8.0, v2.8.1, v2.8.10, v2.8.11, v2.8.12, v2.8.13, v2.8.14, v2.8.15, v2.8.16, v2.8.17, v2.8.18, v2.8.19, v2.8.2, v2.8.20, v2.8.21, v2.8.22, v2.8.23, v2.8.24, v2.8.25, v2.8.26, v2.8.3, v2.8.4, v2.8.5, v2.8.6, v2.8.7, v2.8.8, v2.8.9, v3.0.0, v3.0.1, v3.0.2, v3.0.3, v3.0.4, v3.0.5, v3.0.6, v3.0.7, v3.0.8, v3.0.9, v3.1.0, v3.1.1, v3.1.10, v3.1.2, v3.1.3, v3.1.4, v3.1.5, v3.1.6, v3.1.7, v3.1.8, v3.1.9, v3.2.0, v3.2.1, v3.2.10, v3.2.11, v3.2.12, v3.2.13, v3.2.2, v3.2.3, v3.2.4, v3.2.5, v3.2.6, v3.2.7, v3.2.8, v3.2.9, v3.3.0, v3.3.1, v3.3.2, v3.3.3, v3.3.4, v3.3.5, v3.3.6].
- don't install symfony/dependency-injection v2.8.0|don't install symfony/symfony v2.7.33
- don't install symfony/dependency-injection v2.8.1|don't install symfony/symfony v2.7.33
- don't install symfony/dependency-injection v2.8.10|don't install symfony/symfony v2.7.33
- don't install symfony/dependency-injection v2.8.11|don't install symfony/symfony v2.7.33
..................................................................................................................................................................

Bundle Configuration badly merges clients

I have the following configuration in the main config file (config/packages/knpu_oauth2_client.yaml) :

knpu_oauth2_client:
    clients:
        gsuite:
            # must be "google" - it activates that type!
            type: google
            # add and configure client_id and client_secret in parameters.yaml
            client_id: "%gsuite_client_id%"
            client_secret: "%gsuite_client_secret%"
            # a route name you'll create
            redirect_route: admin_connect_gsuite_check
            redirect_params: {}

And, in another config file (config/packages/another_vendor.yaml) :

knpu_oauth2_client:
    clients:
        custom_connect:
            type: generic
            provider_class: Vendor\ConnectBundle\Security\Authentication\Provider\CustomConnectProvider
            ...

After configuration being processed, the clients are badly indexed :

array:1 [โ–ผ
    "clients" => array:2 [โ–ผ
        "kiplin_connect" => array:6 [โ–ถ]
        0 => array:6 [โ–ถ]
    ]
]

This is due to the way prototype arrays are merged in the Symfony Config component (from PrototpyedArrayNode class) :

    protected function mergeValues($leftSide, $rightSide)
    {
        if (false === $rightSide) {
            // if this is still false after the last config has been merged the
            // finalization pass will take care of removing this key entirely
            return false;
        }

        if (false === $leftSide || !$this->performDeepMerging) {
            return $rightSide;
        }

        foreach ($rightSide as $k => $v) {
            // prototype, and key is irrelevant, so simply append the element
            if (null === $this->keyAttribute) {
                $leftSide[] = $v;
                continue;
            }

            // no conflict
            if (!array_key_exists($k, $leftSide)) {
                if (!$this->allowNewKeys) {
                    $ex = new InvalidConfigurationException(sprintf(
                        'You are not allowed to define new elements for path "%s". '.
                        'Please define all elements for this path in one config file.',
                        $this->getPath()
                    ));
                    $ex->setPath($this->getPath());

                    throw $ex;
                }

                $leftSide[$k] = $v;
                continue;
            }

            $prototype = $this->getPrototypeForChild($k);
            $leftSide[$k] = $prototype->merge($leftSide[$k], $v);
        }

        return $leftSide;
    }

See the first line in the foreach statement :

// prototype, and key is irrelevant, so simply append the element

And so the right site is appended to the left side with no key preservation...

Authenticated NO

Hi,

I am having a weird problem when I register the first time ever and then i fill up the registration password fields i get logged it but the profiler says "Authenticated NO" but if i log out and log back in using the same facebook account this time the Authenticated is YES.

There is one thing I changed to integrated it in current system

  1. We don't need username and email for authentication, we only have email address that we authenticate against, so instead of creating email property in entity we are using username property to save email and then i made the necessary changes required inside the controller to save email in username.

I am having hard time finding out why the first time Authenticated is No and then its Yes once i log out and log back in.

Force Facebook OAuth redirect_uri using https

I am developed login with Facebook, and I add configuration like this:

            type: facebook
            client_id: %Client_id%
            client_secret: %Client_secret%
            redirect_route: auth.facebook.check
            graph_api_version: v2.12

but when I login with facebook, the redirect_uri is non-https uri, how to force redirect_uri with https?

Is there a way to redirect to previous page after redirect_route?

Hi,

I've been using this for months now with no issues but I'd like to enhance it by redirecting back the user connecting to its previously visiting page.

I'm using both Facebook and Google clients with the Guard implementation and both my authenticators have onAuthenticationSuccess set to redirect to my homepage. Which sucks.

Here's my FacebookAuthenticator.php

<?php

namespace AppBundle\Security;

use AppBundle\Entity\User;
use KnpU\OAuth2ClientBundle\Security\Exception\FinishRegistrationException;
use KnpU\OAuth2ClientBundle\Security\Helper\FinishRegistrationBehavior;
use Doctrine\ORM\EntityManager;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Helper\PreviousUrlHelper;
use KnpU\OAuth2ClientBundle\Security\Helper\SaveAuthFailureMessage;
use Symfony\Component\DependencyInjection\ContainerInterface;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\Facebook;
use League\OAuth2\Client\Provider\FacebookUser;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use Symfony\Component\HttpFoundation\Request;
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 KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;

class FacebookAuthenticator extends SocialAuthenticator
{
    private $clientRegistry;
    private $em;
    private $router;
    private $encoder;
    private $locales;

    public function __construct(ClientRegistry $clientRegistry, EntityManager $em, RouterInterface $router, UserPasswordEncoder $encoder, $locales)
    {
        $this->clientRegistry = $clientRegistry;
        $this->em = $em;
        $this->router = $router;
        $this->encoder = $encoder;
        $this->locales = $locales;
    }

    public function getCredentials(Request $request)
    {
        foreach ($this->locales as $locale) {
            if ($request->getPathInfo() == '/' . $locale . '/connect/facebook/check') {
                return $this->fetchAccessToken($this->getFacebookClient());
            }
        }
        return;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        /** @var FacebookUser $facebookUser */
        $facebookUser = $this->getFacebookClient()
            ->fetchUserFromToken($credentials);
        $email = $facebookUser->getEmail();

        //do stuff with the user (add to db... or logging in if exists)
        return $user;
    }

    /**
     * @return FacebookClient
     */
    private function getFacebookClient()
    {
        return $this->clientRegistry
            // "facebook_main" is the key used in config.yml
            ->getClient('facebook');
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        // check credentials - e.g. make sure the password is valid
        // no credential check is needed in this case

        // return true to cause authentication success
        return true;
    }

    /**
     * @param Request $request
     * @param AuthenticationException $exception
     *
     * @return Response|null
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        $url = $this->router->generate('security_login');

        return new RedirectResponse($url);
    }

    /**
     * @param Request $request
     * @param TokenInterface $token
     * @param string $providerKey The provider (i.e. firewall) key
     *
     * @return Response|null
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        $url = $this->router->generate('homepage');

        return new RedirectResponse($url);
    }

    /**
     * @param Request $request
     * @param AuthenticationException $authException
     *
     * @return Response
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        $url = $this->router->generate('security_login');

        return new RedirectResponse($url);
    }

    public function supportsRememberMe()
    {
        return true;
    }
}

And my FacebookController.php

<?php
namespace AppBundle\Controller\Connect;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Entity\User;

/**
 * Class FacebookController
 * @Route("/connect/facebook")
 */
class FacebookController extends Controller
{
    /**
     * Link to this controller to start the "connect" process
     *
     * @Route("/", name="connect_facebook")
     */
    public function connectAction(Request $request)
    {
        // will redirect to Facebook!
        return $this->get('oauth2.registry')
            ->getClient('facebook') // key used in config.yml
            ->redirect();
            // I tried the following:
            // ->redirect([], ['redirect_params' => $request->headers->get('referer')]);
    }

    /**
     * After going to Facebook, you're redirected back here
     * because this is the "redirect_route" you configured
     * in config.yml
     *
     * @Route("/check", name="connect_facebook_check")
     */
    public function connectCheckAction(Request $request)
    {
    }
}

Is there a way to do this using redirect_params so the callback url keeps the user visited page? Right now I'm able to send additional parameters to facebook but it won't give it back to me in the callback url (connect_facebook_check). It seems like a basic functionality and I'm pretty sure it's already implemented but I can't get hold of it.

Or maybe I'm just misusing this code \o\

Invalid state because session is renewed in the process :(

Hello,

I'm reopening my project, ran composer update (3.3.10) and i'm being stuck on the login process.
All my provider returns an "invalid state" and after dumping around, I can see that

        if (!$this->isStateless) {
            $this->getSession()->set(
                self::OAUTH2_SESSION_STATE_KEY,
                $this->provider->getState()
            );
            dump($this->getSession()->getId(), $this->provider->getState());
        }

sessionId : ueqegube9b5mn6q4g56vnqtgj4
state: 5caa08e1139b924c38a202a8545eec05

public function getAccessToken()
    {
        if (!$this->isStateless) {
            $expectedState = $this->getSession()->get(self::OAUTH2_SESSION_STATE_KEY);
            $actualState = $this->getCurrentRequest()->query->get('state');
            dump('excpetced', $this->getSession()->getId(), $expectedState, $actualState);

sessionId: mclqjn3ukffomrhgvvg41qsgs1
expectedState: null
actualState: 5caa08e1139b924c38a202a8545eec05

I don't even know why my session is lost in the process. Is this a Symfony issue or something is happening here ?

cfg for session

    session:
        handler_id: snc_redis.session.handler
        cookie_lifetime: 7776000
        cookie_domain: ".%domain%"
        name: SFSESSID

"Auth0 account is not specified"

Hello,

I'm trying to connect through Auth0, but I get the error Auth0 account is not specified from the riskio/oauth2-auth0 bundle when I try to reach /connect/auth0 route.

What is wrong ?

My setup is the following (Symfony 3.4)

knpu_oauth2_client:
    clients:
        auth0:
            type: auth0
            client_id: '%auth0_client_id%' (in parameters.yml)
            client_secret: '%auth0_client_secret%' (in parameters.yml)
            redirect_route: connect_auth0_check
            redirect_params: {}
    /**
     * @Route("/connect/auth0")
     *
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     * @throws \InvalidArgumentException
     */
    public function connectAction()
    {
        return $this->get('oauth2.registry')
            ->getClient('auth0')
            ->redirect();
    }

    /**
     * @Route("/connect/auth0/check", name="connect_auth0_check")
     *
     * @param Request $request
     * @throws \InvalidArgumentException
     */
    public function connectCheckAction(Request $request)
    {
        $client = $this->get('oauth2.registry')
            ->getClient('auth0');

        try {
            $user = $client->fetchUser();
            var_dump($user->getName());
        } catch (IdentityProviderException $e) {
            var_dump($e->getMessage());die;
        }
    }

Problem with Guard Authenticator

Hi,
I am trying to set up the client for Facebook. Everything works perfectly (well almost). I get the token, I get my user from my database, but when I have to log my user, it is not done.

Here is what happens:

  • Click on the FB connection
  • I validate the access rights FB (public profile, etc.)
  • I come back to my application
  • I'm not connecting / connect / facebook / check
  • I go through the different functions of my authenticator
  • When I arrive in the onAuthenticationSuccess I am logged in (I saw it with the profiler)
  • I am redirected to the homepage and my user is no longer logged in ... (The session does not register it looks like ...
//AppBundle\Controller\Web\Security\FacebookLoginController.php
<?php

namespace AppBundle\Controller\Web\Security;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
 * Class FacebookLoginController
 */
class FacebookLoginController extends Controller {
	/**
	 * Link to this controller to start the "connect" process
	 *
	 * @Route("/connect/facebook", name="connect_facebook_login")
	 */
	public function connectAction() {
		// will redirect to Facebook!
		return $this->get('oauth2.registry')
			->getClient('facebook_main')// key used in config.yml
			->redirect();
	}

	/**
	 * After going to Facebook, you're redirected back here
	 * because this is the "redirect_route" you configured
	 * in config.yml
	 *
	 * @Route("/connect/facebook/check", name="connect_facebook_check")
	 */
	public function connectCheckAction() {

	}
}
#AuthenticatorService
    AppBundle\Services\Security\FacebookAuthenticator:
        class: AppBundle\Services\Security\FacebookAuthenticator
        autowire: true
#Firewall
        main:
            form_login:
                login_path: /%locale%/login
                check_path: /%locale%/login_check
            anonymous: ~
            remember_me:
                secret: "%secret%"
                lifetime: 3600
                path: /
            logout:
                path: /%locale%/logout
                target: /%locale%
            guard:
                authenticators:
                    - cng.form_authenticator
                    - AppBundle\Services\Security\FacebookAuthenticator
                entry_point: cng.form_authenticator
//AppBundle\Services\Security\FacebookAuthenticator
<?php

namespace AppBundle\Services\Security;

use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use Doctrine\ORM\EntityManager;
use League\OAuth2\Client\Provider\FacebookUser;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class FacebookAuthenticator extends SocialAuthenticator {
	/** @var ClientRegistry $clientRegistry */
	private $clientRegistry;

	/** @var EntityManager $em */
	private $em;

	/** @var TokenStorage $token_storage */
	private $token_storage;

	/** @var EventDispatcherInterface */
	private $event_dispatcher;

	/** @var RequestStack $request */
	private $request;

	/** @var RouterInterface $router_interface */
	private $router_interface;

	public function __construct(
		ClientRegistry $clientRegistry,
		EntityManager $em,
		TokenStorage $tokenStorage,
		EventDispatcherInterface $eventDispatcher,
		RequestStack $request,
		RouterInterface $router
	) {
		$this->clientRegistry = $clientRegistry;
		$this->em = $em;
		$this->token_storage = $tokenStorage;
		$this->event_dispatcher = $eventDispatcher;
		$this->request = $request;
		$this->router_interface = $router;
	}

	public function supports(Request $request) {
		// continue ONLY if the URL matches the check URL
		return $request->getPathInfo() == '/connect/facebook/check';
	}

	public function getCredentials(Request $request) {
		// this method is only called if supports() returns true

		return $this->fetchAccessToken($this->getFacebookClient());
	}

	public function getUser($credentials, UserProviderInterface $userProvider) {
		/** @var FacebookUser $facebookUser */
		$facebookUser = $this->getFacebookClient()
			->fetchUserFromToken($credentials);

		$email = $facebookUser->getEmail();

		$existingUser = $this->em->getRepository('AppBundle:User')
			->findOneBy(['facebookId' => $facebookUser->getId()]);
		if ($existingUser) {
			return $existingUser;
		}

		$user = $this->em->getRepository('AppBundle:User')
			->findOneBy(['email' => $email]);

		$user->setFacebookId($facebookUser->getId());
		$this->em->persist($user);
		$this->em->flush();

		return $user;
	}

	/**
	 * @return \KnpU\OAuth2ClientBundle\Client\OAuth2Client
	 */
	private function getFacebookClient() {
		return $this->clientRegistry
			// "facebook_main" is the key used in config.yml
			->getClient('facebook_main');
	}

	public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {

	}

	public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) {
		return new RedirectResponse($this->router_interface->generate('index-front'));
	}

	public function start(Request $request, AuthenticationException $authException = null) {
		$url = $this->router_interface->generate('security_login');

		return new RedirectResponse($url);
	}
}

I don't know what's going on...Is it normal ?

thanks in advance.
Christophe Lablancherie

It's possible to avoid the whole container injection in the ClientRegistry service?

I just discovered that the whole container is injected in the ClientRegistry service (to get dinamically the social client) and I saw in the docs:

CAUTION You can also inject the individual client (e.g. FacebookClient) into your authenticator instead of the ClientRegistry. However, this may cause ciricular reference issues and degrades performance (because autheticators are instantiated on every request, even though you rarely need the FacebookClient to be created). The ClientRegistry lazily creates the client objects.

but the ClientRegistry service it's not lazy loaded (lazy="true") and this means it will always be instantiated (and inflated with the whole container). I'm right? Can you suggest another approach on which I can work on?

PS: what do you think to add a new tag on StackOverflow for this bundle? It would be better for this type of questions, don't you? (I have only 1400 points ๐Ÿ˜† )

Twitter client

Could you please create Twitter Client, coz it more difficult to implement with "request token". Guess oauth client has to be extended.

Fetching GitHub email from API if not provided on the profile response

Sometime, GitHub does not give the user email on the profile response: thephpleague/oauth2-github#3

IMHO, if the email does not exists, we should call the email API yourself. Otherwise, it will just fail.

Maybe it can be done here:

public function fetchUser()
{
return parent::fetchUser();
}

It was done like that for HWI: hwi/HWIOAuthBundle#758

Response null given

Hello, at the outset I want to apologize for my bad English.
I have work to do authentication token JWT and services facebook and google. JWT token works well. But it does not work well this package oauth. I want to do Authenticating with Guard. When a user is logged on Facebook, it's redirected to the login page of Facebook and click the log is returned the error "The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller." The firewall does not work, does not redirect the user even though it was logged on Facebook. Could you look up and see my configurations? I am a bit of a novice in symfony. Best Regards

security.yml

    firewalls:
        main:
            pattern: ^/
            anonymous: true
            guard:
                authenticators:
                    - facebook_authenticator
                    - jwt_token_authenticator
                entry_point:  jwt_token_authenticator

    access_control:
        - { path: ^/_wdt|_profiler, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, roles: IS_AUTHENTICATED_FULLY } 

services.yml

services:
    jwt_token_authenticator:
        class: AppBundle\Security\JwtTokenAuthenticator
        autowire: true

    facebook_authenticator:
        class: AppBundle\Security\FacebookAuthenticator
        autowire: true
...

connect

/**
     * @Route("/connect/facebook", name="connect_facebook")
     */
    public function connectAction()
    {
        // will redirect to Facebook!
        return $this->get('oauth2.registry')
            ->getClient('facebook_main') // key used in config.yml
            ->redirect();
    }

    /**
     * @Route("/connect/facebook/check", name="connect_facebook_check")
     */
    public function connectCheckAction(Request $request)
    {
    }

config.yml

knpu_oauth2_client:
    clients:
        facebook_main:
            type: facebook
            client_id: %facebook_app_id%
            client_secret: %facebook_app_secret%
            redirect_route: connect_facebook_check
            redirect_params: {}
            graph_api_version: v2.3

FacebookAuthenticator

    private $clientRegistry;
    private $em;
    private $router;

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

    public function getCredentials(Request $request)
    {
        if ($request->getPathInfo() != '/api/connect/facebook-check') {
            // don't auth
            return;
        }

        return $this->fetchAccessToken($this->getFacebookClient());
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        /** @var FacebookUser $facebookUser */
        $facebookUser = $this->getFacebookClient()
            ->fetchUserFromToken($credentials);

        $email = $facebookUser->getEmail();

        // 1) have they logged in with Facebook before? Easy!
        $existingUser = $this->em->getRepository(User::class)
            ->findOneBy(['facebookId' => $facebookUser->getId()]);
        if ($existingUser) {
            return $existingUser;
        }

        // 2) do we have a matching user by email?
        $user = $this->em->getRepository('AppBundle:User')
            ->findOneBy(['email' => $email]);

        // 3) Maybe you just want to "register" them by creating
        // a User object
        $user->setFacebookId($facebookUser->getId());
        $this->em->persist($user);
        $this->em->flush();

        return $user;
    }

    /**
     * @return FacebookClient
     */
    private function getFacebookClient()
    {
        return $this->clientRegistry
            // "facebook_main" is the key used in config.yml
            ->getClient('facebook_main');
    }


    /**
     * @param Request $request
     * @param AuthenticationException $exception
     *
     * @return Response|null
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        $url = $this->router->generate('security_login');

        return new RedirectResponse($url);
    }

    /**
     * @param Request $request
     * @param TokenInterface $token
     * @param string $providerKey The provider (i.e. firewall) key
     *
     * @return Response|null
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        $url = $this->router->generate('homepage');

        return new RedirectResponse($url);
    }

    /**
     * @param Request $request
     * @param AuthenticationException $authException
     *
     * @return Response
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        $url = $this->router->generate('security_login');

        return new RedirectResponse($url);
    }

routing.yml

app_secure:
    resource: "@AppBundle/Controller/Security"
    type:     annotation
    prefix:    /api

OAuth2Client: require $request as an arg to getAccessToken()

Before

public function getAccessToken()
// ...

// usage
$client->getAccessToken();

After

public function getAccessToken(Request $request)
// ...

// usage
$client->getAccessToken($request);

It's certainly more expressive, and I think probably worth it, at the cost of slightly more work when using. But users really need to know how to get the Request object.

The guard example documentation should be updated

On Symfony 4.0, if we extends your GuardAuthenticator, we have to implements more method like start or supports.

For example with this part:

    public function getCredentials(Request $request)
    {
        if ($request->getPathInfo() != '/connect/facebook/check') {
            // don't auth
            return;
        }

        return $this->fetchAccessToken($this->getFacebookClient());
    }

The request path check should be done on the supports method.

[RFC] Make providers really decoupled

IMO our current approach to store all providers in one repository has very serious flaw which was demonstraded in #10's reverse. The main problem is that you cannot automatically check dependency graph of new or existing providers because you aren't really require them.

Moreover distribute bundle with all 100500 provider classes inside it when you're using only 1-2 in your project ain't good idea.

So i think once we more or less stabilize our API we could do something like this:

  • Trash $supportedProviderTypes
  • Refactor provider config to something like this:
knpu_oauth2_client:
    clients:
        facebook_main:
            class: \Some\Vendor\Namespace\Provider\FacebookProviderConfigurator
            client_id: YOUR_FACEBOOK_APP_ID
            client_secret: YOUR_FACEBOOK_APP_SECRET
            # the route that you're redirected to after
            # see the controller example below
            redirect_route: connect_facebook_check
            redirect_params: {}
            some_cool_additional_params: { graph_api_version: v2.3  }

Config is partly inspired by SyliusResourceBundle

This will allow you to install new providers just by running composer require (just like current installing providers for league's oauth client lib) and adding few lines of yml. Additionally, it would also allow us to prevent issues like #10

WDYT @weaverryan ?

Choose query or request in getAccessToken method

Hi,

Is it possible to choose between query or request in getAccessToken method thanks to the config file ?
For example:

# app/config/config.yml
knpu_oauth2_client:
    use_query: true # or false, if false request will be used
    clients:
        # the key "facebook_main" can be anything, it
        # will create a service: "knpu.oauth2.client.facebook_main"
        facebook_main:
            # this will be one of the supported types
            type: facebook
            client_id: YOUR_FACEBOOK_APP_ID
            client_secret: YOUR_FACEBOOK_APP_SECRET
            # the route that you're redirected to after
            # see the controller example below
            redirect_route: connect_facebook_check
            redirect_params: {}
            graph_api_version: v2.3
namespace KnpU\OAuth2ClientBundle\Client;
...
public function getAccessToken()
    {
        if (!$this->isStateless) {
            $expectedState = $this->getSession()->get(self::OAUTH2_SESSION_STATE_KEY);
            $actualState = $this->getCurrentRequest()->query->get('state');
            if (!$actualState || ($actualState !== $expectedState)) {
                throw new InvalidStateException('Invalid state');
            }
        }
        if( use_query ) $code = $this->getCurrentRequest()->query->get('code');
        else $code = $this->getCurrentRequest()->request->get('code');
        if (!$code) {
            throw new MissingAuthorizationCodeException('No "code" query parameter was found!');
        }
        return $this->provider->getAccessToken('authorization_code', array(
            'code' => $code
        ));
    }

Scope and Re-access information

Hi,

I managed to get it to work with Facebook and everything is working perfectly file.

I have 3 questions

Question 1

I want to fetch more details of user from Facebook like user_education_history and user_work_history.

My assumption is that in Controller I need to provide the scope I need for example as following

    /**
     * @Route("/connect/facebook", name="connect_facebook")
     */
    public function connectFacebookAction(Request $request)
    {
        // redirect to Facebook
        $facebookClient = $this->get('knpu.oauth2.registry')
            ->getClient('my_facebook_client');

        return $facebookClient->redirect([
            'public_profile', 'email', 'user_education_history', 'user_work_history'
        ]);
    }

By giving the scope I do see in facebook permission popup that education and work are listed so I just wanted to confirm if this is the right way?

Question 2
Suppose I want to give a button to user which they can use to pull updated work and education history from Facebook or LinkedIn, how is that going to be possible? Is there already a service which we can use for this purpose? An example will really be nice to see how this is going to work.

Question 3
I am looking at the tutorial here and when I dump the $accessToken, the refreshToken is null. If my understanding is right I think I will need the refreshToken in case the token expires so how can i get hold of refreshToken?

installation example gist & LongTermAccessToken question

Hi Ryan,

So I've been meaning to showcase my installation as a gist for other users to help people understand how to install this.
It's not easy to install, and there are so many different variances that come into play when installing.

I've also posted it for some help - i'm trying to understand where in my code i would request my longTermAccessToken?
Currently: the user can log in; they get the my registration page so i can get additional information; and i'm saving all of this into the database. The only value i'm not saving is the LongTermAccessToken in the database.
I'm also wondering how often i should re-request the LongTermAccessToken / and on what screen?

I'll be happy to move this gist into this repo's main readme.md as an additional installation example.

https://gist.github.com/rwitchell/f380be61d6d738c78ab567e79b04ffa7

Option approval_prompt is default "auto"

Hi,

Google API forces us to pass the "approval_prompt" = "force" option so that we can retrieve the refresh_token.
The problem is in the redirect function of the OAuth2Client class.

I propose to give the possibility to spend more options as follows:
Public function redirect (array $ scopes = [], $ options = []) {
.....
}
instead of
Public function redirect (array $ scopes = []) {
$ Options = [];
....
}
So we can crush the frozen values like 'response_type' => 'code' and 'approval_prompt' => 'auto' if we want.

Thanks
BBI Fondative

symfony4 bundle configuration

there is no app/config/config.yml anymore in v4. where should I configure this bundle part in v4? (sorry it's probably idiot...), thanks

Facebook Provider - Invalid State Exception

I have Implemented a oauth2 sandbox, but currently I get error when I try to log in with the Facebook Provider. Any hint what is wrong?

`
/app/vendor/knpuniversity/oauth2-client-bundle/src/Client/OAuth2Client.php:97: {

: if (!$actualState || ($actualState !== $expectedState)) {

:     throw new InvalidStateException('Invalid state');

: }

}
`

this is my config.yml:

`
knpu_oauth2_client:

clients:

    facebook_main:

        type: facebook

        client_id: "%facebook_app_id%"

        client_secret: "%facebook_app_secret%"

        redirect_route: connect_facebook_check

        redirect_params: {}

        graph_api_version: v2.8

`

Handle IdentityProviderException properly with guard

Example URL: http://localhost:8000/oauth/connect/github?code=8b79dc6d93813956f275&state=53adf20aaed5b0614a5b46d3805887b7

Here, this URL is a redirect from github, but the code already expired.

Currently, this is throwing a 500 error with the GithubIdentityProviderException -> bad_verification_code error.

You gave a great sample with FacebookController of how to handle that, but what about the security guard?

Here is mine (an abstract one):

abstract class AbstractSocialAuthenticator extends SocialAuthenticator
{
    /**
     * @var RouterInterface
     */
    private $router;

    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * @var FlashBagInterface
     */
    private $flashBag;

    /**
     * @var ClientRegistry
     */
    private $clientRegistry;

    /**
     * @var UserManager|UserManagerInterface
     */
    private $userManager;

    public function __construct(RouterInterface $router, TokenStorageInterface $tokenStorage, FlashBagInterface $flashBag, ClientRegistry $clientRegistry, UserManagerInterface $userManager)
    {
        Assert::isInstanceOf($userManager, UserManager::class);

        $this->router = $router;
        $this->tokenStorage = $tokenStorage;
        $this->flashBag = $flashBag;
        $this->clientRegistry = $clientRegistry;
        $this->userManager = $userManager;
    }

    /**
     * {@inheritdoc}
     */
    final public function start(Request $request, AuthenticationException $authException = null)
    {
        return new RedirectResponse($this->router->generate('login'));
    }

    /**
     * {@inheritdoc}
     */
    final public function supports(Request $request)
    {
        return $request->getPathInfo() === $this->router->generate($this->getSocialConnectRouteName());
    }

    /**
     * {@inheritdoc}
     */
    final public function getCredentials(Request $request)
    {
        return $this->fetchAccessToken($this->getOAuthClient());
    }

    /**
     * {@inheritdoc}
     */
    final public function getUser($credentials, UserProviderInterface $userProvider)
    {
        if (!$credentials instanceof AccessToken || !$userProvider instanceof UserProvider) {
            return null;
        }

        return $this->getUserByAccessToken($credentials, $userProvider);
    }

    final public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        $this->flashBag->add('error', $exception->getMessage());

        return new RedirectResponse($this->router->generate('homepage'));
    }

    final public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        return new RedirectResponse($this->getPreviousUrl($request, $providerKey));
    }

    final protected function getClientRegistry(): ClientRegistry
    {
        return $this->clientRegistry;
    }

    final protected function getUserManager(): UserManager
    {
        return $this->userManager;
    }

    final protected function getUserByPlatformIdAndEmail(string $platformField, int $platformId, string $email): UserInterface
    {
        // If an user is already logged in, we are on the account linking process.
        // Just return the connected user to update the information.
        if (null !== $this->tokenStorage->getToken()
            &&
            $this->tokenStorage->getToken()->getUser() instanceof UserInterface
        ) {
            return $this->tokenStorage->getToken()->getUser();
        }

        $user = $this->userManager->findUserBy([
            $platformField => $platformId,
        ]);

        if (null === $user) {
            $user = $this->userManager->findUserByEmail($email);
        }

        if (null === $user) {
            $user = $this->userManager->createUser()
                ->setUsername(\uniqid('u'))
                ->setPassword(\md5(\uniqid((string) \time())))
                ->setEnabled(true)
            ;
        }

        return $user;
    }

    abstract protected function getUserByAccessToken(AccessToken $accessToken, UserProvider $userProvider): User;

    abstract protected function getSocialConnectRouteName(): string;

    abstract protected function getOAuthClient(): OAuth2Client;
}

I call fetchAccessToken on getCredentials as suggested on #24 (comment), but this change nothing, the onAuthenticationFailure is never called.

Did I miss something?

Provide a guard test case

It would be good to provide a guard test case with necessary mock system to provide a fake user.

It would allows end user to focus on user creation/update logic.

WDYT?

Circular reference detected

Hi Ryan,

I keep getting the same error on every second refresh. I cannot track it down - any thoughts?

Circular reference detected for service "routing.loader", path: "routing.loader -> assetic.asset_manager -> twig -> security.authorization_checker -> security.authentication.manager -> myBundle.security.my_facebook_authenticator -> knpu.oauth2.client.facebook".
500 Internal Server Error - ServiceCircularReferenceException

my services.yml looks like the following:

comp_my.security.my_facebook_authenticator:
        class: comp\myBundle\Security\MyFacebookAuthenticator
        #autowire: true
        arguments: [ @knpu.oauth2.client.facebook, @doctrine.orm.entity_manager, @router ]
 #        calls:
 #            - [ getUserInfoFromSession, [ @request ] ]
 #            - [ getPreviousUrl, [ @request, @? ] ]
 #            - [ onAuthenticationFailure, [ @request, @? ] ]
 #            - [ createAuthenticatedToken, [ @security.user.provider.in_memory.user, @? ] ]
 #            - [ onAuthenticationSuccess, [ @request, @?, @? ] ]
 #            - [ getUser, [ @?, @security.user.provider.ldap ] ]
 #            - [ start, [ @request, @? ] ]
 #            - [ getCredentials, [ @request ] ]
 #            - [ checkCredentials, [ @?, @security.user.provider.in_memory.user ] ]
 #            

I've copied this from your 'finished' branch. the calls caused an error, so i commented them out. I tried autowire: true, but i get the same circular reference issue.

Add a coding style checker and enforce it

Adding a .php_cs file would make the project more consistent internally, I noticed a lot of missing comments, long array notation et al.

(I'll be happy to do this if asked but it's commonly considered maintainer's task and privilege to set and enforce standards).

MissingAuthorizationCodeException is thrown before processing AbstractProvider:checkResponse()

In this case we have a generic provider as shown below. When following the example code in the README, "Step 3) Use the Client Service" (also shown below, and slightly modified), trying to get the accessToken results in a MissingAuthorizationCodeException even when the "error" query parameter is set. Can anyone see what we're doing wrong that is preventing AbstractProvider:checkResponse() being called first? That is what I would expect would happen.

This is slightly related to #24.

namespace AppBundle\Controller;

use AppBundle\Component\Orcid\OauthCodeHandler;
use AppBundle\Component\Orcid\OauthErrorHandler;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;

class OrcidController extends Controller
{
    /**
     * Link to this controller to start the "connect" process.
     *
     * @Route("/connect/orcid", name="connect_orcid")
     * @Method("GET")
     */
    public function connectAction()
    {
        return $this->get('oauth2.registry')->getClient('orcid_oauth')->redirect();
    }

    /**
     * After going to Orcid, you're redirected back here.
     *
     * @Route("/connect/orcid/check", name="connect_orcid_check")
     * @Method("GET")
     */
    public function connectCheckAction(Request $request, OauthErrorHandler $oauthErrorHandler, OauthCodeHandler $oauthCodeHandler)
    {
        /** @var \KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient $client */
        $client = $this->get('oauth2.registry')
            ->getClient('orcid_oauth');

        try {
            $token = $client->getAccessToken();
            // ...
        } catch (IdentityProviderException $e) {
            // something went wrong!
            // probably you should return the reason to the user
            var_dump($e->getMessage());die;
        }
    }
}
namespace AppBundle\Component\Orcid\Provider;

use AppBundle\Component\Orcid\Exception\OrcidIdentityProviderException;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
use League\OAuth2\Client\Token\AccessToken;
use Psr\Http\Message\ResponseInterface;

class Orcid extends AbstractProvider
{
    protected $mode;

    const OAUTH_BASE_PRODUCTION = 'https://orcid.org';
    const OAUTH_BASE_SANDBOX = 'https://sandbox.orcid.org';
    const API_BASE_PRODUCTION = 'https://api.orcid.org/v2.1';
    const API_BASE_SANDBOX = 'https://api.sandbox.orcid.org/v2.1';

    public function getBaseUrl(): string
    {
        return $this->mode == 'production' ? self::OAUTH_BASE_PRODUCTION : self::OAUTH_BASE_SANDBOX;
    }

    public function getBaseAuthorizationUrl()
    {
        return $this->getBaseUrl().'/oauth/authorize';
    }

    public function getBaseAccessTokenUrl(array $params)
    {
        return $this->getBaseUrl().'/oauth/token';
    }

    private function getApiBaseUrl():string
    {
        return $this->mode == 'production' ? self::API_BASE_PRODUCTION : self::API_BASE_SANDBOX;
    }

    public function getResourceOwnerDetailsUrl(AccessToken $token)
    {
        return $this->getApiBaseUrl().'/'.$token->getValues()['orcid'].'/record';
    }

    protected function getDefaultScopes()
    {
        return [
            '/read-limited /activities/update /person/update',
        ];
    }

    protected function checkResponse(ResponseInterface $response, $data)
    {
        if (!empty($data['error'])) {
            throw new OrcidIdentityProviderException(
                $data['error'] ?: $response->getReasonPhrase(),
                $response->getStatusCode(),
                $response
            );
        }
    }

    protected function createResourceOwner(array $response, AccessToken $token): ResourceOwnerInterface
    {
        return new OrcidResourceOwner($response);
    }

    protected function getAuthorizationHeaders($token = null)
    {
        return [
            'Authorization' => "Bearer $token",
            'Accept' => 'application/vnd.orcid+json',
        ];
    }
}

Create a "generic" provider configuration

The OAuth2 client library has a "catch-all" generic provider and we should support using this.

In fact, I don't see any reason to prevent someone from configuring some sort of generic adapter and specifying their own Provider class that should be used.

Error creating the tutorial's Authenticator class

Im getting this error:

Error: Class App\Security\FacebookAuthenticator contains 3 abstract methods and must therefore be declared abstract or implement the remaining methods (Symfony\Component\Security\Guard\AuthenticatorInterface::onAuthentication Failure, Symfony\Component\Security\Guard\AuthenticatorInterface::onAuthent icationSuccess, Symfony\Component\Security\Http\EntryPoint\AuthenticationEn tryPointInterface::start)

Deprecation messages for created service

With the following basic config:

knpu_oauth2_client:
    clients:
        # the key "facebook" can be anything, it
        # will create a service: "knpu.oauth2.client.facebook"
        facebook:
            # this will be one of the supported types
            type: facebook

The following deprecation messages arise:

Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. You should rename (or alias) the "knpu.oauth2.provider.facebook" service to "League\OAuth2\Client\Provider\Facebook" instead.

redirect_params how to dynamically pass parameters to the check route

Hi there,

I'm using symfony 4.1 and I've multiple domains with optional prefixes, which makes it very difficult to configure things like this bundle. I've been searching for some info regarding how to pass extra parameters to Facebook or find a different solution. I've looked at issue #73 but seems like there is probably a simpler way of achieving this ?

Currently I have theese routes, few years ago they were talking about doing something like /({prefix}/)/connect/facebook but seems like that never actually happened so I'm stuck with making double routes.

connect_facebook:
    path: /connect/facebook
    controller: App\Controller\FacebookController::connect

connect_facebook_prefix:
    path: /{prefix}/connect/facebook
    controller: App\Controller\FacebookController::connect
    requirements:
        prefix: 'be-fr|be-nl|ch-fr|ch-de|ch-it|int|usa'

connect_facebook_check:
    path: /connect/facebook/check
    controller: App\Controller\FacebookController::connectCheck

connect_facebook_check_prefix:
    path: /{prefix}/connect/facebook/check
    controller: App\Controller\FacebookController::connectCheck
    requirements:
        prefix: 'be-fr|be-nl|ch-fr|ch-de|ch-it|int|usa'

My issue comes from my FacebookController connect function as I attempt to get the Client, it can't form the route /{prefix}/connect/facebook/check as it requires the prefix. So I would have loved to do something like $this->clientRegistry->getClient($client, ['prefix' => $prefix])->redirect();

public function connect(Request $request)
    {

        $client = 'facebook_main';

        if(!is_null($request->attributes->get('prefix'))){
            $client = 'facebook_main_prefix';
        }

        return $this->clientRegistry
            ->getClient($client)// key used in config.yml
            ->redirect();
    }

The only way I can insert the prefix is via the yaml configuration so I'm sort of stuck in doing a provider for each prefix which is completelly mad

        facebook_main_prefix:
            type: facebook
            client_id: '%env(FB_APP_ID)%'
            client_secret: '%env(FB_APP_SECRET)%'
            redirect_route: connect_facebook_check_prefix
            redirect_params:
                prefix: be-fr
            graph_api_version: v2.12

Is there a way to do this differenlty ?

Wrong redirectUrl when enabling DoctrineExtensionsBundle

Hi,

first great bundle @weaverryan. Thank you.

After installing your oauth2-client-bundle everything works.

If I add stof/doctrine-extensions-bundle and enable it, the generated redirectUrl of facebook is always http://localhost also on dev with localhost:8000 and in production.

I made a new symfony 3.1.1 installation to reproduce the problem: https://github.com/sgasser/oauth2-client-bundle-doctrine-extensions-bundle

Steps to reproduce:

  1. git clone https://github.com/sgasser/oauth2-client-bundle-doctrine-extensions-bundle
  2. composer install
  3. php bin/console server:start
  4. open http://localhost:8000/connect/facebook
  5. Redirects to http://localhost
  6. Delete stof_doctrine_extensions in config.yml
  7. open http://localhost:8000/connect/facebook
  8. Redirects correctly to http://localhost:8000

Any idea?

Some strange problem with the router?

Thanks

OAuth2 client libraries like Facebook needs "league/oauth2-client": "^2.0" to be installed!

I would like to switch to this library from HWIOAuthBundle (we like so much Guard ๐Ÿ‘), but client libraries like Facebook, Github, Instagram, Google, Linkedin, Bitbucket (and maybe others...) have a required dependency with "league/oauth2-client": "^2.0" that's locked at the "league/oauth2-client": "^1.0" version in this bundle. How we can resolve the issue?

Two questions about SocialAuthentication

I have a couple of questions here.

The SocialAuthenticator examples show stashing the Social ID in the user model. Is that just for illustrative purposes or does that really makes sense from a modeling perspective? It seems like it might make more sense to have another table to make it easier to connect multiple social accounts.

The second question is where should I store the auth token? Should I store it in my User Model?

Login with google api (Symfony4)

Hi, im using SonataAdmin and i need to redirect '/admin' if user has ROLE_ADMIN, if not to '/post'.
Already registered a user with ROLE_ADMIN in DB, all other user has to set ROLE_USER after succsesfull login via google api.
The problem is i dont know how to use the user provided from google api to login and replace anonymous token:

captura de pantalla de 2018-07-02 20-58-51

security.yml

security:
    encoders:
        App\Entity\User: bcrypt
    providers:
        database_users:
            entity: { class: App\Entity\User, property: username }
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            pattern: ^/
            anonymous: true
            form_login:
                check_path: security_login
                login_path: security_login
                csrf_token_generator: security.csrf.token_manager
                default_target_path: post_index
            logout:
                path: security_logout
                target: security_login
            guard:
                authenticators:
                    - google_authenticator
                entry_point: google_authenticator
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/post, roles: ROLE_USER }

services.yml

parameters:
    locale: 'en'
services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false
    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']
    google_authenticator:
        class: App\Controller\Auth\GoogleAuthenticator
        autowire: true

GoogleController

namespace App\Controller\Auth;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
 * @Route("/connect/google")
 */
class GoogleController extends Controller
{
    /**
     * @Route("/", name="connect_google")
     */
    public function connectAction()
    {
        return $this->get('knpu.oauth2.registry')
            ->getClient('google') // key used in config.yml
            ->redirect();
    }

    /**
     * @Route("/check", name="connect_google_check")
     */
    public function connectCheckAction(Request $request)
    {
        return $this->redirect($this->generateUrl('sonata_admin_dashboard'));
    }
}

GoogleAuthenticator

namespace App\Auth;

use App\Entity\User;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Routing\RouterInterface;
use KnpU\OAuth2ClientBundle\Client\Provider\GoogleClient;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;

class GoogleAuthenticator extends SocialAuthenticator
{
    /**
     * @var ClientRegistry $clientRegistry
     */
    private $clientRegistry;

    /**
     * @var EntityManagerInterface $clientRegistry
     */
    private $em;

    /**
     * @var RouterInterface $clientRegistry
     */
    private $router;

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

    public function supports(Request $request)
    {
        return $request->getPathInfo() == $this->router->getRouteCollection()->get('connect_google_check')->getPath();
    }

    /**
     * @param Request $request
     *
     * @return mixed|null
     * @throws IdentityProviderException
     */
    public function getCredentials(Request $request)
    {
        if ($request->getPathInfo() != $this->router->getRouteCollection()->get('connect_google_check')->getPath()) {
            return null;
        }

        try {
            return $this->fetchAccessToken($this->getGoogleClient());
        } catch (IdentityProviderException $e) {
            throw $e;
        }
    }

    /**
     * @param mixed                 $credentials
     * @param UserProviderInterface $userProvider
     *
     * @throws AuthenticationException
     *
     * @return UserInterface|null
     */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        /**
         * @var GoogleUser $googleUser
         */
        $googleUser = $this->getGoogleClient()->fetchUserFromToken($credentials);

        $email = $googleUser->getEmail();

        // 1) have they logged in with Google before? Easy!
        $existingUser = $this->em->getRepository(User::class)->findOneBy(['googleId' => $googleUser->getId()]);
        if ($existingUser) {
            return $existingUser;
        }

        // 2) do we have a matching user by email?
        $user = $this->em->getRepository(User::class)->findOneBy(['email' => $email]);

        // 3) Maybe you just want to "register" them by creating
        // a User object
        if (!$user) {
            $user = new User();
            $user->setUsername($email);
            $user->setEmail($email);
            $user->setFirstName($googleUser->getFirstName());
            $user->setLastName($googleUser->getLastName());
            $user->setRoles(['ROLE_USER']);
            $user->setPassword('xxxxxxxxxx');
        }
        $user->setGoogleId($googleUser->getId());
        $this->em->persist($user);
        $this->em->flush();

        return $user;
    }

    /**
     * @return GoogleClient
     */
    private function getGoogleClient()
    {
        return $this->clientRegistry->getClient('google');
    }

    public function checkCredentials($credentials, UserInterface $user){}

    public function createAuthenticatedToken(UserInterface $user, $providerKey){}

    /**
     * @param Request                 $request
     * @param AuthenticationException $exception
     *
     * @return Response|null
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
  {
        throw $exception;
    }

    /**
    * @param Request        $request
    * @param TokenInterface $token
    * @param string         $providerKey The provider (i.e. firewall) key
    *
    * @return Response|null
    */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        return new RedirectResponse($this->router->generate('sonata_admin_redirect'));
    }

    public function supportsRememberMe(){}

    /**
     * @param Request                 $request       The request that resulted in an AuthenticationException
     * @param AuthenticationException $authException The exception that started the authentication process
     *
     * @return void
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        return new RedirectResponse($this->router->generate('security_login'));
    }
}

Error with guard

Hello,
I try this bundle with azure and it works like a charm whan I go directly t the route. But when I go on my root url, (localhost:8000) with guard definition and putting my azure service as entry_point I have an error :
ErrorHandler ->handleError ('4096', 'Argument 1 passed to Symfony\Component\HttpKernel\Event\GetResponseEvent::setResponse() must be an instance of Symfony\Component\HttpFoundation\Response, null given, called in \vendor\symfony\symfony\src\Symfony\Component\Security\Http\Firewall\ExceptionListener.php on line 125 and defined', '\app\cache\dev\classes.php', '2373', array())

If I put another service as entry_point (a login_form for example) all work fine.

Have you any Idea ?
My security.yml :

 firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        default:
            anonymous: ~
            logout: ~
            guard:
                authenticators:
                    - app.azure_authenticator
                    - app.form_login_authenticator
                    - prayno.cas_authenticator
                entry_point: app.azure_authenticator

my services.yml :

app.azure_authenticator:
        class: AppBundle\Security\AzureAuthenticator
        arguments:
            - '@oauth2.registry'
            - '@doctrine.orm.entity_manager'
            - '@router'
            - "@service_container"

    app.form_login_authenticator:
        class: AppBundle\Security\LoginFormAuthenticator
        arguments:
            - '@service_container'

My AzureController :

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
 * Class AzureController
 * @package AppBundle\Controller
 */
class AzureController extends Controller
{
    /**
     * @Route("/login/azure", name="connect_azure")
     */
    public function connectAction()
    {
        // will redirect to Azure!
        return $this->get('oauth2.registry')
            ->getClient('azure')
            ->redirect();
    }

    /**
     * @Route("/login/azure/check", name="connect_azure_check")
     */
    public function connectCheckAction()
    {
        // will never be executed
    }

    /**
     * @Route("/login/azure/error", name="connect_azure_failed")
     */
    public function connectFailedAction()
    {
        die;
    }
}

and filanlly my AzureAuthenticator :

namespace AppBundle\Security;

use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use KnpU\OAuth2ClientBundle\Client\Provider\AzureClient;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

class AzureAuthenticator extends SocialAuthenticator
{
    private $container;
    private $clientRegistry;
    private $em;
    private $router;

    public function __construct(ClientRegistry $clientRegistry, EntityManager $em, RouterInterface $router,
                                ContainerInterface $container)
    {
        $this->container = $container;
        $this->clientRegistry = $clientRegistry;
        $this->em = $em;
        $this->router = $router;
    }

    public function getCredentials(Request $request)
    {

        if ($request->getPathInfo() != '/login/azure/check') {
            // don't auth
            return;
        }

        return $this->fetchAccessToken($this->getAzureClient());
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        /** @var AzureUser $azureUser */
        $azureUser = $this->getAzureClient()
            ->fetchUserFromToken($credentials);

        $email = $azureUser->getUpn();

        $user = $this->em->getRepository('AppBundle:User')
            ->findOneBy(['email' => $email]);

        return $user;
    }

    /**
     * @return AzureClient
     */
    private function getAzureClient()
    {
        return $this->clientRegistry
            // "azure" is the key used in config.yml
            ->getClient('azure');
    }

    /**
     * @inheritDoc
     */
    public function start (Request $request, AuthenticationException $authException = null)
    {
        // TODO: Implement start() method.
    }

    /**
     * @inheritDoc
     */
    public function onAuthenticationFailure (Request $request, AuthenticationException $exception)
    {
        $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
        $url = $this->container->get('router')->generate('connect_azure_failed');
        $request->getSession()->clear();
        return new RedirectResponse($url);
    }

    /**
     * @inheritDoc
     */
    public function onAuthenticationSuccess (Request $request, TokenInterface $token, $providerKey)
    {
        if ($this->container->get('security.authorization_checker')->isGranted('ROLE_SUPERVISEUR')) {
            $url = $this->container->get('router')->generate('previsionnel_index');
            return new RedirectResponse($url);
        }
        $url = $this->container->get('router')->generate('invitesgroupe_new');
        return new RedirectResponse($url);
    }

    /**
     * {@inheritdoc}
     */
    public function supportsRememberMe ()
    {
        return true;
    }
}

Thank you

Introduce (optional) provider-specific client classes

Currently, all providers return an instance of OAuth2Client, which gives you some nice Symfony-specific sugar. But, if we allow providers to sub-class this (e.g. FacebookClient), there are a few advantages:

  1. Autowiring: if you use autowiring, then it'll continue to work out-of-the-box even if you have multiple client services
  2. Provider-specific users: FacebookClient could override fetchUser() and fetchUserFromToken() just to add PHPDoc that describes that you'll get back a specific FacebookUser. That's quite helpful for auto-complete
  3. (Possibly), if there is some crazy provider-specific functionality, that could be put into the client

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.