Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Oauth2 Pkce Client Laravel Package

beyondbluesky/oauth2-pkce-client

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps to Begin

  1. Installation

    composer require beyondbluesky/oauth2-pkce-client
    
  2. Configure the Bundle Create config/packages/oauth2_pkce_client.yaml with your OAuth2 provider details (e.g., auth/token URIs, client credentials, scopes).

  3. Set Up Database Run the provided migration to create the oauth2_pkce_state table:

    php bin/console doctrine:migrations:diff
    php bin/console doctrine:migrations:migrate
    
  4. Create a Controller Define a controller with a route matching your OAuth2 redirect URI:

    #[Route('/oauth2/callback', name: 'oauth2_callback')]
    public function callback(OAuth2CallbackHandler $handler): Response
    {
        return $handler->handle();
    }
    
  5. Implement a Custom Authenticator Extend AbstractOAuth2Authenticator and override getCredentials() to return user credentials (e.g., email from token response):

    use BeyondBluesky\OAuth2PkceClient\Auth\AbstractOAuth2Authenticator;
    
    class MyOAuth2Authenticator extends AbstractOAuth2Authenticator
    {
        public function getCredentials(): array
        {
            return ['email' => $this->getUser()->getEmail()];
        }
    }
    
  6. Configure Security Add the authenticator to your firewall in config/packages/security.yaml:

    firewalls:
        main:
            custom_authenticators:
                - App\Auth\MyOAuth2Authenticator
    
  7. Trigger Authentication Redirect users to the OAuth2 provider:

    return $this->redirect($this->generateUrl('oauth2_authenticate'));
    

First Use Case: Login with OAuth2

  • Use the OAuth2Authenticator to handle the OAuth2 flow.
  • The package automatically manages PKCE (Proof Key for Code Exchange) to secure authorization codes.
  • After a successful callback, the user is authenticated via Symfony’s security system.

Implementation Patterns

Workflows

  1. Initiate OAuth2 Flow

    • Redirect users to the provider’s auth endpoint with PKCE parameters (automatically handled by the bundle).
    • Example:
      #[Route('/login/oauth2', name: 'oauth2_authenticate')]
      public function initiateOAuth2(OAuth2Authenticator $authenticator): Response
      {
          return $authenticator->start();
      }
      
  2. Handle Callback

    • The OAuth2CallbackHandler processes the redirect from the provider, exchanges the code for a token, and validates the PKCE challenge.
    • Example:
      #[Route('/oauth2/callback', name: 'oauth2_callback')]
      public function callback(OAuth2CallbackHandler $handler): Response
      {
          return $handler->handle();
      }
      
  3. User Authentication

    • The custom authenticator (AbstractOAuth2Authenticator) maps the token response to a Symfony user (e.g., via UserProvider).
    • Override getUser() to load or create a user:
      public function getUser(): ?UserInterface
      {
          $email = $this->getCredentials()['email'];
          return $this->userProvider->loadUserByIdentifier($email);
      }
      
  4. Token Refresh

    • Use OAuth2TokenManager to refresh access tokens silently:
      $tokenManager = $this->container->get(OAuth2TokenManager::class);
      $refreshedToken = $tokenManager->refreshToken($storedState->getAccessToken());
      

