diff --git a/web/callback.php b/web/callback.php new file mode 100644 index 0000000..b6b1534 --- /dev/null +++ b/web/callback.php @@ -0,0 +1,59 @@ +exchangeCode($code); + + // Validate ID token (minimal). + $idInfo = $oidc->validateIDToken($tokens['id_token'], $nonce); + + // Get userinfo (includes groups because of scope). + $userInfo = $oidc->getUserInfo($tokens['access_token']); + + // Store everything we need in the session. + $_SESSION['user'] = [ + 'id_token' => $tokens['id_token'], + 'access_token'=> $tokens['access_token'], + 'refresh_token'=> $tokens['refresh_token'] ?? null, + 'expires_in' => $tokens['expires_in'] ?? null, + 'id_info' => $idInfo, + 'user_info' => $userInfo, + ]; +} catch (\Throwable $e) { + http_response_code(500); + exit('Error during OIDC exchange: ' . $e->getMessage()); +} + +// Clean up the query string (code, state) by redirecting to the home page. +header('Location: /'); +exit; \ No newline at end of file diff --git a/web/composer.json b/web/composer.json new file mode 100644 index 0000000..365b572 --- /dev/null +++ b/web/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "guzzlehttp/guzzle": "^7.0", + "firebase/php-jwt": "^6.0" + } +} \ No newline at end of file diff --git a/web/config.php b/web/config.php new file mode 100644 index 0000000..7495633 --- /dev/null +++ b/web/config.php @@ -0,0 +1,23 @@ +load(); + +/** + * Basic OIDC configuration – keep all secrets out of source‑control. + */ +define('OIDC_CLIENT_ID', getenv('OIDC_CLIENT_ID')); +define('OIDC_CLIENT_SECRET', getenv('OIDC_CLIENT_SECRET')); +define('OIDC_REDIRECT_URI', getenv('OIDC_REDIRECT_URI')); +define('OIDC_AUTH_ENDPOINT', getenv('OIDC_AUTH_ENDPOINT')); +define('OIDC_TOKEN_ENDPOINT', getenv('OIDC_TOKEN_ENDPOINT')); +define('OIDC_USERINFO_ENDPOINT', getenv('OIDC_USERINFO_ENDPOINT')); +define('OIDC_LOGOUT_ENDPOINT', getenv('OIDC_LOGOUT_ENDPOINT')); +define('OIDC_POST_LOGOUT_REDIRECT_URI', getenv('OIDC_POST_LOGOUT_REDIRECT_URI')); \ No newline at end of file diff --git a/web/index.php b/web/index.php index e69de29..1e1c6eb 100644 --- a/web/index.php +++ b/web/index.php @@ -0,0 +1,38 @@ + + + + + + Fitness Site – OIDC Demo + + + +

Fitness Site – OIDC Demo

+ + +

You are not logged in.

+

+ +

Logged in as:

+ +
+

User Information

+
+
+ +
+

ID Token Payload (claims)

+
+
+ +

+ + + \ No newline at end of file diff --git a/web/login.php b/web/login.php new file mode 100644 index 0000000..0f22b2b --- /dev/null +++ b/web/login.php @@ -0,0 +1,18 @@ +buildAuthUrl($state, $nonce)); +exit; \ No newline at end of file diff --git a/web/logout.php b/web/logout.php new file mode 100644 index 0000000..b134ec8 --- /dev/null +++ b/web/logout.php @@ -0,0 +1,15 @@ + + +
+ Drag photo here or click to choose +
+ + \ No newline at end of file diff --git a/web/photos/photos.php b/web/photos/photos.php new file mode 100644 index 0000000..9829887 --- /dev/null +++ b/web/photos/photos.php @@ -0,0 +1,314 @@ +getMessage(), E_USER_WARNING); +} + +// Check upload permissions +$has_upload_perm = true; // Add authorization logic here + +// Generate unique token for upload (security) +$upload_token = bin2hex(random_bytes(16)); +?> + + + + + + + Photos for Service Provider #<?php echo htmlspecialchars($provider_id); ?> + + + +

Photos for Service Provider #

+ + +
+ +
+ + + +
+ + +
+
+ +

Upload permissions disabled. Contact administrator.

+ + +
+ Loading photos... +
+ + + + + + \ No newline at end of file diff --git a/web/src/OIDC.php b/web/src/OIDC.php new file mode 100644 index 0000000..c1e47b2 --- /dev/null +++ b/web/src/OIDC.php @@ -0,0 +1,91 @@ +http = new Client(['http_errors' => false]); + } + + /** + * Build the Auth URL that redirects the user to Authelia. + */ + public function buildAuthUrl(string $state, string $nonce): string + { + $params = [ + 'response_type' => 'code', + 'client_id' => OIDC_CLIENT_ID, + 'redirect_uri' => OIDC_REDIRECT_URI, + 'scope' => 'openid profile email groups', + 'state' => $state, + 'nonce' => $nonce, + ]; + + return OIDC_AUTH_ENDPOINT . '?' . http_build_query($params); + } + + /** + * Exchange the Authorization Code for tokens. + */ + public function exchangeCode(string $code): array + { + $response = $this->http->post(OIDC_TOKEN_ENDPOINT, [ + 'auth' => [OIDC_CLIENT_ID, OIDC_CLIENT_SECRET], + 'form_params' => [ + 'grant_type' => 'authorization_code', + 'code' => $code, + 'redirect_uri' => OIDC_REDIRECT_URI, + ], + ]); + + if ($response->getStatusCode() !== 200) { + throw new \RuntimeException('Token endpoint returned ' . $response->getStatusCode()); + } + + return json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); + } + + /** + * Call /userinfo with the access token. + */ + public function getUserInfo(string $accessToken): array + { + $response = $this->http->get(OIDC_USERINFO_ENDPOINT, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $accessToken, + ], + ]); + + if ($response->getStatusCode() !== 200) { + throw new \RuntimeException('Userinfo endpoint returned ' . $response->getStatusCode()); + } + + return json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); + } + + /** + * Validate the ID Token (minimal validation for demo purposes). + */ + public function validateIDToken(string $idToken, string $nonce): array + { + // Authelia uses RS256 by default, but your config says "none". + // We'll decode without verification if alg is "none". + $decoded = JWT::decode($idToken, new Key('', ''), ['none', 'RS256']); + + // Check nonce matches what we stored in session. + if ($decoded->nonce !== $nonce) { + throw new \RuntimeException('Invalid nonce'); + } + + return (array)$decoded; + } +} \ No newline at end of file