Important Note: This forum is being archived and will be completely locked down on the 7th of February. The new forum can be found at http://laravel.io.
Not sure if this is a "best practice" post or not, but I would like to do it the "proper" way so that it plays nicely with other packages.
Basically, my users are on a remote system, and can be accessed through an API. I have a library to handle all the API access. The remote system has the usernames, password hashes and other details about the users. I do not want or need to store them on the local Laravel application, so access to these users will be via the API. Access privileges to resources on the Laravel site may be stored on the L4 site. When a user logs in, details about that user will be pulled into the session and perhaps that is the point the user's ACL config can be set or updated.
So, very broad question, how do I go about this? I can see that users are within a nice big stack that includes facades, auth service providers, a user provider, a generic user, etc. but I just can't see where I would need to hook in. Would I need to replace the Auth facade and build everything beneath? Or maybe just the providers?
What I can't work out, is whether I need a few configuration details to point to one or two of my own classes to handle the storage and retrieval of users (which is essentially all I need) or whether I have to replace a much bigger chunk of the stack that sits under the Auth Facade.
Anyone done this before? Any hints or tutorials that I could be pointed at? Thanks :-)
Is it actually just a new *driver* that I need here? Would I create a custom driver, being an instance of Guard() but injected with my own ExternalUserProvider() rather than one of the built-in user providers such as Illuminate\Auth\DatabaseUserProvider()?
If so, any idea where I would do this? I'm not sure if I am extending classes, or registering something somewhere.
As usual, half the problem is knowing what you are looking for. I found "extending auth" in the L4 documentation (I was looking at extending *users* rather than *auth*). This has led to a neat package for authentication against WordPress:
That package, as a worked example, is exactly what I needed.
The hampel package has got me halfway there. It allows me to sent password reset emails, manages the token, authenticates against an external system etc.
However, once authenticated, what then? It does not provide an interface to an external user except for the action of authentication. I can "log in" using Auth::attempt() and Laravel will go to the external system and fetch the remote user and password hash and confirm that it all matches. But what then needs to happen for that user to actually be logged in? Issuing User::check() or anything like that returns an error telling me the "user" table does not exist. Do I *have to* create a user in a local table once they have been authenticated? I don't really want to - the authenticated user details can sit in the session so far as I am concerned. But does the general Laravel ecosystem not support that?
Edit: is it Guard I need to extend to manage users through an external system?
Edit2: Yes, it looks like it. So I have x that handles external authentication and password recovery, but does not handle the user itself. Then packages such as https://github.com/franzliedke/auth-fluxbb handle the external authentication, and do handle the user, but don't touch password recovery. So I "just" need to put them into a melting pot to merge them together.
Laravel is fun, but working with it can be hard. The problem is needing to keep twenty balls in the air at the same time to do what should be simple things. It kind of works well at runtime, as there is this big hierarchy of providers and objects created lazily when needed, but there is no easy way to view that hierarchy or quick way to get to grips with it. Or maybe I'm missing something?
Last edited by JasonJudge (2013-11-15 17:46:51)
You shouldn't need to extend Guard, simply use Auth::extend to add a custom UserProvider. As long as you replace the UserProvider that should be all you need to do. Of course if you want to be able to query the user table for other purposes than authenticating, you'll have to set up a model that uses a second DB connection.
Thanks. I've got a custom user provider, and that seems to work for authentication - I can successfully authenticate the user (with the custom hasher injected) and reset the password. So authentication is working.
What I then fail to see is where the "logged in user" is actually kept. I can do an Auth::attempt() with the user entered email and password, and I get a "success" returned from that. But I'm missing something - if I then do User::check() or User::get() I see database errors as Laravel tries to get details from the database. I'm not sure where that is coming from, as there is no User provider or alias in config/app.php
Is "User" the model that gets put into app/models/User.php by default? Is that what I would need to change to use the same driver that I use in the AuthUser Provider? And how does that connect with the user that the Auth provider now believes is logged in? I just don't see how the two connect.
Edit: the thing is, there is an Eloquent user model in app/models/User.php and there is an Eloquent user model Provider in Illuminate\Auth\ that creates a user as an Equoquent model (but not a app/models/User model so far as I can see). And then there is an Illuminate\Auth\GenericUser.php that also fits into this somewhere. Of course, none of these scripts contain notes to explain what they are doing there and what their intended uses are.
Last edited by JasonJudge (2013-11-17 15:21:12)
What actually is "Guard"? It seems to contain the methods that gets called by Auth::whatever() Is it a general container for all things related to the current authenticated user? It is obviously nothing to do with the other "guard" that pre-processes front-end assets.
Last edited by JasonJudge (2013-11-17 16:55:42)
Okay, it is working for me now. I'm not sure what I've tweaked, but now Auth::check() tells me if I'm logged in, and Auth::user() returns the user model. It does seem to fetch the user model from the remote CRM every time it is requested (so every page) but I guess I need to set up events (on Guard?) to save the user details in the session when a user logs in, by serialising the model.
Auth::user() will always return the logged in user object as fetched by your user provider. You don't need to serialize or store it in the session - Laravel automatically saves the user ID in the session and asks the user provider to retrieve the user on each request (provided you call Auth::check() or Auth::user() at some point). If your user provider is the default Eloquent provider, it will use the model you set in app/config/auth.php - but since you're using a custom provider, the provider is in full control over what is stored and retrieved and what models are used.
Guard is indeed the class that is behind the Auth:: facade. Technically Auth points to the AuthManager class, which is more or less a decorator/proxy/wrapper for Guard, which is the class that handles authentication.
Thanks, that helps a lot. It is that kind of overview that is difficult to get from the code alone.
I think my user provider will need to do the session caching. The user is fetched and authenticated on a remote application over an RPC-like-REST-ish (it's messy) protocol, so if I can reduce overhead of fetching the details of the on every single page, that would help performance and server load at both ends.
I've also need to find out if any of the groups and privileges packages that I would like to use insist on users being stored in the same local database. It could be that keeping a shadow of the external users in a local users table may be necessary for user/privilege relationships, and would double as the caching too. Update events could handle getting updates (names, addresses etc.) sent back to the remote system.
Last edited by JasonJudge (2013-11-17 23:02:20)