Integration Tips

  1. Custom Token Handling Extend OAuth2TokenManager to add logic for token validation or custom claims:

    class CustomTokenManager extends OAuth2TokenManager
    {
        protected function validateTokenResponse(array $response): void
        {
            // Custom validation logic
        }
    }
    
  2. Multi-Provider Support Configure multiple OAuth2 clients in oauth2_pkce_client.yaml under clients and use dependency injection to switch contexts:

    oauth2_pkce_client:
        clients:
            google:
                id: '%env(GOOGLE_CLIENT_ID)%'
                secret: '%env(GOOGLE_CLIENT_SECRET)%'
                # ...
            github:
                id: '%env(GITHUB_CLIENT_ID)%'
                secret: '%env(GITHUB_CLIENT_SECRET)%'
                # ...
    
  3. State Management The bundle stores PKCE states in the database. For high-traffic apps, consider:

    • Using a faster cache (e.g., Redis) for state storage by implementing a custom StateStorageInterface.
    • Setting a shorter state_ttl in config to reduce database load.
  4. Error Handling Catch OAuth2Exception in controllers to provide user-friendly feedback:

    try {
        return $handler->handle();
    } catch (OAuth2Exception $e) {
        $this->addFlash('error', $e->getMessage());
        return $this->redirectToRoute('home');
    }
    
  5. Testing Use OAuth2MockClient for unit tests:

    $mockClient = new OAuth2MockClient();
    $mockClient->setAuthCodeResponse(['access_token' => 'mock_token']);
    $this->container->set(OAuth2Client::class, $mockClient);
    

Gotchas and Tips

Pitfalls

  1. PKCE State Mismatch

    • Issue: If the state parameter in the callback doesn’t match the stored state, the request is rejected.
    • Fix: Ensure the redirect_uri in your OAuth2 config matches exactly (including trailing slashes). Use urlencode() for dynamic URIs.
  2. CSRF Protection

    • Issue: The bundle uses PKCE for security, but ensure your Symfony csrf_token is not conflicting with OAuth2 state tokens.
    • Fix: Exclude the OAuth2 routes from CSRF protection in security.yaml:
      firewalls:
          main:
              csrf_protection: ~
              pattern: ^/oauth2
              csrf_protection: false
      
  3. Token Expiry

    • Issue: Access tokens expire, and silent refresh may fail if the refresh_token is invalid.
    • Fix: Implement a fallback to re-authenticate the user if refresh fails:
      try {
          $refreshedToken = $tokenManager->refreshToken($token);
      } catch (OAuth2Exception $e) {
          return $this->redirectToRoute('oauth2_authenticate');
      }
      
  4. Database Locking

    • Issue: Concurrent requests to the callback endpoint may cause race conditions when updating the PKCE state.
    • Fix: Use database transactions or optimistic locking in your StateStorage implementation.
  5. Provider-Specific Quirks

    • Issue: Some providers (e.g., Google) require additional scopes or parameters.
    • Fix: Extend OAuth2Client to add provider-specific logic:
      class GoogleOAuth2Client extends OAuth2Client
      {
          public function getAuthUri(array $options = []): string
          {
              $options['access_type'] = 'offline'; // Force refresh token
              return parent::getAuthUri($options);
          }
      }
      

Debugging Tips

  1. Enable Verbose Logging Add this to config/packages/monolog.yaml:

    handlers:
        oauth2:
            type: stream
            path: "%kernel.logs_dir%/oauth2.log"
            level: debug
            channels: ["oauth2"]
    

    Then enable the channel in OAuth2Client:

    $client->setLogger($this->container->get('logger')->channel('oauth2'));
    
  2. Inspect Token Responses Dump the raw token response in your authenticator:

    public function getUser(): ?UserInterface
    {
        $tokenResponse = $this->getTokenResponse();
        dump($tokenResponse); // Inspect claims/scopes
        return $this->userProvider->loadUserByIdentifier($tokenResponse['email']);
    }
    
  3. Validate PKCE Flow Use PKCE validator tools to test your implementation manually.


Extension Points

  1. Custom State Storage Implement BeyondBluesky\OAuth2PkceClient\Storage\StateStorageInterface for non-database storage (e.g., Redis):
    class RedisStateStorage implements StateStorageInterface
    {
        public function saveState(State $state): void
        {
            $this->redis->set($state->getId(), $state->serialize());
        }
    
        public function findState(string $id): ?State
        {
            $data = $this->redis->get($id);
            return $data ? State::unserialize($data) : null;
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge