Skip to content

Commit

Permalink
Silent SSO login implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
weilai-irl committed Jul 1, 2024
1 parent 79fea3c commit 21afa2e
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 13 deletions.
23 changes: 17 additions & 6 deletions auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ public function loginpage_hook() {
* @return bool If this returns true then redirect
*/
public function should_login_redirect() {
global $SESSION;
global $CFG, $SESSION;

$oidc = optional_param('oidc', null, PARAM_BOOL);
// Also support noredirect param - used by other auth plugins.
$noredirect = optional_param('noredirect', 0, PARAM_BOOL);
Expand All @@ -137,13 +138,22 @@ public function should_login_redirect() {
}

// Check whether we've skipped the login page already.
// This is here because loginpage_hook is called again during form
// submission (all of login.php is processed) and ?oidc=off is not
// preserved forcing us to the IdP.
// This is here because loginpage_hook is called again during form submission (all of login.php is processed) and
// ?oidc=off is not preserved forcing us to the IdP.
//
// This isn't needed when duallogin is on because $oidc will default to 0
// and duallogin is not part of the request.
// This isn't needed when duallogin is on because $oidc will default to 0 and duallogin is not part of the request.
if ((isset($SESSION->oidc) && $SESSION->oidc == 0)) {
if (!isset($SESSION->silent_login_mode)) {
return false;
}
}

// If the user is redirectred to the login page immediately after logging out, don't redirect.
$silentloginmodesetting = get_config('auth_oidc', 'silentloginmode');
$forceredirectsetting = get_config('auth_oidc', 'forceredirect');
$forceloginsetting = get_config('core', 'forcelogin');
if ($silentloginmodesetting && $forceredirectsetting && $forceloginsetting && isset($_SERVER['HTTP_REFERER']) &&
strpos($_SERVER['HTTP_REFERER'], $CFG->wwwroot) !== false) {
return false;
}

Expand All @@ -156,6 +166,7 @@ public function should_login_redirect() {
if (isset($SESSION->oidc)) {
unset($SESSION->oidc);
}

return true;
}

Expand Down
31 changes: 27 additions & 4 deletions classes/loginflow/authcode.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,27 @@ protected function getoidcparam($name, $fallback = '') {
public function handleredirect() {
global $CFG, $SESSION;

$error = optional_param('error', '', PARAM_TEXT);
$errordescription = optional_param('error_description', '', PARAM_TEXT);
$silentloginmode = get_config('auth_oidc', 'silentloginmode');
$selectaccount = false;
if ($silentloginmode) {
if ($error == 'login_required') {
// If silent login mode is enabled and the error is 'login_required', redirect to the login page.
$loginpageurl = new moodle_url('/login/index.php', ['noredirect' => 1]);
redirect($loginpageurl);
die();
} else if ($error == 'interaction_required') {
if (strpos($errordescription, 'multiple user identities') !== false) {
$selectaccount = true;
} else {
$loginpageurl = new moodle_url('/login/index.php', ['noredirect' => 1]);
redirect($loginpageurl);
die();
}
}
}

if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) {
$adminconsent = optional_param('admin_consent', '', PARAM_TEXT);
if ($adminconsent) {
Expand All @@ -127,7 +148,7 @@ public function handleredirect() {
$promptlogin = (bool)optional_param('promptlogin', 0, PARAM_BOOL);
$promptaconsent = (bool)optional_param('promptaconsent', 0, PARAM_BOOL);
$justauth = (bool)optional_param('justauth', 0, PARAM_BOOL);
if (!empty($state)) {
if (!empty($state) && $selectaccount === false) {
$requestparams = [
'state' => $state,
'code' => $code,
Expand Down Expand Up @@ -155,7 +176,7 @@ public function handleredirect() {
if ($justauth === true) {
$stateparams['justauth'] = true;
}
$this->initiateauthrequest($promptlogin, $stateparams, $extraparams);
$this->initiateauthrequest($promptlogin, $stateparams, $extraparams, $selectaccount);
}
}

Expand Down Expand Up @@ -186,10 +207,12 @@ public function user_login($username, $password = null) {
* @param bool $promptlogin Whether to prompt for login or use existing session.
* @param array $stateparams Parameters to store as state.
* @param array $extraparams Additional parameters to send with the OIDC request.
* @param bool $selectaccount Whether to prompt the user to select an account.
*/
public function initiateauthrequest($promptlogin = false, array $stateparams = array(), array $extraparams = array()) {
public function initiateauthrequest($promptlogin = false, array $stateparams = array(), array $extraparams = array(),
bool $selectaccount = false) {
$client = $this->get_oidcclient();
$client->authrequest($promptlogin, $stateparams, $extraparams);
$client->authrequest($promptlogin, $stateparams, $extraparams, $selectaccount);
}

/**
Expand Down
20 changes: 17 additions & 3 deletions classes/oidcclient.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,13 @@ public function get_endpoint($endpoint) {
* @param bool $promptlogin Whether to prompt for login or use existing session.
* @param array $stateparams Parameters to store as state.
* @param array $extraparams Additional parameters to send with the OIDC request.
* @param bool $selectaccount Whether to prompt the user to select an account.
* @return array Array of request parameters.
*/
protected function getauthrequestparams($promptlogin = false, array $stateparams = array(), array $extraparams = array()) {
protected function getauthrequestparams($promptlogin = false, array $stateparams = array(), array $extraparams = array(),
bool $selectaccount = false) {
global $SESSION;

$nonce = 'N'.uniqid();

$params = [
Expand All @@ -188,6 +192,14 @@ protected function getauthrequestparams($promptlogin = false, array $stateparams

if ($promptlogin === true) {
$params['prompt'] = 'login';
} else if ($selectaccount === true) {
$params['prompt'] = 'select_account';
} else {
$silentloginmode = get_config('auth_oidc', 'silentloginmode');
if ($silentloginmode) {
$params['prompt'] = 'none';
$SESSION->silent_login_mode = true;
}
}

$domainhint = get_config('auth_oidc', 'domainhint');
Expand Down Expand Up @@ -247,8 +259,10 @@ protected function getnewstate($nonce, array $stateparams = array()) {
* @param bool $promptlogin Whether to prompt for login or use existing session.
* @param array $stateparams Parameters to store as state.
* @param array $extraparams Additional parameters to send with the OIDC request.
* @param bool $selectaccount Whether to prompt the user to select an account.
*/
public function authrequest($promptlogin = false, array $stateparams = array(), array $extraparams = array()) {
public function authrequest($promptlogin = false, array $stateparams = array(), array $extraparams = array(),
bool $selectaccount = false) {
if (empty($this->clientid)) {
throw new moodle_exception('erroroidcclientnocreds', 'auth_oidc');
}
Expand All @@ -257,7 +271,7 @@ public function authrequest($promptlogin = false, array $stateparams = array(),
throw new moodle_exception('erroroidcclientnoauthendpoint', 'auth_oidc');
}

$params = $this->getauthrequestparams($promptlogin, $stateparams, $extraparams);
$params = $this->getauthrequestparams($promptlogin, $stateparams, $extraparams, $selectaccount);
$redirecturl = new moodle_url($this->endpoints['auth'], $params);
redirect($redirecturl);
}
Expand Down
16 changes: 16 additions & 0 deletions lang/en/auth_oidc.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@
$string['cfg_loginflow_authcode_desc'] = 'Using this flow, the user clicks the name of the IdP (See "Provider Display Name" above) on the Moodle login page and is redirected to the provider to log in. Once successfully logged in, the user is redirected back to Moodle where the Moodle login takes place transparently. This is the most standardized, secure way for the user log in.';
$string['cfg_loginflow_rocreds'] = 'Resource Owner Password Credentials Grant <b>(deprecated)</b>';
$string['cfg_loginflow_rocreds_desc'] = '<b>This login flow is deprecated and will be removed from the plugin soon.</b><br/>Using this flow, the user enters their username and password into the Moodle login form like they would with a manual login. This will authorize the user with the IdP, but will not create a session on the IdP\'s site. For example, if using Microsoft 365 with OpenID Connect, the user will be logged in to Moodle but not the Microsoft 365 web applications. Using the authorization request is recommended if you want users to be logged in to both Moodle and the IdP. Note that not all IdP support this flow. This option should only be used when other authorization grant types are not available.';
$string['cfg_silentloginmode_key'] = 'Silent Login Mode';
$string['cfg_silentloginmode_desc'] = 'If enabled, Moodle will try to use the active session of a user authenticated to the configured authorization endpoint to log the user in.<br/>
To use this feature, the following configurations are required:
<ul>
<li><b>Force users to log in</b> (forcelogin) in the <a href="{$a}" target="_blank">Site policies section</a> is enabled.</li>
<li><b>Force redirect</b> (auth_oidc/forceredirect) setting above is enabled.</li>
</ul>
In order to avoid Moodle trying to use personal accounts or accounts from other tenants to login, it is also recommended to use tenant specific endpoints, rather than generic ones using "common" or "organization" etc. paths.<br/>
<br/>
For Microsoft IdPs, the user experience is as follows:
<ul>
<li>If no active user session is found, Moodle login page will show.</li>
<li>If only one active user session is found, and the user has access to the Entra ID app (i.e. user is from the same tenant, or is a guest user of the tenant), the user will be logged in to Moodle automatically using SSO.</li>
<li>If only one active user session is found, but the user doesn\'t have access to the Entra ID app (e.g. the user is from a different tenant, or the app requires user assignment and the user isn\'t assigned), the Moodle login page will show.</li>
<li>If there are multiple active user sessions who have access to the Entra ID app, a page will show to allow the user to select the account to log in with.</li>
</ul>';
$string['oidcresource'] = 'Resource';
$string['oidcresource_help'] = 'The OpenID Connect resource for which to send the request.<br/>
<b>Note</b> this is paramater is not supported in <b>Microsoft identity platform (v2.0)</b> IdP type.';
Expand Down
6 changes: 6 additions & 0 deletions settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
$settings->add(new admin_setting_configcheckbox('auth_oidc/forceredirect',
get_string('cfg_forceredirect_key', 'auth_oidc'), get_string('cfg_forceredirect_desc', 'auth_oidc'), 0));

// Silent login mode.
$forceloginconfigurl = new moodle_url('/admin/settings.php', ['section' => 'sitepolicies']);
$settings->add(new admin_setting_configcheckbox('auth_oidc/silentloginmode',
get_string('cfg_silentloginmode_key', 'auth_oidc'),
get_string('cfg_silentloginmode_desc', 'auth_oidc', $forceloginconfigurl->out(false)), 0));

// Auto-append.
$settings->add(new admin_setting_configtext('auth_oidc/autoappend',
get_string('cfg_autoappend_key', 'auth_oidc'), get_string('cfg_autoappend_desc', 'auth_oidc'), '', PARAM_TEXT));
Expand Down

0 comments on commit 21afa2e

Please sign in to comment.