Skip to content

Commit

Permalink
Authentik SSO (#806)
Browse files Browse the repository at this point in the history
  • Loading branch information
CocoPoops authored Jun 4, 2024
1 parent 7397c29 commit d38d2da
Show file tree
Hide file tree
Showing 14 changed files with 422 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ OAUTH_SIMPLELOGIN_SECRET=
OAUTH_ZITADEL_ID=
OAUTH_ZITADEL_SECRET=
OAUTH_ZITADEL_BASE_URL=
OAUTH_AUTHENTIK_ID=
OAUTH_AUTHENTIK_SECRET=
OAUTH_AUTHENTIK_BASE_URL=
# If true, sign ins and sign ups will only be possible through the OAuth providers configured above
SSO_ONLY_MODE=

Expand Down
3 changes: 3 additions & 0 deletions .env.example_docker
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ OAUTH_SIMPLELOGIN_SECRET=
OAUTH_ZITADEL_ID=
OAUTH_ZITADEL_SECRET=
OAUTH_ZITADEL_BASE_URL=
OAUTH_AUTHENTIK_ID=
OAUTH_AUTHENTIK_SECRET=
OAUTH_AUTHENTIK_BASE_URL=

# If true, sign ins and sign ups will only be possible through the OAuth providers configured above
SSO_ONLY_MODE=
Expand Down
10 changes: 10 additions & 0 deletions config/kbin_routes/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ oauth_zitadel_verify:
path: /oauth/zitadel/verify
methods: [ GET ]

oauth_authentik_connect:
controller: App\Controller\Security\AuthentikController::connect
path: /oauth/authentik/connect
methods: [ GET ]

oauth_authentik_verify:
controller: App\Controller\Security\AuthentikController::verify
path: /oauth/authentik/verify
methods: [ GET ]

oauth_create_client:
controller: App\Controller\Api\OAuth2\CreateClientApi
path: /api/client
Expand Down
9 changes: 9 additions & 0 deletions config/packages/knpu_oauth2_client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,12 @@ knpu_oauth2_client:
redirect_route: oauth_zitadel_verify
redirect_params: { }
provider_class: 'App\Provider\Zitadel'
authentik:
type: generic
client_id: '%oauth_authentik_id%'
client_secret: '%oauth_authentik_secret%'
provider_options:
base_url: '%oauth_authentik_base_url%'
redirect_route: oauth_authentik_verify
redirect_params: { }
provider_class: 'App\Provider\Authentik'
1 change: 1 addition & 0 deletions config/packages/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ security:
- App\Security\KeycloakAuthenticator
- App\Security\SimpleLoginAuthenticator
- App\Security\ZitadelAuthenticator
- App\Security\AuthentikAuthenticator
logout:
enable_csrf: true
path: app_logout
Expand Down
4 changes: 4 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ parameters:
oauth_zitadel_secret: "%env(OAUTH_ZITADEL_SECRET)%"
oauth_zitadel_base_url: "%env(OAUTH_ZITADEL_BASE_URL)%"

oauth_authentik_id: "%env(default::OAUTH_AUTHENTIK_ID)%"
oauth_authentik_secret: "%env(OAUTH_AUTHENTIK_SECRET)%"
oauth_authentik_base_url: "%env(OAUTH_AUTHENTIK_BASE_URL)%"


router.request_context.host: "%env(KBIN_DOMAIN)%"
router.request_context.scheme: https
Expand Down
29 changes: 29 additions & 0 deletions migrations/Version20240603230734.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240603230734 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add Authentik SSO';
}

public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE "user" ADD oauth_authentik_id VARCHAR(255) DEFAULT NULL');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE "user" DROP oauth_authentik_id');
}
}
28 changes: 28 additions & 0 deletions src/Controller/Security/AuthentikController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace App\Controller\Security;

use App\Controller\AbstractController;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class AuthentikController extends AbstractController
{
public function connect(ClientRegistry $clientRegistry): Response
{
return $clientRegistry
->getClient('authentik')
->redirect([
'openid',
'email',
'profile',
]);
}

public function verify(Request $request, ClientRegistry $client)
{
}
}
4 changes: 3 additions & 1 deletion src/Entity/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Visibil
public ?string $oauthSimpleLoginId = null;
#[Column(type: 'string', nullable: true)]
public ?string $oauthZitadelId = null;
#[Column(type: 'string', nullable: true)]
public ?string $oauthAuthentikId = null;
#[Column(type: 'boolean', nullable: false, options: ['default' => true])]
public bool $hideAdult = true;
#[Column(type: 'json', nullable: false, options: ['jsonb' => true, 'default' => '[]'])]
Expand Down Expand Up @@ -757,7 +759,7 @@ public function removeOAuth2UserConsent(OAuth2UserConsent $oAuth2UserConsent): s

public function isSsoControlled(): bool
{
return $this->oauthAzureId || $this->oauthGithubId || $this->oauthGoogleId || $this->oauthFacebookId || $this->oauthKeycloakId || $this->oauthSimpleLoginId || $this->oauthZitadelId;
return $this->oauthAzureId || $this->oauthGithubId || $this->oauthGoogleId || $this->oauthFacebookId || $this->oauthKeycloakId || $this->oauthSimpleLoginId || $this->oauthZitadelId || $this->oauthAuthentikId;
}

public function getCustomCss(): ?string
Expand Down
74 changes: 74 additions & 0 deletions src/Provider/Authentik.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace App\Provider;

use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;

class Authentik extends AbstractProvider
{
use BearerAuthorizationTrait;

protected $baseUrl;

public function __construct(array $options = [], array $collaborators = [])
{
$this->baseUrl = $options['base_url'] ?? '';

parent::__construct($options, $collaborators);
}

protected function getBaseUrl()
{
return rtrim($this->baseUrl, '/').'/';
}

protected function getAuthorizationHeaders($token = null)
{
return ['Authorization' => 'Bearer '.$token];
}

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

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

public function getResourceOwnerDetailsUrl(AccessToken $token)
{
return $this->getBaseUrl().'application/o/userinfo/';
}

protected function getDefaultScopes()
{
return ['openid', 'profile', 'email'];
}

protected function checkResponse(ResponseInterface $response, $data)
{
if (!empty($data['error'])) {
$error = htmlentities($data['error'], ENT_QUOTES, 'UTF-8');
$message = htmlentities($data['error_description'], ENT_QUOTES, 'UTF-8');
throw new IdentityProviderException($message, $response->getStatusCode(), $response);
}
}

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

protected function getScopeSeparator()
{
return ' ';
}
}
68 changes: 68 additions & 0 deletions src/Provider/AuthentikResourceOwner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace App\Provider;

use League\OAuth2\Client\Provider\ResourceOwnerInterface;

class AuthentikResourceOwner implements ResourceOwnerInterface
{
protected $response;

public function __construct(array $response)
{
$this->response = $response;
}

public function getId()
{
return $this->getResponseValue('sub');
}

public function getEmail()
{
return $this->getResponseValue('email');
}

public function getFamilyName()
{
return $this->getResponseValue('family_name');
}

public function getGivenName()
{
return $this->getResponseValue('given_name');
}

public function getPreferredUsername()
{
return $this->getResponseValue('preferred_username');
}

public function getPictureUrl()
{
return $this->getResponseValue('picture');
}

public function toArray()
{
return $this->response;
}

protected function getResponseValue($key)
{
$keys = explode('.', $key);
$value = $this->response;

foreach ($keys as $k) {
if (isset($value[$k])) {
$value = $value[$k];
} else {
return null;
}
}

return $value;
}
}
Loading

0 comments on commit d38d2da

Please sign in to comment.