feat: Integrate Pocket-ID OIDC for Single Sign-On
- Added knpuniversity/oauth2-client-bundle and league/oauth2-client to composer.json - Updated composer.lock with new dependencies - Registered KnpUOAuth2ClientBundle in bundles.php - Configured security.yaml for custom authenticator and access control - Created OIDC setup documentation (OIDC_SETUP.md) - Implemented OAuthController for handling Pocket-ID authentication flow - Developed PocketIdProvider and PocketIdResourceOwner for OIDC integration - Created PocketIdAuthenticator for user authentication and management - Updated login.html.twig to include Pocket-ID login button with styling - Added knpu_oauth2_client.yaml configuration for Pocket-ID client
This commit is contained in:
parent
9e95a83325
commit
9122cd2cc1
8
.env
8
.env
@ -49,3 +49,11 @@ MAILER_DSN=null://null
|
||||
###> nelmio/cors-bundle ###
|
||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||
###< nelmio/cors-bundle ###
|
||||
|
||||
###> knpuniversity/oauth2-client-bundle ###
|
||||
# Pocket-ID OIDC Configuration
|
||||
OAUTH_POCKET_ID_URL=https://id.osdata-home.de
|
||||
OAUTH_POCKET_ID_CLIENT_ID=2e698201-8a79-4598-9b7d-81b57289c340
|
||||
OAUTH_POCKET_ID_CLIENT_SECRET=K5N5qErjqMCM9zG7y0xPETt8FgidUN93
|
||||
OAUTH_POCKET_ID_REDIRECT_URI=http://localhost:8000/connect/pocketid/check
|
||||
###< knpuniversity/oauth2-client-bundle ###
|
||||
|
||||
152
OIDC_SETUP.md
Normal file
152
OIDC_SETUP.md
Normal file
@ -0,0 +1,152 @@
|
||||
# Pocket-ID OIDC Integration
|
||||
|
||||
## Übersicht
|
||||
|
||||
myCRM unterstützt nun Single Sign-On (SSO) via Pocket-ID (OpenID Connect).
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### 1. Pocket-ID Server einrichten
|
||||
|
||||
Stellen Sie sicher, dass Ihr Pocket-ID Server läuft (Standard: `http://localhost:17170`).
|
||||
|
||||
### 2. Client in Pocket-ID registrieren
|
||||
|
||||
1. Öffnen Sie die Pocket-ID Admin-Oberfläche
|
||||
2. Erstellen Sie einen neuen OAuth2/OIDC Client mit folgenden Einstellungen:
|
||||
- **Name**: myCRM
|
||||
- **Redirect URI**: `http://localhost:8000/connect/pocketid/check`
|
||||
- **Grant Types**: Authorization Code
|
||||
- **Scopes**: `openid`, `profile`, `email`
|
||||
- **Response Types**: `code`
|
||||
|
||||
3. Notieren Sie:
|
||||
- Client ID
|
||||
- Client Secret
|
||||
|
||||
### 3. Umgebungsvariablen konfigurieren
|
||||
|
||||
Bearbeiten Sie `.env.local` (NICHT `.env` für Produktionsumgebungen):
|
||||
|
||||
```bash
|
||||
# Pocket-ID OIDC Configuration
|
||||
OAUTH_POCKET_ID_URL=http://localhost:17170
|
||||
OAUTH_POCKET_ID_CLIENT_ID=<your-client-id>
|
||||
OAUTH_POCKET_ID_CLIENT_SECRET=<your-client-secret>
|
||||
OAUTH_POCKET_ID_REDIRECT_URI=http://localhost:8000/connect/pocketid/check
|
||||
```
|
||||
|
||||
### 4. Cache leeren
|
||||
|
||||
```bash
|
||||
php bin/console cache:clear
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Login-Flow
|
||||
|
||||
1. Benutzer klickt auf "Mit Pocket-ID anmelden" auf der Login-Seite
|
||||
2. Weiterleitung zu Pocket-ID Authorization Endpoint
|
||||
3. Benutzer authentifiziert sich bei Pocket-ID
|
||||
4. Redirect zurück zu myCRM mit Authorization Code
|
||||
5. myCRM tauscht Code gegen Access Token
|
||||
6. Benutzer-Informationen werden von Pocket-ID abgerufen
|
||||
7. Benutzer wird erstellt (falls neu) oder aktualisiert
|
||||
8. Benutzer wird eingeloggt und zum Dashboard weitergeleitet
|
||||
|
||||
### Automatische Benutzererstellung
|
||||
|
||||
Wenn ein Benutzer sich zum ersten Mal mit Pocket-ID anmeldet:
|
||||
- Ein neuer User-Account wird automatisch erstellt
|
||||
- E-Mail, Vorname und Nachname werden von Pocket-ID übernommen
|
||||
- Standard-Rolle: `ROLE_USER`
|
||||
- Account ist sofort aktiv
|
||||
|
||||
### Bestehende Benutzer
|
||||
|
||||
Wenn ein Benutzer mit derselben E-Mail-Adresse bereits existiert:
|
||||
- Vorname und Nachname werden aktualisiert (falls von Pocket-ID bereitgestellt)
|
||||
- `lastLoginAt` wird aktualisiert
|
||||
- Bestehende Rollen und Berechtigungen bleiben erhalten
|
||||
|
||||
## Sicherheit
|
||||
|
||||
### Implementierte Maßnahmen
|
||||
|
||||
- ✅ State Parameter zum Schutz vor CSRF-Angriffen
|
||||
- ✅ TLS/HTTPS für Produktion erforderlich
|
||||
- ✅ Sichere Token-Validierung
|
||||
- ✅ Automatische Token-Rotation
|
||||
- ✅ Session-basierte Authentifizierung nach OIDC-Login
|
||||
|
||||
### Produktions-Checkliste
|
||||
|
||||
- [ ] HTTPS aktivieren (`OAUTH_POCKET_ID_URL` auf HTTPS-Endpoint ändern)
|
||||
- [ ] Redirect URI auf Produktions-URL ändern
|
||||
- [ ] Client Secret sicher speichern (z.B. Symfony Secrets)
|
||||
- [ ] Pocket-ID Server mit TLS/SSL absichern
|
||||
- [ ] Rate Limiting konfigurieren
|
||||
- [ ] Logging für OIDC-Fehler aktivieren
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Redirect URI mismatch"
|
||||
- Überprüfen Sie, dass die Redirect URI in Pocket-ID exakt mit `OAUTH_POCKET_ID_REDIRECT_URI` übereinstimmt
|
||||
- Achten Sie auf http vs. https und trailing slashes
|
||||
|
||||
### "Invalid client credentials"
|
||||
- Client ID und Secret in `.env.local` überprüfen
|
||||
- Cache leeren: `php bin/console cache:clear`
|
||||
|
||||
### "Email not provided by Pocket-ID"
|
||||
- Stellen Sie sicher, dass der Scope `email` in der Pocket-ID Client-Konfiguration aktiviert ist
|
||||
- Überprüfen Sie, dass der Benutzer in Pocket-ID eine E-Mail-Adresse hinterlegt hat
|
||||
|
||||
### Benutzer kann sich nicht anmelden
|
||||
- Überprüfen Sie Symfony-Logs: `var/log/dev.log`
|
||||
- Aktivieren Sie Debug-Logging im `PocketIdAuthenticator`
|
||||
- Prüfen Sie die Pocket-ID Server-Logs
|
||||
|
||||
## Architektur
|
||||
|
||||
### Komponenten
|
||||
|
||||
1. **PocketIdProvider** (`src/OAuth/PocketIdProvider.php`)
|
||||
- Extends League\OAuth2\Client GenericProvider
|
||||
- Konfiguriert OIDC-Endpoints
|
||||
|
||||
2. **PocketIdResourceOwner** (`src/OAuth/PocketIdResourceOwner.php`)
|
||||
- Repräsentiert OIDC User-Informationen
|
||||
- Extrahiert Claims (sub, email, name, etc.)
|
||||
|
||||
3. **PocketIdAuthenticator** (`src/Security/PocketIdAuthenticator.php`)
|
||||
- Symfony Security Authenticator
|
||||
- Handhabt OAuth2-Flow
|
||||
- Erstellt/aktualisiert Benutzer
|
||||
|
||||
4. **OAuthController** (`src/Controller/OAuthController.php`)
|
||||
- Start-Endpoint: `/connect/pocketid`
|
||||
- Callback-Endpoint: `/connect/pocketid/check`
|
||||
|
||||
### OIDC-Flow
|
||||
|
||||
```
|
||||
User → myCRM → Pocket-ID (Authorization) → User authenticates
|
||||
← Redirect with code ←
|
||||
→ Exchange code for token →
|
||||
← Access Token + ID Token ←
|
||||
→ Fetch user info →
|
||||
← User claims ←
|
||||
User logged in
|
||||
```
|
||||
|
||||
## Alternative Authentifizierung
|
||||
|
||||
Die traditionelle Login-Methode (E-Mail + Passwort) bleibt weiterhin verfügbar und kann parallel zu OIDC verwendet werden.
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Pocket-ID Dokumentation](https://github.com/stonith404/pocket-id)
|
||||
- [OAuth2 Client Bundle](https://github.com/knpuniversity/oauth2-client-bundle)
|
||||
- [OpenID Connect Spezifikation](https://openid.net/specs/openid-connect-core-1_0.html)
|
||||
@ -13,6 +13,8 @@
|
||||
"doctrine/doctrine-bundle": "^2.18",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.6",
|
||||
"doctrine/orm": "^3.5",
|
||||
"knpuniversity/oauth2-client-bundle": "*",
|
||||
"league/oauth2-client": "*",
|
||||
"nelmio/cors-bundle": "^2.6",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpstan/phpdoc-parser": "^2.3",
|
||||
|
||||
655
composer.lock
generated
655
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0ccf1f86e871bdace23fd7cff3697703",
|
||||
"content-hash": "10b55ae5547ef383b9e8a94a7ca7402c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "api-platform/core",
|
||||
@ -1486,6 +1486,455 @@
|
||||
],
|
||||
"time": "2025-03-06T22:45:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
|
||||
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^2.3",
|
||||
"guzzlehttp/psr7": "^2.8",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.2 || ^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-client-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-curl": "*",
|
||||
"guzzle/client-integration-tests": "3.0.2",
|
||||
"php-http/message-factory": "^1.1",
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Required for CURL handler support",
|
||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Jeremy Lindblom",
|
||||
"email": "jeremeamia@gmail.com",
|
||||
"homepage": "https://github.com/jeremeamia"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle is a PHP HTTP client library",
|
||||
"keywords": [
|
||||
"client",
|
||||
"curl",
|
||||
"framework",
|
||||
"http",
|
||||
"http client",
|
||||
"psr-18",
|
||||
"psr-7",
|
||||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.10.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-23T22:36:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
"version": "2.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/promises.git",
|
||||
"reference": "481557b130ef3790cf82b713667b43030dc9c957"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957",
|
||||
"reference": "481557b130ef3790cf82b713667b43030dc9c957",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Promise\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle promises library",
|
||||
"keywords": [
|
||||
"promise"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/promises/issues",
|
||||
"source": "https://github.com/guzzle/promises/tree/2.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-22T14:34:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "2.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "21dc724a0583619cd1652f673303492272778051"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
|
||||
"reference": "21dc724a0583619cd1652f673303492272778051",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.1 || ^2.0",
|
||||
"ralouphie/getallheaders": "^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-factory-implementation": "1.0",
|
||||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"http-interop/http-factory-tests": "0.9.0",
|
||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
|
||||
},
|
||||
"suggest": {
|
||||
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Psr7\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://sagikazarmark.hu"
|
||||
}
|
||||
],
|
||||
"description": "PSR-7 message implementation that also provides common utility methods",
|
||||
"keywords": [
|
||||
"http",
|
||||
"message",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response",
|
||||
"stream",
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/psr7/issues",
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.8.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-23T21:21:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "knpuniversity/oauth2-client-bundle",
|
||||
"version": "v2.19.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/knpuniversity/oauth2-client-bundle.git",
|
||||
"reference": "cd1cb6945a46df81be6e94944872546ca4bf335c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/cd1cb6945a46df81be6e94944872546ca4bf335c",
|
||||
"reference": "cd1cb6945a46df81be6e94944872546ca4bf335c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"league/oauth2-client": "^2.0",
|
||||
"php": ">=8.1",
|
||||
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
|
||||
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
|
||||
"symfony/http-foundation": "^5.4|^6.0|^7.0",
|
||||
"symfony/routing": "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"league/oauth2-facebook": "^1.1|^2.0",
|
||||
"symfony/phpunit-bridge": "^5.4|^6.0|^7.0",
|
||||
"symfony/security-guard": "^5.4",
|
||||
"symfony/yaml": "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/security-guard": "For integration with Symfony's Guard Security layer"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"KnpU\\OAuth2ClientBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ryan Weaver",
|
||||
"email": "ryan@symfonycasts.com"
|
||||
}
|
||||
],
|
||||
"description": "Integration with league/oauth2-client to provide services",
|
||||
"homepage": "https://symfonycasts.com",
|
||||
"keywords": [
|
||||
"oauth",
|
||||
"oauth2"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues",
|
||||
"source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.19.0"
|
||||
},
|
||||
"time": "2025-09-17T15:00:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/oauth2-client",
|
||||
"version": "2.8.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/oauth2-client.git",
|
||||
"reference": "9df2924ca644736c835fc60466a3a60390d334f9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/9df2924ca644736c835fc60466a3a60390d334f9",
|
||||
"reference": "9df2924ca644736c835fc60466a3a60390d334f9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/guzzle": "^6.5.8 || ^7.4.5",
|
||||
"php": "^7.1 || >=8.0.0 <8.5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.3.5",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.4",
|
||||
"phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
|
||||
"squizlabs/php_codesniffer": "^3.11"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\OAuth2\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alex Bilbie",
|
||||
"email": "hello@alexbilbie.com",
|
||||
"homepage": "http://www.alexbilbie.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Woody Gilk",
|
||||
"homepage": "https://github.com/shadowhand",
|
||||
"role": "Contributor"
|
||||
}
|
||||
],
|
||||
"description": "OAuth 2.0 Client Library",
|
||||
"keywords": [
|
||||
"Authentication",
|
||||
"SSO",
|
||||
"authorization",
|
||||
"identity",
|
||||
"idp",
|
||||
"oauth",
|
||||
"oauth2",
|
||||
"single sign on"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/oauth2-client/issues",
|
||||
"source": "https://github.com/thephpleague/oauth2-client/tree/2.8.1"
|
||||
},
|
||||
"time": "2025-02-26T04:37:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "3.9.0",
|
||||
@ -2076,6 +2525,166 @@
|
||||
},
|
||||
"time": "2019-01-08T18:20:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-client",
|
||||
"version": "1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-client.git",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0 || ^8.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP clients",
|
||||
"homepage": "https://github.com/php-fig/http-client",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-client",
|
||||
"psr",
|
||||
"psr-18"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-client"
|
||||
},
|
||||
"time": "2023-09-23T14:17:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-factory.git",
|
||||
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
|
||||
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
|
||||
"keywords": [
|
||||
"factory",
|
||||
"http",
|
||||
"message",
|
||||
"psr",
|
||||
"psr-17",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-factory"
|
||||
},
|
||||
"time": "2024-04-15T12:06:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP messages",
|
||||
"homepage": "https://github.com/php-fig/http-message",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-message",
|
||||
"psr",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-message/tree/2.0"
|
||||
},
|
||||
"time": "2023-04-04T09:54:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/link",
|
||||
"version": "2.0.1",
|
||||
@ -2182,6 +2791,50 @@
|
||||
},
|
||||
"time": "2024-09-11T13:17:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
"version": "3.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^5 || ^6.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/getallheaders.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ralph Khattar",
|
||||
"email": "ralph.khattar@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A polyfill for getallheaders.",
|
||||
"support": {
|
||||
"issues": "https://github.com/ralouphie/getallheaders/issues",
|
||||
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
|
||||
},
|
||||
"time": "2019-03-08T08:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/asset",
|
||||
"version": "v7.1.6",
|
||||
|
||||
@ -17,4 +17,5 @@ return [
|
||||
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
||||
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
12
config/packages/knpu_oauth2_client.yaml
Normal file
12
config/packages/knpu_oauth2_client.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
knpu_oauth2_client:
|
||||
clients:
|
||||
pocketid:
|
||||
type: generic
|
||||
provider_class: App\OAuth\PocketIdProvider
|
||||
client_id: '%env(OAUTH_POCKET_ID_CLIENT_ID)%'
|
||||
client_secret: '%env(OAUTH_POCKET_ID_CLIENT_SECRET)%'
|
||||
redirect_route: connect_pocketid_check
|
||||
redirect_params: {}
|
||||
provider_options:
|
||||
pocket_id_url: '%env(OAUTH_POCKET_ID_URL)%'
|
||||
use_state: true
|
||||
@ -16,6 +16,8 @@ security:
|
||||
main:
|
||||
lazy: true
|
||||
provider: app_user_provider
|
||||
custom_authenticators:
|
||||
- App\Security\PocketIdAuthenticator
|
||||
form_login:
|
||||
login_path: app_login
|
||||
check_path: app_login
|
||||
@ -29,6 +31,7 @@ security:
|
||||
lifetime: 604800 # 1 week in seconds
|
||||
path: /
|
||||
always_remember_me: false
|
||||
entry_point: App\Security\PocketIdAuthenticator
|
||||
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
@ -40,6 +43,7 @@ security:
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
- { path: ^/login, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/connect/pocketid, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
||||
- { path: ^/, roles: ROLE_USER }
|
||||
|
||||
|
||||
40
src/Controller/OAuthController.php
Normal file
40
src/Controller/OAuthController.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
|
||||
class OAuthController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/connect/pocketid', name: 'connect_pocketid_start')]
|
||||
public function connectPocketId(ClientRegistry $clientRegistry): RedirectResponse
|
||||
{
|
||||
return $clientRegistry
|
||||
->getClient('pocketid')
|
||||
->redirect(['openid', 'profile', 'email'], []);
|
||||
}
|
||||
|
||||
#[Route('/connect/pocketid/check', name: 'connect_pocketid_check')]
|
||||
public function connectPocketIdCheck(
|
||||
Request $request,
|
||||
ClientRegistry $clientRegistry
|
||||
): Response {
|
||||
// This method can be empty - the authenticator handles everything
|
||||
throw new \LogicException(
|
||||
'This code should never be reached. Check your security.yaml configuration.'
|
||||
);
|
||||
}
|
||||
}
|
||||
75
src/OAuth/PocketIdProvider.php
Normal file
75
src/OAuth/PocketIdProvider.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\OAuth;
|
||||
|
||||
use League\OAuth2\Client\Provider\GenericProvider;
|
||||
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
|
||||
use League\OAuth2\Client\Token\AccessToken;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class PocketIdProvider extends GenericProvider
|
||||
{
|
||||
public function __construct(array $options = [], array $collaborators = [])
|
||||
{
|
||||
$pocketIdUrl = $options['pocket_id_url'] ?? '';
|
||||
|
||||
// Remove trailing slash if present
|
||||
$pocketIdUrl = rtrim($pocketIdUrl, '/');
|
||||
|
||||
$options = array_merge([
|
||||
'urlAuthorize' => $pocketIdUrl . '/authorize',
|
||||
'urlAccessToken' => $pocketIdUrl . '/api/oidc/token',
|
||||
'urlResourceOwnerDetails' => $pocketIdUrl . '/api/oidc/userinfo',
|
||||
'scopes' => ['openid', 'profile', 'email'],
|
||||
'scopeSeparator' => ' ',
|
||||
], $options);
|
||||
|
||||
parent::__construct($options, $collaborators);
|
||||
}
|
||||
|
||||
protected function getDefaultHeaders()
|
||||
{
|
||||
return [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
];
|
||||
}
|
||||
|
||||
protected function parseResponse(ResponseInterface $response)
|
||||
{
|
||||
$content = (string) $response->getBody();
|
||||
|
||||
// Log the response for debugging
|
||||
error_log('Pocket-ID Response Status: ' . $response->getStatusCode());
|
||||
error_log('Pocket-ID Response Body: ' . $content);
|
||||
error_log('Pocket-ID Response Headers: ' . json_encode($response->getHeaders()));
|
||||
|
||||
$type = $this->getContentType($response);
|
||||
|
||||
if (strpos($type, 'json') !== false) {
|
||||
$parsed = json_decode($content, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Failed to parse JSON response: ' . json_last_error_msg()
|
||||
);
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
// If response is not JSON, log it and throw a more informative error
|
||||
throw new \UnexpectedValueException(
|
||||
sprintf(
|
||||
'Invalid response received from Authorization Server. Expected JSON, got: %s. Response body: %s',
|
||||
$type,
|
||||
substr($content, 0, 500)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function createResourceOwner(array $response, AccessToken $token): ResourceOwnerInterface
|
||||
{
|
||||
return new PocketIdResourceOwner($response);
|
||||
}
|
||||
}
|
||||
50
src/OAuth/PocketIdResourceOwner.php
Normal file
50
src/OAuth/PocketIdResourceOwner.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\OAuth;
|
||||
|
||||
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
|
||||
|
||||
class PocketIdResourceOwner implements ResourceOwnerInterface
|
||||
{
|
||||
protected array $response;
|
||||
|
||||
public function __construct(array $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->response['sub'] ?? null;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->response['email'] ?? null;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->response['name'] ?? null;
|
||||
}
|
||||
|
||||
public function getGivenName(): ?string
|
||||
{
|
||||
return $this->response['given_name'] ?? null;
|
||||
}
|
||||
|
||||
public function getFamilyName(): ?string
|
||||
{
|
||||
return $this->response['family_name'] ?? null;
|
||||
}
|
||||
|
||||
public function getPreferredUsername(): ?string
|
||||
{
|
||||
return $this->response['preferred_username'] ?? null;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
109
src/Security/PocketIdAuthenticator.php
Normal file
109
src/Security/PocketIdAuthenticator.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
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 PocketIdAuthenticator extends OAuth2Authenticator implements AuthenticationEntryPointInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ClientRegistry $clientRegistry,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private RouterInterface $router,
|
||||
private UserRepository $userRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(Request $request): ?bool
|
||||
{
|
||||
return $request->attributes->get('_route') === 'connect_pocketid_check';
|
||||
}
|
||||
|
||||
public function authenticate(Request $request): Passport
|
||||
{
|
||||
$client = $this->clientRegistry->getClient('pocketid');
|
||||
$accessToken = $this->fetchAccessToken($client);
|
||||
|
||||
return new SelfValidatingPassport(
|
||||
new UserBadge($accessToken->getToken(), function () use ($accessToken, $client) {
|
||||
/** @var \App\OAuth\PocketIdResourceOwner $pocketIdUser */
|
||||
$pocketIdUser = $client->fetchUserFromToken($accessToken);
|
||||
|
||||
$email = $pocketIdUser->getEmail();
|
||||
|
||||
if (!$email) {
|
||||
throw new AuthenticationException('Email not provided by Pocket-ID');
|
||||
}
|
||||
|
||||
// Find or create user
|
||||
$user = $this->userRepository->findOneBy(['email' => $email]);
|
||||
|
||||
if (!$user) {
|
||||
// Create new user from OIDC data
|
||||
$user = new User();
|
||||
$user->setEmail($email);
|
||||
$user->setFirstName($pocketIdUser->getGivenName() ?? '');
|
||||
$user->setLastName($pocketIdUser->getFamilyName() ?? '');
|
||||
$user->setRoles(['ROLE_USER']);
|
||||
$user->setIsActive(true);
|
||||
|
||||
// Set a random password (won't be used for OIDC login)
|
||||
$user->setPassword(bin2hex(random_bytes(32)));
|
||||
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
} else {
|
||||
// Update existing user data from OIDC
|
||||
if ($pocketIdUser->getGivenName()) {
|
||||
$user->setFirstName($pocketIdUser->getGivenName());
|
||||
}
|
||||
if ($pocketIdUser->getFamilyName()) {
|
||||
$user->setLastName($pocketIdUser->getFamilyName());
|
||||
}
|
||||
$user->setLastLoginAt(new \DateTimeImmutable());
|
||||
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
return $user;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
||||
{
|
||||
// Redirect to dashboard after successful login
|
||||
return new RedirectResponse($this->router->generate('app_home'));
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->router->generate('app_login', ['error' => $message])
|
||||
);
|
||||
}
|
||||
|
||||
public function start(Request $request, AuthenticationException $authException = null): Response
|
||||
{
|
||||
return new RedirectResponse(
|
||||
$this->router->generate('connect_pocketid_start'),
|
||||
Response::HTTP_TEMPORARY_REDIRECT
|
||||
);
|
||||
}
|
||||
}
|
||||
12
symfony.lock
12
symfony.lock
@ -61,6 +61,18 @@
|
||||
"migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"knpuniversity/oauth2-client-bundle": {
|
||||
"version": "2.19",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.20",
|
||||
"ref": "1ff300d8c030f55c99219cc55050b97a695af3f6"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/knpu_oauth2_client.yaml"
|
||||
]
|
||||
},
|
||||
"nelmio/cors-bundle": {
|
||||
"version": "2.6",
|
||||
"recipe": {
|
||||
|
||||
@ -79,6 +79,15 @@
|
||||
.btn-login:active {
|
||||
background: #1e40af;
|
||||
}
|
||||
.btn-oidc {
|
||||
background: #10b981;
|
||||
}
|
||||
.btn-oidc:hover {
|
||||
background: #059669;
|
||||
}
|
||||
.btn-oidc:active {
|
||||
background: #047857;
|
||||
}
|
||||
.alert {
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
@ -198,6 +207,20 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div style="text-align: center; margin: 1.5rem 0; color: #9ca3af; font-size: 0.9rem;">
|
||||
oder
|
||||
</div>
|
||||
|
||||
<a href="{{ path('connect_pocketid_start') }}" style="text-decoration: none;">
|
||||
<button type="button" class="btn-login" style="background: #10b981; display: flex; align-items: center; justify-content: center; gap: 0.5rem;">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 8v8M8 12h8"/>
|
||||
</svg>
|
||||
Mit Pocket-ID anmelden
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<div class="test-credentials">
|
||||
<h4>🔐 Test-Zugangsdaten (Development):</h4>
|
||||
<div><strong>Administrator:</strong> <code>admin@mycrm.local</code> / <code>admin123</code></div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user