Overview of entire authentication code flow

Overview of entire authentication code flow

by Alan Zaitchik -
Number of replies: 29

Can anyone point me to an overview of how Moodle authentication works in terms of all the various auth modules and the code flow between core Moodle and module code? I have been looking at some of the core code, but it is hard to follow. Perhaps this has been discussed here, but searching on the obvious terms did not yield what I am looking for.

Thanks

Average of ratings: -
In reply to Alan Zaitchik

Re: Overview of entire authentication code flow

by Alan Wessman -
Having just recently developed a custom authentication plugin, this is fresh in my mind. Please note that I am using Moodle 1.9.2, so different versions might work differently.

The authentication use case in Moodle starts when a user clicks on the Login link in the UI. Then the following happens (skipping some minor details and rarer scenarios):
  1. If using the default login page, the page /login/index.php is displayed, asking for the user's credentials. OR, if a system administrator has set the Alternate Login URL on the "Manage authentication" page, that URL will be displayed.
  2. User enters their credentials and submits the form.
  3. The handler code in /login/index.php runs:
    1. It gets a list of enabled authentication plugins.
    2. It runs loginpage_hook() for each plugin, in case any of them needs to intercept the login request.
    3. It checks to make sure that the username meets Moodle's criteria (alphanumeric, with periods and hyphens allowed).
    4. It calls authenticate_user_login() in /lib/moodlelib.php, which returns a $user object. (Details of this code follow this main outline.)
    5. It determines whether authentication was successful (by checking whether $user is a valid object) and, if not, sends them back to the login page with an error message. Otherwise, it figures out where to send the user based on their original page request, whether their password is expired, etc., and redirects them there.
