Skip to content

Commit

Permalink
Provide default login/logout actions and move logic into a service class
Browse files Browse the repository at this point in the history
  • Loading branch information
buchmarv committed Sep 2, 2024
1 parent 1f6f6f3 commit b24c5e2
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 56 deletions.
69 changes: 15 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ security:
# config/routes.yaml
logout:
path: /logout

login:
alias: t3g_keycloak_login
```
## Step 3: Enable the Bundle
Expand All @@ -48,60 +51,13 @@ in the `config/bundles.php` file of your project:
return [
// ...
Jose\Bundle\JoseFramework\JoseFrameworkBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
Http\HttplugBundle\HttplugBundle::class => ['all' => true],
T3G\Bundle\Keycloak\T3GKeycloakBundle::class => ['all' => true],
];
```

## Step 5: Create a login controller

In order to log in, a simple login controller will suffice:

```php
<?php
namespace App\Controller;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class HomeController extends AbstractController
{
/**
* Link to this controller to start the "connect" process.
*/
#[Route(path: '/login', name: 'login', methods: ['GET'])]
public function login(ClientRegistry $clientRegistry): RedirectResponse
{
if (null !== $this->getUser()) {
return $this->redirectToRoute('dashboard');
}
return $clientRegistry
->getClient('keycloak')
->redirect([
'openid', 'profile', 'roles', 'email', // the scopes you want to access
]);
}
/**
* This route must match the authentication route in your bundle configuration.
*/
#[IsGranted('ROLE_USER')]
#[Route(path: '/oauth/callback', name: 'oauth_callback', methods: ['GET'])]
public function checkLogin(): RedirectResponse
{
// fallback in case the authenticator does not redirect
return $this->redirectToRoute('dashboard');
}
}
```

# Configuration

```bash
Expand All @@ -118,17 +74,12 @@ php bin/console debug:config t3g_keycloak
# Default configuration for extension with alias: "t3g_keycloak"
t3g_keycloak:
keycloak:
jku_url: 'https://login.typo3.com/realms/TYPO3/protocol/openid-connect/certs'
user_provider_class: T3G\Bundle\Keycloak\Security\KeyCloakUserProvider
default_roles:
# Defaults:
- ROLE_USER
- ROLE_OAUTH_USER
routes:
# redirect_route passed to keycloak
authentication: oauth_callback
# route to redirect to after successful authentication
success: dashboard
clientId: '%env(KEYCLOAK_CLIENT_ID)%'
```

