M0: Skelett + Auth-Grundgerüst (Keycloak-OIDC + API-Token)

- Symfony 7.4 LTS, PHP 8.4, FrankenPHP, SQLite (3-Layer src/UI|Logic|Data)
- Auth: manueller OidcClient (Authorization-Code) im UI-Layer, kein
  Security-Bundle/league; Session-Gate via SessionAuthSubscriber
- API-Guard via statischem X-Api-Token (ApiTokenSubscriber); ExceptionSubscriber
- UserEntity (Auto-Provision) + Init-Migration
- Docker (php:8.4-frankenphp) + compose (essen.ljh31.de, traefik-net external)
- SmokeTests (7) grün: Redirect-Fälle, API-Token, Auth-Render, Logout

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 22:48:24 +02:00
co-authored by Claude Opus 4.8
parent 3c0bee5826
commit cb3cbfb394
56 changed files with 9596 additions and 19 deletions
+8
View File
@@ -0,0 +1,8 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
];
+19
View File
@@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null
+19
View File
@@ -0,0 +1,19 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
profiling_collect_backtrace: '%kernel.debug%'
orm:
auto_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore
mappings:
Data:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Data'
prefix: 'App\Data'
alias: Data
when@test:
doctrine:
dbal:
url: 'sqlite:///:memory:'
+6
View File
@@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false
+19
View File
@@ -0,0 +1,19 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
# Note that the session will be started ONLY if you read or write from it.
session: true
http_client:
default_options:
timeout: 15
#esi: true
#fragments: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file
+10
View File
@@ -0,0 +1,10 @@
framework:
router:
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
default_uri: '%env(DEFAULT_URI)%'
when@prod:
framework:
router:
strict_requirements: null
+6
View File
@@ -0,0 +1,6 @@
twig:
file_name_pattern: '*.twig'
when@test:
twig:
strict_variables: true
+5
View File
@@ -0,0 +1,5 @@
<?php
foreach (glob(dirname(__DIR__).'/var/cache/prod/*.preload.php') ?: [] as $file) {
require $file;
}
+1065
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
# yaml-language-server: $schema=https://symfony.com/schema/config/routing/routing.yaml
home:
path: /
controller: App\UI\Http\HomeController::index
methods: [GET]
login:
path: /login
controller: App\UI\Http\Auth\OidcAuthController::login
methods: [GET]
auth_callback:
path: /auth/callback
controller: App\UI\Http\Auth\OidcAuthController::callback
methods: [GET]
logout:
path: /logout
controller: App\UI\Http\Auth\OidcAuthController::logout
methods: [GET, POST]
+4
View File
@@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
prefix: /_error
+36
View File
@@ -0,0 +1,36 @@
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
parameters:
app.debug: '%kernel.debug%'
app.expected_api_token: '%env(API_TOKEN)%'
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/Kernel.php'
# Router-Controller automatisch als (getaggen) Service bekannt machen
App\UI\Http\:
resource: '../src/UI/Http/'
tags:
- { name: controller.service_arguments }
App\UI\Http\Auth\OidcClient:
arguments:
$issuer: '%env(string:KEYCLOAK_ISSUER)%'
$clientId: '%env(string:KEYCLOAK_CLIENT_ID)%'
$clientSecret: '%env(default::KEYCLOAK_CLIENT_SECRET)%'
$redirectUri: '%env(string:KEYCLOAK_REDIRECT_URI)%'
App\UI\Http\ExceptionSubscriber:
arguments:
$debug: '%app.debug%'
App\UI\Http\Api\ApiTokenSubscriber:
arguments:
$expectedToken: '%app.expected_api_token%'