habityzer/kinde-bundle

Symfony bundle for Kinde authentication integration with JWT validation, webhooks, and user sync

Installs: 11

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

Type:symfony-bundle

pkg:composer/habityzer/kinde-bundle

v2.0.0 2025-12-03 17:45 UTC

This package is auto-updated.

Last update: 2025-12-03 17:47:26 UTC


README

Symfony bundle for Kinde authentication integration with JWT validation, webhooks, and user synchronization.

Features

  • JWT Token Validation - Validates Kinde tokens using JWKS with automatic caching
  • Symfony Security Integration - Custom authenticator for seamless integration
  • User Synchronization - Sync users from Kinde to your database
  • Webhook Support - Handle Kinde webhook events (user updates, subscriptions)
  • Event-Driven - Dispatch Symfony events for business logic
  • Fully Decoupled - Uses interfaces for app-specific logic
  • Debug Command - CLI tool to inspect and debug JWT tokens

Requirements

  • PHP 8.2 or higher
  • Symfony 6.4 or 7.x
  • Kinde account with configured application

Installation

composer require habityzer/kinde-bundle

The bundle installs successfully without configuration, but you must configure it before using:

1. Set Environment Variables

Add to your .env file:

KINDE_DOMAIN=https://your-business.kinde.com
KINDE_CLIENT_ID=your-client-id-from-kinde
KINDE_CLIENT_SECRET=your-client-secret
KINDE_WEBHOOK_SECRET=your-webhook-secret

Get these values from your Kinde Dashboard. See Kinde Setup Guide for detailed instructions.

2. Create Configuration File

Create config/packages/habityzer_kinde.yaml:

habityzer_kinde:
    domain: '%env(KINDE_DOMAIN)%'
    client_id: '%env(KINDE_CLIENT_ID)%'
    client_secret: '%env(KINDE_CLIENT_SECRET)%'
    webhook_secret: '%env(KINDE_WEBHOOK_SECRET)%'

3. Clear Cache

php bin/console cache:clear

Note: The bundle will throw helpful runtime errors if you try to use authentication without proper configuration.

Configuration Reference

# config/packages/habityzer_kinde.yaml
habityzer_kinde:
    # Required: Your Kinde domain (e.g., https://your-business.kinde.com)
    domain: '%env(KINDE_DOMAIN)%'
    
    # Required: Kinde application client ID
    client_id: '%env(KINDE_CLIENT_ID)%'
    
    # Optional: Kinde application client secret (for server-side flows)
    client_secret: '%env(KINDE_CLIENT_SECRET)%'
    
    # Required for webhooks: Secret for webhook signature verification
    webhook_secret: '%env(KINDE_WEBHOOK_SECRET)%'
    
    # Optional: JWKS cache duration in seconds (default: 3600 = 1 hour)
    jwks_cache_ttl: 3600
    
    # Optional: Auto-register webhook route at /api/webhooks/kinde (default: true)
    enable_webhook_route: true

Quick Start

1. Implement the User Provider Interface

Create a class that implements KindeUserProviderInterface to handle user management:

namespace App\Kinde;

use Habityzer\KindeBundle\Contract\KindeUserProviderInterface;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;

class UserProvider implements KindeUserProviderInterface
{
    public function __construct(
        private readonly UserRepository $userRepository,
        private readonly EntityManagerInterface $em
    ) {}

    public function findByKindeId(string $kindeId): ?object
    {
        return $this->userRepository->findOneBy(['kindeId' => $kindeId]);
    }
    
    public function syncUser(array $kindeUserData): object
    {
        $user = new User();
        $user->setKindeId($kindeUserData['kinde_id']);
        $user->setEmail($kindeUserData['email']);
        $user->setName($kindeUserData['name'] ?? '');
        
        $this->em->persist($user);
        $this->em->flush();
        
        return $user;
    }
    
    public function updateUser(object $user, array $kindeUserData): void
    {
        $user->setEmail($kindeUserData['email']);
        $user->setName($kindeUserData['name'] ?? '');
        $this->em->flush();
    }
    
    public function handleUserDeletion(object $user): void
    {
        $user->setKindeId(null); // Soft delete approach
        $this->em->flush();
    }
}

Register it as a service:

# config/services.yaml
services:
    App\Kinde\UserProvider:
        tags:
            - { name: 'habityzer_kinde.user_provider' }

2. Configure Security