That's the main outline, but a lot of interesting stuff happens in authenticate_user_login():
  1. It gets a list of enabled authentication plugins.
  2. It looks up the username in the mdl_user table to see if they are allowed to log in, and which authentication plugin handles their login requests. (This will be the plugin that handled their first-ever login request.)
  3. It creates a user object, which will contain the data from mdl_user if the username is known; if not, it will be an empty object.
  4. It does the following with the authentication plugin (note that for a username unknown to Moodle, it will do these steps for each authenticated plugin until one succeeds or it has tried them all):
    1. Calls the user_login() function provided by that plugin, which returns a boolean value based on whether the credentials authenticate or not. If the result is false (not authenticated), skips the rest of the steps below and continues to the next plugin.
    2. If the plugin authenticates against an external system (not Moodle's user database), its update_user_record() function is called to get the user's name, contact info, etc.
    3. Creates the Moodle user record if it doesn't already exist.
    4. Calls the plugin's sync_roles() function (I am not clear what this is exactly supposed to do).
    5. Notifies each enabled authentication plugin that the user successfully authenticated, by calling each one's user_authenticated_hook() function.
  5. It returns the user object if everything was successful, or false if no plugin was able to successfully authenticate the credentials.

That's the gist of it; I hope this is useful.
Average of ratings: Useful (5)
In reply to Alan Wessman

Re: Overview of entire authentication code flow

by Penny Leach -
Hi,

Thanks for writing that up, it would be great to add it to Moodledocs in something like 'How to write an authentication plugin'

smile Penny
In reply to Penny Leach

Re: Overview of entire authentication code flow

by Alan Wessman -
In reply to Alan Wessman

Re: Overview of entire authentication code flow

by Helen Foster -
Picture of Core developers Picture of Documentation writers Picture of Moodle HQ Picture of Particularly helpful Moodlers Picture of Plugin developers Picture of Testers Picture of Translators
Hi Alan,

Thanks a lot for adding your authentication plugins write-up to the documentation wiki. approve I've added a few links to the page so that people can find it easily in future.
In reply to Alan Wessman

Re: Overview of entire authentication code flow

by Alan Zaitchik -
Wow. That was great. Thanks.

If I may, let me ask you follow up, somewhat related questions.

1. What is the bare minimum that an authentication plugin MUST implement? Is it enough to implement user_login() and update_user_login()?

2. How does one register an authentication plugin and determine the order in which it is called in the list of authentication plugins?

Thanks.

In reply to Alan Zaitchik

Re: Overview of entire authentication code flow

by Alan Wessman -
1. The only function required to be implemented in all cases is user_login(). In Moodle 1.9, there is no update_user_login() function in the base plugin class.

If you have user signup and/or new user confirmation enabled, you will need to implement user_signup() and/or user_confirm(). We don't use those features, so we did not implement those functions.

In our case, we needed the external system to which we authenticate to provide the user's personal information (last and first name, etc.), so we implemented get_userinfo(). We also have a custom config page for the plugin, so we implemented config_form() and process_config(). And we overrode is_internal(), user_update_password(), and user_update() to turn off these defaults from the base plugin, based on our requirements. That's pretty much all we did to get a working plugin.

2. To create and register an authentication plugin, do the following:
  1. Choose a name for your plugin. I'll use 'sentry' as an example below; change it to whatever name you have chosen.
  2. Under your Moodle installation root, create the directory /auth/sentry. It should be sibling to existing auth plugin directories: 'db', 'nologin', 'none', etc.
  3. Create the file /auth/sentry/auth.php. Within the file, create a class auth_plugin_sentry that extends auth_plugin_base from /lib/authlib.php. (You will need to require_once the authlib file.)
  4. Make sure the constructor for your plugin class sets $this->authtype to the name of your plugin--in this case, 'sentry'.
  5. Implement the user_login() function in your auth.php file, and create or override additional functions based on your plugin's requirements.
  6. Log in to your Moodle installation as a site administrator and find, in the site administrator block, the page "Users -> Authentication -> Manage authentication". You will see your plugin in the list, appearing as auth_sentrytitle. You can enable it and move it up and down in the order. At this point, with the plugin enabled, your plugin is registered and will be used by Moodle in its authentication process.
  7. If you don't like seeing auth_sentrytitle as the name of your plugin in the Moodle UI, you'll need to create language files for your plugin. Do this by creating the directory /auth/sentry/lang, and under it, a directory for each language that your installation needs to support. (Example: /auth/sentry/lang/en_us_utf8.) Within each of these language directories, create a file called auth_sentry.php. That file should set the desired value for $string['auth_sentrytitle'] for that language. You can also set the plugin description by setting $string['auth_sentrydescription'], and you can also assign other translatable strings that your plugin uses, in these files.
  8. If you want to configure your plugin through the Moodle UI, implement config_form() and process_config() in the plugin class. I used the 'db' plugin as a model for this; you too might find this a useful approach. The plugin's config settings can then be managed through the Manage authentication page by clicking on the Settings link for that plugin, and the values will be stored in the mdl_config_plugins table in the database.
Average of ratings: Useful (2)
In reply to Alan Wessman

One last question...

by Alan Zaitchik -
With your helpful summary I am 90% of the way home, but there is one last (I suspect trivial) problem.
I want to have users try to authenticate first against my new authentication plugin, and if that returns false against the more general LDAP authentication setup I have configured (and which works fine).
How do I set these up in the mdl_user.auth field? Neither comma separated nor space separated values worked. The admin UI seems to allow only one method to be specified.
Thanks, again.

In reply to Alan Zaitchik

Re: One last question...

by Alan Wessman -
Well, as I understand it, that field does only support one method for authentication, so once a user has successfully authenticated by a particular method, that is the only one that will handle that user's authentication requests from that point on (unless the value is changed somehow).

One possible way to get around that is to have your new plugin do the falling back, instead of relying on Moodle's native process for it. I'm not sure if I'm being very clear here, so I'll get a bit more detailed.

In your new plugin's user_login() function, if it failed to authenticate by its particular credentials, it could instantiate/find the LDAP plugin itself and call its user_login() function, and then return the result of that.

From Moodle's perspective, then, it's your plugin that successfully authenticates the user, and so your plugin is recorded in mdl_user.auth. Your plugin is basically a wrapper for the LDAP plugin, after it has done its own specialized authentication.

If you take this approach, you might have to do similar proxying in the other plugin functions to get user information, etc., but I think it ought to work all right.
Average of ratings: Useful (1)
In reply to Alan Wessman

That seems kind of crazy...

by Alan Zaitchik -
I guess I could do as you suggest, but there MUST be some other way to make this work using the Moodle code itself-- not that I am in a position to disagree with you...
I remember seeing code in one of the files that gets the list of plugins enabled for this particular user, as opposed to the list of enabled plugins in general-- so somewhere there must be such a list for each user, no? I will try to hunt down the code that by now is foggy in my memory.
In reply to Alan Zaitchik

Re: That seems kind of crazy...

by Alan Wessman -
I think I know which code you're talking about (though I don't have the source code in front of me to be able to tell you which exact code it was -- but it's most likely authenticate_user_login() in moodlelib.php). The code creates an array and then loops through the plugins, putting the appropriate ones in the array.

Problem is, if I understood the code correctly, the array is really there for the case when the user hasn't yet been associated with an authentication plugin, either because their record didn't exist yet or else it did but somehow hadn't yet been authenticated against. When the user's record does have a value in the 'auth' field, I believe the resulting array will only contain that plugin as its single element. I might be wrong about this but I remember looking closely at that bit of code when I ran across it. Do please feel free to correct me if I'm mistaken.
In reply to Alan Wessman

Re: That seems kind of crazy...

by Alan Zaitchik -
The way I read the function authenticate_user_login in moodlelib.php there is a loop in which all enabled authentication plugins are tried (until one succeeds). I am refering to the lines
foreach ($auths as $auth) {
$authplugin = get_auth_plugin($auth);

// on auth fail fall through to the next plugin
if (!$authplugin->user_login($username, $password)) {
continue;
}

// successful authentication
if ($user->id) { // User already exists in database
if (empty($user->auth)) { // For some reason auth isn't set yet
set_field('user', 'auth', $auth, 'username', $username);
$user->auth = $auth;
}

... <skipped some stuff> ...
} else {
// if user not found, create him
$user = create_user_record($username, $password, $auth);
}
In other words, it looks like the user table auth field gets set to whatever authentication plugin authenticates the user in case it was unset, but that in any event all the enabled authentication plugin modules get a chance to authenticate the user.
So I am unclear how the contents of the mdl_user.auth field prevent all but one module from trying to authenticate the user. Maybe I am misunderstanding the code...
Alan
In reply to Alan Zaitchik

Re: That seems kind of crazy...

by Alan Wessman -
You're right, in the sense that all of the plugins in the $auths array are tried in order to authenticate the user. But what's in the $auths array when you get to that point?

Here's the code above the section you quoted:

 $authsenabled = get_enabled_auth_plugins();

 if ($user = get_complete_user_data('username', $username)) {
 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
 if ($auth=='nologin' or !is_enabled_auth($auth)) {
 add_to_log(0, 'login', 'error', 'index.php', $username);
 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
 return false;
 }
 if (!empty($user->deleted)) {
 add_to_log(0, 'login', 'error', 'index.php', $username);
 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
 return false;
 }
 $auths = array($auth);

 } else {
 $auths = $authsenabled;
 $user = new object();
 $user->id = 0; // User does not exist
 }


The main thing to note here is that $auths does not always contain the entire list of enabled plugins. It does only if the username can't be found in mdl_user. If Moodle already knows about the user, then $auths will contain exactly one element. That will either be the plugin named in the 'auth' field of the user record, or else 'manual' if for some reason there was no authentication method associated with that user.

So, the code makes it look like Moodle is trying multiple plugins, but in the case of a known user, it's looping over a list of one element, so that's the only one that's tried.
In reply to Alan Wessman

Re: That seems kind of crazy...

by Alan Zaitchik -
Thanks for the clarifications. I hadn't read the code very carefully.
I guess I am surprised that there isn't support in the core for an iterative process of testing authentication, same as their is for enrollments. Or at least a switch to enable it.

In reply to Alan Zaitchik

Re: That seems kind of crazy...

by Elvedin Trnjanin -
I'd say there is a pretty good reason for that. For those of us that use plugins other than 'manual', we still fill the password fields with something that probably isn't unique to all users and most likely isn't hard to guess.

The problem would be if a password fails in your main plugin and then defaults to the 'manual' auth plugin, it would go against whatever is in the user's password field. If the default entry is something simple like 'password' then you're in trouble.

You would probably want to check against LDAP in your own plugin rather than modifying the authentication "protocol" Moodle runs through to check for these cases.
In reply to Alan Wessman

Re: That seems kind of crazy...

by HJWUCGA INC. -
Hi there,

I'm trying to create a Single Sign-On like functionality from an existing Coldfusion based application to pass their credentials (via LDAP - eDirectory) over to moodle (LDAP - eDirectory).

I have a conceptual idea on how to do this but not codewise (not yet anyway) .. was thinking of getting passing the cn or uid in the http headers and moodle looks for it and does the authenticating to the ldap-eDirectory tree.

Anyone have ideas?

thanks
In reply to HJWUCGA INC.

Re: That seems kind of crazy...

by Ron W -
I have been looking into Single Sign-On as well.

The easy part is account creation. After the "main" application records the user info in the database, it can send a HTTP request to Moodle to login the user for the first time. This will cause Moodle to create the user's Moodle account.

I am assuming the Moodle uses PHP session cookies. If so, I have not yet found a way to generate such cookies from another application. If not, then it should be possible to port Moodle session cookie generator to work in another application.

I suppose could have the main app log into Moodle via HTTP request and pass through the cookie from the HTTP response, but will slow the main app's response.

Another idea would be to modify Moodle to accept an extra cookie, generated by the main app, and use that to log the user in automatically. For security reasons, this would require the cookie value be securely hashed and only be accepted once.
In reply to Alan Wessman

Re: That seems kind of crazy...

by E. Smith -
Very interesting discussion. I'm coming to this quite late, so I'm not sure anyone will see or respond to this.

In any case, I have a need to use multiple authentication methods and to allow them to be used "interchangeably". As opposed to the "one and done" way things are now.

My solution to this problem would appear to be a small hack

I just bypassed the code that fills the array with auth methods with the ones I want checked.

Testing shows that this works for me, but I fear that i may be missing some major issue with this. (the generic password thing is not a issue for us).

Thoughts?

--Thanks

in ./lib/moodlelib.php

function authenticate_user_login($username, $password) {

global $CFG;

$authsenabled = get_enabled_auth_plugins();

if ($user = get_complete_user_data('username', $username)) {
$auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
if ($auth=='nologin' or !is_enabled_auth($auth)) {
add_to_log(0, 'login', 'error', 'index.php', $username);
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
return false;
}
//added to get fall through authentication
// not recommend by Moodle
//$auths = array($auth);
$auths = array(manual,db,ldap);

} else {........

In reply to Alan Wessman

Re: Overview of entire authentication code flow

by islam khalil -
thanx man
your post is really helpfull for me but i have a question if any one can help me .
is there any way to auth user using cookies only .
as iam auth users in another application and set a cookie in the domain level and other application check this cookie and give users ticke after validate this user in his db

what the apis i can use to auth user directly
thanx

regards

In reply to Alan Wessman

Re: Overview of entire authentication code flow

by Candice Taylor -

Apologies for hijacking this thread, but so far this is the closest one to what i'm looking for.


I'm experiencing an issue where authenticate_user_login()  isn't operating quite as it should.

For instance, if a user logs in with the incorrect password, the correct moodle process follows, whereby the login page gets updated with an appropriate error message.

However the same does not happen if the user uses and incorrect username. Instead, the page is reloaded with an HTTP 500 error (or if I turn off friend HTML error messages in the browser, the login page becomes completely blank)

If anyone has an idea, or can direct me to the right place, I would be most grateful.


Thanks

Attachment moodle1.png
Attachment moodle2.png
In reply to Candice Taylor

Re: Overview of entire authentication code flow

by Candice Taylor -

With regard to my post above,


If error reporting is turned on in the php.ini file, I get this error


ADONewConnection: Unable to load database driver ''


I'm hoping this might be useful to someone who knows more than I do, and can help shed light on my problem.

In reply to Alan Zaitchik

Re: Overview of entire authentication code flow

by Sourjya Sankar Sen -
Funny.. I'm facing a similar problem.

I'm trying to authenticate against the user db of my website (CMS based) and it uses a slightly different approach at storing hashed passwords. It uses a randomly generated salt for each user. Direct field-mapped authentication (as the External DB plugin does) won't work for me.

Hence I just mirrored the DB plugin and modified the user_login() procedure to calculate the hash and match it up against the database. So far so good.

But when I try to login, username / passwords corresponding to the website database are failing. However, the password (for the same user) that was stored in Moodle earlier on is getting me through.

That means, either my authentication routine is failing or moodle's internal db based auth mechanism is taking precedence over it.

I have all other forms of authentication turned off (except for Manual which can't be turned off) and my own.

Any ideas on how to solve this issue?

Thanks,
Sourjya
In reply to Alan Wessman

Re: Overview of entire authentication code flow

by Moodle User -

Hello, I hope anyone in this forum can help me please.

I have enabled OpenId plugin in Moodle 1.9.7. After I enter my openid in the login page it takes me to my openID server page where i need to confirm some of the details(which is all working fine).

But when i click the confirm button on this page, I am redirected to Moodle's Error page saying that "Authentication failed. Server reported: Server denied check_authentication"

I am really trying hard to find what is wrong here. this is bit urgent. Any response would be greatly appreciated.

Thanks in advance.

In reply to Moodle User

Re: Overview of entire authentication code flow

by Marc Grober -
Link to Stuart Metcalf at bottom of module page amd link to bug tracker on right:
https://bugs.launchpad.net/moodle-openid/
notice first item
In reply to Marc Grober

Re: Overview of entire authentication code flow

by Moodle User -
Thanks again Marc. Ya I found the Bugs and Issues link on that page. Also sent a message to Stuart asking about the status of bug.
In reply to Alan Wessman

Re: Overview of entire authentication code flow

by Craig Simmons -
ALan,

Thank you - very helpful.,

We are having a problem whereby no error message is displayed when the wrong password is entered.

Any idea where the process may be going wrong?

Thanks

Craig

In reply to Alan Zaitchik

Re: Overview of entire authentication code flow

by dave c -
I've been able to implement an SSO auth plugin with these methods in Moodle 1.9:
  • loginpage_hook
  • user_authenticated_hook
  • get_userinfo
  • user_update
  • sync_roles
  • user_delete
  • user_exists
  • is_internal
  • config_form
  • process_config
My question is this: How many versions back in Moodle can I support? In other words, how long has this authentication plugin mechanism been around?

And will things change at all in Moodle 2.0?

In reply to dave c

Re: Overview of entire authentication code flow

by Iñaki Arenaza -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

1.8 should work (all of those functions are there). 1.7 would probably work, but i haven't looked at it (1.7 is too old and has a lot of performance issues, so anyone running 1.7 should upgrade to 1.8 at least).

As of today, nothing has changed in Moodle 2.0 in this area (barring minor tweaks here and there), but who knows what could change before 2.0 comes out? smile

Saludos. Iñaki.

In reply to dave c

Re: Overview of entire authentication code flow

by Froffo Firfo -
Hi.
Would you like to share your SSO plugin?

Thank you.