aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+studip@gmail.com>2024-08-16 11:10:00 +0000
committerJan-Hendrik Willms <tleilax+studip@gmail.com>2024-08-16 11:10:00 +0000
commit647c77c1a7cd3adcad6715b3e9a8b3603e5d9579 (patch)
treeb272b92a1a1ff3bb104ed9565f6e1a38fb159032
parent27fcdec5c327767f26aca05cefef6272942e8dd0 (diff)
add oauth2 as auth plugin, fixes #4482
Closes #4482 Merge request studip/studip!3266
-rw-r--r--composer.json3
-rw-r--r--composer.lock443
-rw-r--r--config/config_defaults.inc.php30
-rw-r--r--lib/classes/auth_plugins/StudipAuthOAuth2.php113
-rw-r--r--lib/phplib/Seminar_Auth.php8
-rw-r--r--lib/seminar_open.php9
6 files changed, 598 insertions, 8 deletions
diff --git a/composer.json b/composer.json
index e7fb7e5..4a80967 100644
--- a/composer.json
+++ b/composer.json
@@ -123,7 +123,8 @@
"symfony/polyfill-php83": "1.30.0",
"symfony/polyfill-php84": "1.30.0",
"nyholm/psr7": "1.8.1",
- "nyholm/psr7-server": "1.1.0"
+ "nyholm/psr7-server": "1.1.0",
+ "league/oauth2-client": "2.7.0"
},
"replace": {
"symfony/polyfill-php73": "*",
diff --git a/composer.lock b/composer.lock
index 8af2df6..0cc31d1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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": "4d8cd43aecf3277942d94871e22bf861",
+ "content-hash": "cad2a823e38968efd43dc8b63fdd8812",
"packages": [
{
"name": "algo26-matthias/idna-convert",
@@ -361,6 +361,331 @@
"time": "2023-11-12T22:16:48+00:00"
},
{
+ "name": "guzzlehttp/guzzle",
+ "version": "7.9.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
+ "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
+ "guzzlehttp/psr7": "^2.7.0",
+ "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.9.2"
+ },
+ "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": "2024-07-24T11:22:20+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
+ "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ },
+ "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.0.3"
+ },
+ "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": "2024-07-18T10:29:17+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "2.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "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.39 || ^9.6.20"
+ },
+ "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.7.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": "2024-07-18T11:15:46+00:00"
+ },
+ {
"name": "illuminate/collections",
"version": "v10.48.12",
"source": {
@@ -1020,6 +1345,76 @@
"time": "2018-11-26T11:52:41+00:00"
},
{
+ "name": "league/oauth2-client",
+ "version": "2.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/oauth2-client.git",
+ "reference": "160d6274b03562ebeb55ed18399281d8118b76c8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8",
+ "reference": "160d6274b03562ebeb55ed18399281d8118b76c8",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "^6.0 || ^7.0",
+ "paragonie/random_compat": "^1 || ^2 || ^9.99",
+ "php": "^5.6 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.3.5",
+ "php-parallel-lint/php-parallel-lint": "^1.3.1",
+ "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5",
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-2.x": "2.0.x-dev"
+ }
+ },
+ "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.7.0"
+ },
+ "time": "2023-04-16T18:19:15+00:00"
+ },
+ {
"name": "league/oauth2-server",
"version": "8.5.4",
"source": {
@@ -3383,6 +3778,50 @@
"time": "2024-04-02T15:57: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": "scssphp/scssphp",
"version": "v1.12.1",
"source": {
@@ -8217,5 +8656,5 @@
"platform-overrides": {
"php": "8.1"
},
- "plugin-api-version": "2.2.0"
+ "plugin-api-version": "2.6.0"
}
diff --git a/config/config_defaults.inc.php b/config/config_defaults.inc.php
index 78d5356..ce48492 100644
--- a/config/config_defaults.inc.php
+++ b/config/config_defaults.inc.php
@@ -245,13 +245,14 @@ $_language_domain = "studip"; // the name of the language file. Should not be c
----------------------------------------------------------------
the following plugins are available:
Standard authentication using the local Stud.IP database
-StandardExtern authentication using an alternative Stud.IP database, e.g. another installation
+StandardExtern authentication using an alternative Stud.IP database, e.g. another installation
Ldap authentication using an LDAP server, this plugin uses anonymous bind against LDAP to retrieve the user dn,
- then it uses the submitted password to authenticate with this user dn
+ then it uses the submitted password to authenticate with this user dn
LdapReader authentication using an LDAP server, this plugin binds to the server using a given dn and a password,
- this account must have read access to gather the attributes for the user who tries to authenticate.
-CAS authentication using a central authentication server (CAS)
+ this account must have read access to gather the attributes for the user who tries to authenticate.
+CAS authentication using a central authentication server (CAS)
Shib authentication using a Shibboleth identity provider (IdP)
+OAuth2 authentication using an OAuth2 identity provider
If you write your own plugin put it in studip-htdocs/lib/classes/auth_plugins
and enable it here. The name of the plugin is the classname excluding "StudipAuth".
@@ -267,6 +268,7 @@ $STUDIP_AUTH_PLUGIN[] = "Standard";
// $STUDIP_AUTH_PLUGIN[] = "LTI";
// $STUDIP_AUTH_PLUGIN[] = "Shib";
// $STUDIP_AUTH_PLUGIN[] = "IP";
+// $STUDIP_AUTH_PLUGIN[] = 'OAuth2';
$STUDIP_AUTH_CONFIG_STANDARD = ["error_head" => "intern"];
@@ -378,6 +380,26 @@ $STUDIP_AUTH_CONFIG_SHIB = array("session_initiator" => "https://sp.studip.de/Sh
$STUDIP_AUTH_CONFIG_IP = array('allowed_users' =>
array ('root' => array('127.0.0.1', '::1')));
+
+$STUDIP_AUTH_CONFIG_OAUTH2 = [
+ 'client_id' => '',
+ 'client_secret' => '',
+ 'redirect_uri' => '',
+
+ 'url_authorize' => '',
+ 'url_access_token' => '',
+ 'url_resource_owner_details' => '',
+
+ 'login_description' => 'Login with OAuth2',
+
+ 'user_data_mapping' => [
+ 'auth_user_md5.username' => ['callback' => 'getUserData', 'map_args' => 'nickname'],
+ 'auth_user_md5.password' => ['callback' => 'dummy', 'map_args' => ''],
+ 'auth_user_md5.Vorname' => ['callback' => 'getUserData', 'map_args' => 'given_name'],
+ 'auth_user_md5.Nachname' => ['callback' => 'getUserData', 'map_args' => 'family_name'],
+ 'auth_user_md5.EMail' => ['callback' => 'getUserData', 'map_args' => 'email'],
+ ],
+];
*/
//some additional authification-settings
diff --git a/lib/classes/auth_plugins/StudipAuthOAuth2.php b/lib/classes/auth_plugins/StudipAuthOAuth2.php
new file mode 100644
index 0000000..aa90776
--- /dev/null
+++ b/lib/classes/auth_plugins/StudipAuthOAuth2.php
@@ -0,0 +1,113 @@
+<?php
+use League\OAuth2\Client\Provider\GenericProvider;
+
+/**
+ * StudipAuthOAuth2.php - Stud.IP authentication using OAuth2
+ *
+ * @copyright 2024 Jan-Hendrik Willms <tleilax@gmail.com>
+ * @license GPL2 or any later version
+ * @since Stud.IP 6.0
+ */
+final class StudipAuthOAuth2 extends StudipAuthSSO
+{
+ protected string $client_id;
+ protected string $client_secret;
+ protected string $redirect_uri;
+
+ protected string $url_authorize;
+ protected string $url_access_token;
+ protected string $url_resource_owner_details;
+
+ private GenericProvider $oauth2_provider;
+
+ private ?array $user_data = null;
+
+ public function __construct($config = [])
+ {
+ parent::__construct($config);
+
+ if (!isset($this->plugin_fullname)) {
+ $this->plugin_fullname = _('OAuth2');
+ }
+
+ if (Request::option('sso') === $this->plugin_name) {
+ $options = [
+ 'clientId' => $this->client_id,
+ 'clientSecret' => $this->client_secret,
+ 'redirectUri' => $this->redirect_uri,
+ 'urlAuthorize' => $this->url_authorize,
+ 'urlAccessToken' => $this->url_access_token,
+ 'urlResourceOwnerDetails' => $this->url_resource_owner_details,
+ ];
+
+ if (Config::get()->getValue('HTTP_PROXY')) {
+ $options['proxy'] = Config::get()->getValue('HTTP_PROXY');
+ $options['verify'] = false;
+ }
+
+ $this->oauth2_provider = new GenericProvider($options);
+ }
+ }
+
+ public function getUser()
+ {
+ return $this->getUserData($this->getUsernameKey());
+ }
+
+ public function verifyUsername($username)
+ {
+ if (isset($this->user_data)) {
+ return parent::verifyUsername($this->getUser());
+ }
+
+ if (!Request::get('code')) {
+ $authorizationUrl = $this->oauth2_provider->getAuthorizationUrl(['scope' => 'profile email']);
+
+ $_SESSION[self::class] = [
+ 'state' => $this->oauth2_provider->getState(),
+ 'redirect' => Request::url(),
+ ];
+
+ page_close();
+ header('Location: ' . $authorizationUrl);
+ die;
+ } elseif (
+ !Request::get('state')
+ || empty($_SESSION[self::class]['state'])
+ || Request::get('state') !== $_SESSION[self::class]['state']
+ ) {
+ if (isset($_SESSION[self::class])) {
+ unset($_SESSION[self::class]);
+ }
+ } else {
+ $accessToken = $this->oauth2_provider->getAccessToken('authorization_code', [
+ 'code' => Request::get('code'),
+ ]);
+
+ $resourceOwner = $this->oauth2_provider->getResourceOwner($accessToken);
+
+ $this->user_data = $resourceOwner->toArray();
+
+ return parent::verifyUsername($this->getUser());
+ }
+
+ return null;
+ }
+
+ /**
+ * Callback that can be used in user_data_mapping array.
+ */
+ public function getUserData(string $key): ?string
+ {
+ return $this->user_data[$key];
+ }
+
+ /**
+ * Returns the key used to store the username from user_data_mapping if
+ * present. Defaults to 'nickname'.
+ */
+ private function getUsernameKey(): string
+ {
+ return $this->user_data_mapping['map_args']['auth_user_md5.username'] ?? 'nickname';
+ }
+}
diff --git a/lib/phplib/Seminar_Auth.php b/lib/phplib/Seminar_Auth.php
index 92ee2c1..546d6d8 100644
--- a/lib/phplib/Seminar_Auth.php
+++ b/lib/phplib/Seminar_Auth.php
@@ -125,7 +125,13 @@ class Seminar_Auth
# Check for user supplied automatic login procedure
if ($uid = $this->auth_preauth()) {
$this->auth["uid"] = $uid;
- $sess->regenerate_session_id(['auth', '_language', 'phpCAS', 'contrast']);
+ $sess->regenerate_session_id([
+ '_language',
+ 'auth',
+ 'contrast',
+ 'phpCAS',
+ StudipAuthOAuth2::class
+ ]);
$sess->freeze();
$GLOBALS['user'] = new Seminar_User($this->auth['uid']);
return true;
diff --git a/lib/seminar_open.php b/lib/seminar_open.php
index 2e831bf..7e3acf6 100644
--- a/lib/seminar_open.php
+++ b/lib/seminar_open.php
@@ -158,6 +158,15 @@ if (Navigation::hasItem('/profile/edit')) {
}
if ($user_did_login) {
+ if (isset($_SESSION[StudipAuthOAuth2::class]['redirect'])) {
+ $redirect = $_SESSION[StudipAuthOAuth2::class]['redirect'];
+ unset($_SESSION[StudipAuthOAuth2::class]);
+
+ page_close();
+ header('Location: ' . $redirect);
+ die;
+ }
+
NotificationCenter::postNotification('UserDidLogin', $user->id);
}