# config/packages/security.yaml
security:
    firewalls:
        # Allow public access to Kinde webhook
        kinde_webhook:
            pattern: ^/api/webhooks/kinde$
            stateless: true
            security: false
        
        # API firewall with Kinde authentication
        api:
            pattern: ^/api/
            stateless: true
            custom_authenticators:
                - Habityzer\KindeBundle\Security\KindeTokenAuthenticator

3. Token Format

When making API requests, prefix your Kinde JWT tokens with kinde_:

Authorization: Bearer kinde_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZF8xMjM0In0...

This prefix allows the authenticator to identify Kinde tokens and coexist with other authentication methods. The authenticator automatically removes the kinde_ prefix before validating the JWT.

Client-side example (JavaScript):

// Prepend kinde_ to your JWT token
const kindeToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZF8xMjM0In0...';
const authHeader = `Bearer kinde_${kindeToken}`;

fetch('/api/protected-endpoint', {
    headers: {
        'Authorization': authHeader
    }
});

4. Subscribe to Webhook Events

namespace App\EventSubscriber;

use Habityzer\KindeBundle\Event\KindeEvents;
use Habityzer\KindeBundle\Event\KindeSubscriptionUpdatedEvent;
use Habityzer\KindeBundle\Event\KindeUserDeletedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class KindeWebhookSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            KindeEvents::SUBSCRIPTION_UPDATED => 'onSubscriptionUpdated',
            KindeEvents::USER_DELETED => 'onUserDeleted',
        ];
    }
    
    public function onSubscriptionUpdated(KindeSubscriptionUpdatedEvent $event): void
    {
        $userId = $event->getUserId();
        $planName = $event->getPlanName();
        // Your business logic here
    }
    
    public function onUserDeleted(KindeUserDeletedEvent $event): void
    {
        $kindeId = $event->getKindeId();
        // Your cleanup logic here
    }
}

Events

The bundle dispatches the following Symfony events:

Event Constant Event Name Description
KindeEvents::USER_UPDATED kinde.user.updated User information updated in Kinde
KindeEvents::USER_DELETED kinde.user.deleted User deleted from Kinde
KindeEvents::USER_AUTHENTICATED kinde.user.authenticated User authenticated via webhook
KindeEvents::SUBSCRIPTION_CREATED kinde.subscription.created New subscription created
KindeEvents::SUBSCRIPTION_UPDATED kinde.subscription.updated Subscription plan changed
KindeEvents::SUBSCRIPTION_CANCELLED kinde.subscription.cancelled Subscription cancelled
KindeEvents::SUBSCRIPTION_REACTIVATED kinde.subscription.reactivated Subscription reactivated

📖 See Events Reference for complete event documentation with all available methods.

Debug Command

Debug JWT tokens to inspect claims and troubleshoot issues:

# Accepts tokens with or without kinde_ prefix
php bin/console kinde:debug-token YOUR_JWT_TOKEN
php bin/console kinde:debug-token kinde_YOUR_JWT_TOKEN
php bin/console kinde:debug-token "Bearer kinde_YOUR_JWT_TOKEN"

The command automatically strips Bearer and kinde_ prefixes if present.

Output includes:

  • Token header (algorithm, type)
  • All payload claims
  • Email presence check with fix suggestions
  • Token expiration status

Documentation

Document Description
Installation Guide Detailed step-by-step installation
Events Reference Complete event classes documentation
Services API Services and their public methods
Kinde Setup Configure Kinde dashboard for this bundle
Advanced Usage Advanced scenarios and customization

Architecture

┌─────────────────┐     ┌──────────────────────┐     ┌─────────────────┐
│  HTTP Request   │────▶│ KindeTokenAuthenticator │────▶│  Your User     │
│  (Bearer Token) │     └──────────────────────┘     │   Entity        │
└─────────────────┘              │                   └─────────────────┘
                                 │
                    ┌────────────┴────────────┐
                    ▼                         ▼
          ┌─────────────────┐      ┌─────────────────┐
          │ KindeTokenValidator │      │  KindeUserSync   │
          │  (JWKS validation)  │      │ (User provider)  │
          └─────────────────┘      └─────────────────┘
                    │                         │
                    ▼                         ▼
          ┌─────────────────┐      ┌─────────────────────┐
          │ KindeUserInfoService │      │ KindeUserProviderInterface │
          │ (Fallback for email) │      │   (Your implementation)    │
          └─────────────────┘      └─────────────────────┘

License

MIT

Support

For issues and questions: https://github.com/habityzer/kinde-bundle/issues