### Role Mapping
Expand All @@ -139,3 +90,13 @@ t3g_keycloak:
role_mapping:
my-role: ROLE_ADMIN
```

### Routes
```yaml
t3g_keycloak:
routes:
# route to redirect to after successful authentication
success: home
# redirect_route passed to keycloak
authentication: t3g_keycloak_oauthCallback
```
44 changes: 44 additions & 0 deletions src/Controller/LoginController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php declare(strict_types=1);

/*
* This file is part of the package t3g/symfony-keycloak-bundle.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/

namespace T3G\Bundle\Keycloak\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use T3G\Bundle\Keycloak\Service\RedirectService;

class LoginController extends AbstractController
{
private RedirectService $redirectService;

public function __construct(RedirectService $redirectService)
{
$this->redirectService = $redirectService;
}

public function login(): RedirectResponse
{
if (null !== $this->getUser()) {
return $this->redirectToRoute($this->getParameter('t3g_keycloak.routes.success'));
}

return $this->redirectService->generateLoginRedirectResponse(['openid', 'profile', 'roles', 'email']);
}

public function oauthCallback(): RedirectResponse
{
// fallback in case the authenticator does not redirect
return $this->redirectToRoute($this->getParameter('t3g_keycloak.routes.success'));
}

public function oauthLogout(): RedirectResponse
{
return $this->redirectService->generateLogoutRedirectResponse();
}
}
8 changes: 6 additions & 2 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,19 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultValue([])
->scalarPrototype()->end()
->end()
->scalarNode('clientId')
->defaultValue('%env(KEYCLOAK_CLIENT_ID)%')
->cannotBeEmpty()
->end()
->end()
->end()
->arrayNode('routes')->addDefaultsIfNotSet()
->children()
->scalarNode('authentication')
->defaultValue(null)
->defaultValue('t3g_keycloak_oauthCallback')
->end()
->scalarNode('success')
->defaultValue(null)
->defaultValue('home')
->end()
->end()
->end()
Expand Down
14 changes: 14 additions & 0 deletions src/DependencyInjection/T3GKeycloakExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,27 @@ public function prepend(ContainerBuilder $container): void
$container->setParameter('t3g_keycloak.keycloak.user_provider_class', $config['keycloak']['user_provider_class']);
$container->setParameter('t3g_keycloak.keycloak.default_roles', $config['keycloak']['default_roles']);
$container->setParameter('t3g_keycloak.keycloak.role_mapping', $config['keycloak']['role_mapping']);
$container->setParameter('t3g_keycloak.keycloak.clientId', $config['keycloak']['clientId']);
$container->setParameter('t3g_keycloak.routes.authentication', $config['routes']['authentication']);
$container->setParameter('t3g_keycloak.routes.success', $config['routes']['success']);

if ($container->hasExtension($this->getAlias())) {
$container->prependExtensionConfig($this->getAlias(), ['keycloak' => [], 'routes' => []]);
}

if ($container->hasExtension('knpu_oauth2_client')) {
$container->prependExtensionConfig(
'knpu_oauth2_client',
[
'clients' => [
'keycloak' => [
'redirect_route' => '%t3g_keycloak.routes.authentication%',
],
],
]
);
}

if ($container->hasExtension('httplug')) {
$container->prependExtensionConfig(
'httplug',
Expand Down
9 changes: 9 additions & 0 deletions src/Resources/config/routes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="t3g_keycloak_login" controller="keycloak.typo3.com.login_controller::login" path="/login"></route>
<route id="t3g_keycloak_oauthCallback" controller="keycloak.typo3.com.login_controller::oauthCallback" path="/oauth/callback"></route>
<route id="t3g_keycloak_logout" controller="keycloak.typo3.com.login_controller::oauthLogout" path="/oauth/logout"></route>
</routes>
9 changes: 9 additions & 0 deletions src/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ services:
$roleMapping: '%t3g_keycloak.keycloak.role_mapping%'
$defaultRoles: '%t3g_keycloak.keycloak.default_roles%'

keycloak.typo3.com.login_controller:
class: T3G\Bundle\Keycloak\Controller\LoginController

T3G\Bundle\Keycloak\Service\RedirectService:
class: T3G\Bundle\Keycloak\Service\RedirectService
public: true
arguments:
$clientId: '%t3g_keycloak.keycloak.clientId%'

T3G\Bundle\Keycloak\Security\KeyCloakAuthenticator:
class: T3G\Bundle\Keycloak\Security\KeyCloakAuthenticator
public: true
Expand Down
60 changes: 60 additions & 0 deletions src/Service/RedirectService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php declare(strict_types=1);

/*
* This file is part of the package t3g/symfony-keycloak-bundle.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/

namespace T3G\Bundle\Keycloak\Service;

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\OAuth2Client;
use Stevenmaguire\OAuth2\Client\Provider\Keycloak;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;

class RedirectService
{
private ClientRegistry $clientRegistry;
private RouterInterface $router;
private string $clientId;

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

/**
* @param string[] $scopes
*/
public function generateLoginRedirectResponse(array $scopes): RedirectResponse
{
/** @var OAuth2Client $client */
$client = $this->clientRegistry->getClient('keycloak');

return $client->redirect($scopes);
}

public function generateLogoutRedirectResponse(): RedirectResponse
{
$redirectAfterOAuthLogout = rtrim($this->router->generate('home', [], UrlGeneratorInterface::ABSOLUTE_URL), '/');
/** @var Keycloak $provider */
$provider = $this->clientRegistry->getClient('keycloak')->getOAuth2Provider();
$redirectTarget = sprintf(
'%s/realms/%s/protocol/openid-connect/logout?client_id=%s&post_logout_redirect_uri=%s',
$provider->authServerUrl,
$provider->realm,
$this->clientId,
urlencode($redirectAfterOAuthLogout)
);

return new RedirectResponse($redirectTarget, Response::HTTP_TEMPORARY_REDIRECT);
}
}

0 comments on commit b24c5e2

Please sign in to comment.