I am building an application that is using a third party authentication database. I have created a custom composer package to "intercept" the POST request to /login. Everything is working great - I'm able to get a user object back and save it to my (laravel) database.
I am now at the point where I want to redirect to the home page and do "stuff". I would like to use Laravel's native Auth as much as possible if I can.
For example, on the home page I am doing this:
$foo = auth()->user()->foo->where('active', 1);
No surprise, since I am not using Laravel's native Auth method, auth()->user() is returning null. Once I have my user created/found in my database, is it possible to tie back into Laravel's auth() methods?
Thank you for any suggestions!
EDIT
Reading the documentation, this looks like the direction I need to go but I'm falling short understanding how to connect/register my custom package (I think)...
EDIT 2
I am going to keep updating this as I feel I make any progress in hopes that it will not only help me, but help others get a better picture of what I am trying to accomplish. Ultimately help others who may be trying to do the same.
I have updated my app/Providers/AuthServiceProviderAuthServiceProvider as such:
use My\Package\MyThirdPartyServiceProvider;
...
Auth::provider('foo', function ($app, array $config) {
// Return an instance of Illuminate\Contracts\Auth\UserProvider...
return new MyThirdPartyServiceProvider($app->make('foo.connection'));
});
I have also updated my config/auth file:
'providers' => [
'users' => [
'driver' => 'foo',
'model' => App\User::class,
]
As you mentioned the documentation suggests implementing a custom user provider. The following steps more or less describe how you'll tackle it in a bit more detail.
Create or edit a service provider
You can create a new service provider by running
php artisan make:provider CustomAuthServiceProvider
In the boot method of your service provider you'll have to configure our auth provider (which will be implemented in step 4).
public function boot()
{
Auth::provider('custom-auth', function ($app, array $config) {
return new CustomAuthProvider();
});
}
Update your auth.php configuration to use the serviceprovider we registered in step 2
'providers' => [
'users' => [
'driver' => 'custom-auth',
],
],
Create the CustomAuthProvider class itself and implement the UserProvider interface
class CustomAuthProvider implements UserProvider
{
public function retrieveById($identifier) {
// Retrieve a user by their unique identifier.
}
public function retrieveByToken($identifier, $token) {
// Retrieve a user by their unique identifier and "remember me" token.
}
public function updateRememberToken(Authenticatable $user, $token) {
// Update the "remember me" token for the given user in storage.
}
public function retrieveByCredentials(array $credentials) {
// Retrieve a user by the given credentials.
}
public function validateCredentials(Authenticatable $user, array $credentials) {
// Validate a user against the given credentials.
}
}
Related
I want to use Laravel Auth::logoutOtherDevices. I added \Illuminate\Session\Middleware\AuthenticateSession::class to $middlewareGroups => web in kernal.
When I use this in login function :
public function login(Request $request)
{
Auth::attempt([
'email' => $request->input('email'),
'password' => $request->input('password')
], $request->has('remember'));
Auth::logoutOtherDevices($request->input('password'));
}
All passwords in my database change and I can't login with other accounts. :| Where is the problem?
Before getting started, you should make sure that the Illuminate\Session\Middleware\AuthenticateSession middleware is present and un-commented in your App\Http\Kernel class' web middleware group:
'web' => [
// ...
\Illuminate\Session\Middleware\AuthenticateSession::class,
// ...
],
Then, you may use the logoutOtherDevices method provided by the Auth facade. This method requires the user to confirm their current password, which your application should accept through an input form
use Illuminate\Support\Facades\Auth;
Auth::logoutOtherDevices($currentPassword);
You should have sendLoginResponse method after attempt as it is inside of AuthenticateUsers trait, because sendLoginResponse implements $request->session()->regenerate(); in it.
A clean implementation of your purpose is to leave trait's login method intact and create authenticated method in the controller that has AuthenticateUsers trait and add below code it in.
protected function authenticated(Request $request, $user)
{
Auth::logoutOtherDevices($request->get('password'));
}
I am using LumenPassport (https://github.com/dusterio/lumen-passport) and I followed a few tutorials listed here.
I used a combination of these tutorials as well as a heck of google and stackoverflow searches to achieve what I have thus far:
http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/
http://esbenp.github.io/2015/05/26/lumen-web-api-oauth-2-authentication/
https://blog.pusher.com/make-an-oauth2-server-using-laravel-passport/
What I achieved so far
1. Using password grant to get an access & refresh token
2. Storing these tokens in a secure http only cookie
3. Retrieving these tokens in Lumen's AuthServiceProvider
What I am unable to do
1. Getting the authenticated user with the AccessToken
I am trying to access either of these endpoints:
$router->group(['middleware' => 'auth:api'], function () use ($router) {
$router->get('/', function () use ($router) {return $router->app->version();});
$router->post('/logout', '\App\Auth\LoginController#logout');
});
I will immediately get an unauthorized error.. After some deep diving, the error comes from Authenticate.php which I know is called after AuthServiceProvider. I took a look at AuthServiceProvider and according to Lumen's documentation, this is how the boot method should looks like. Of course it is using the "api" driver and I had to switch it to "passport" for it to work.
AuthServiceProvider.php
public function boot()
{
$this->app['auth']->viaRequest('passport', function ($request) {
// dd("test") // this works
// dd(Auth::user());
// dd($request->user());
// dd(Auth::guard('api')->user());
});
}
Authenticate.php
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->guard($guard)->guest()) {
$status = Response::HTTP_UNAUTHORIZED;
return response()->json(['success' => false, 'status' => $status, 'message' => 'HTTP_UNAUTHORIZED'], $status);
}
return $next($request);
}
From here, I am still unable to get any of the authenticated user's information. I have made sure to access these endpoints with Postman with the appropriate Authorization headers.
The reason why I need to retrieve the user is because I hope that in my logout method, I will be able to then retrieve the accessToken of that authenticated user and revoke the token and clear the cookies.
LoginController.php
public function logout()
{
// Get the accessToken from Auth
// Need to fix AuthServiceProvider first
$accessToken = $this->auth->user()->token();
$refreshToken = $this->db
->table('oauth_refresh_tokens')
->where('access_token_id', $accessToken->id)
->update([
'revoked' => true,
]);
$accessToken->revoke();
$this->cookie->queue($this->cookie->forget(self::REFRESH_TOKEN));
}
At that point you cannot use Auth::user() since that function is the functionality for resolving that. So what you need to do is extract the bearer token with $request->bearerToken() and use that to retrieve your user.
Update
I took a look at your code and I would recommend the following:
An API is recommended to be 'stateless' meaning that it should not persist any state (i.e. cookies). It is far better to pass the access token with each request and let the application that accesses your API handle the tokens. Therefore I would recommend to remove the log-out functionality. Then you can do the following in your AuthServiceProvider:
if ($token_exists) {
$user = User::find($token->user_id);
return $user;
}
I want to authorize my API of my Laravel application.
My structure right now is like this:
Users belong to an organization, and the organization has many (lets say) objects.
Now I want that only users can view/edit/create/delete objects, that belong to the organization they are part of.
My API route for viewing the objects is:
Route::get('organizations/{id}/objects','ObjectController#indexOrganization')->middleware('auth:api');
I created the Models User, Organization and Object. They all have their own Controller.
I created the ObjectPolicy and tried this:
public function view(User $user, Object $object)
{
return $user->organization_id === $object->organization_id;
}
And then I added ->middleware('can:view,object'); to the route.
Unfortunately, it does not work and the Laravel documentation does not provide the information I need.
Can someone help?
Thanks!
EDIT
I have no idea what I'm doing wrong!! I Changed everything but I still get a 403 response.
Here is my code:
Route:
Route::get('organizations/{organization}/objects','ObjectController#index Organization')->middleware('auth:api', 'can:view, organization');
OrganizationPolicy:
public function view(User $user, Organization $organization)
{
return $user->organization_id === $organization->id;
}
ObjectController:
public function indexOrganization(Organization $organization)
{
$objects = $organization->objects;
return ObjectResource::collection($objects);
}
I also added this to my AuthServiceProvider:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
App\Organization::class => App\Policies\OrganizationPolicy::class,
];
EDIT 2 / SOLUTION
The answer from newUserName02 works! The problem was inside the AuthServiceProvider. After I changed the code (see above in Edit) there to:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'App\Organization' => 'App\Policies\OrganizationPolicy',
];
it worked!
The policy method should to match the arguments you are passing to the controller. It looks like you are passing the id of the Organization in the route, but you are trying to check the Object on the policy.
https://laravel.com/docs/5.7/authorization#via-middleware
You can take advantage of Laravel's implicit model binding to inject the Organization into the controller like this:
Route:
Route::get('organizations/{organization}/objects','ObjectController#indexOrganization')->middleware('auth:api', 'can:view,organization');
Policy:
public function view(User $user, Organization $organization)
{
return $user->organization_id === $organization->id;
}
Controller:
public function indexOrganization(Organization $organization)
{
...
}
Notice that {organization} in the route matches organization in the ->middleware() call, which matches $organization in the policy and controller.
I created a new Module named Article using laravel-modules. Some backend routes needed authentication and i added auth middleware and an additional permission view_backend. I am using https://github.com/spatie/laravel-permission package for role-permissions.
the issue is when i try to access the route admin/article/posts it prompts me the login as expected. But after login it show null on __construct() method for Auth::user();
I added web middleware as mentioned on #204 but it did not solve the issue. Can you please guide me to resolve this? My project is on Laravel 5.6 and using the latest version of Laravel-Modules
Route::group(['namespace' => 'Modules\Article\Http\Controllers\Backend', 'as' => 'backend.article.', 'middleware' => ['web', 'auth', 'can:view_backend'], 'prefix' => 'admin/article'], function () {
Route::resource("posts", "PostsController");
});
My project is hosted at Github, https://github.com/nasirkhan/laravel-starter/tree/module
First of all, add Spatie Middleware to your kernel:
protected $routeMiddleware = [
// ...
'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
];
Then in your controller check for permission or roles:
public function __construct(Request $request)
{
$this->middleware(['permission: order.index']);
}
Now you can access to your authenticated with $request->user() like:
public function create(Request $request)
{
if ($request->user()->hasRole('admin')) {
// return view("carmodel.create", ["manufacturers"=>$manufacturers]);
} else {
return view("admin.error", ['code'=>'001','msg'=>'err']);
}
}
According to the docs:
In previous versions of Laravel, you could access session variables or the authenticated user in your controller's constructor. This was never intended to be an explicit feature of the framework. In Laravel 5.3, you can't access the session or authenticated user in your controller's constructor because the middleware has not run yet.
As an alternative, you may define a Closure based middleware directly
in your controller's constructor. Before using this feature, make sure
that your application is running Laravel 5.3.4 or above:
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->projects = Auth::user()->projects;
return $next($request);
});
}
Or you could typehint it:
public function index(Request $request)
{
$projects = $request->user()->projects;
$value = $request->session()->get('key');
}
Docs
I'm using the out of the box authentication in Laravel 5.2 (using artisan command make:auth). It works like a charm.
Thing is, I'd like to restrict login for only active users (deleted_at = NULL).
However when using soft deletes I cannot retrieve other models with a user_id foreign key (allthough the user has been deleted, I still have to access the users information).
What would be a good approach?
An alternative I came up with is to use an "active" boolean column instead of a "deleted_at" date column. This way, I could filter only user with "active"=TRUE and would have no problem whith foreign keys.
In this case, how could I restrict users to login only if "active" is set to TRUE?
Cheers!
The trait Laravel uses to authenticate in controllers has a handleUserWasAuthenticated() method. It checks for another method called authenticated() (which isn’t defined by default) and calls that before fully authenticating a user and letting them access your application. Therefore, if you define this method in your own AuthController, you can do any post-authentication checking such as if the user is active.
class AuthController
{
public function authenticated($request, $user)
{
if (! $user->is_active) {
// Throw exception, display error etc.
}
return redirect()->intended($this->redirectPath());
}
}
You can always pass any extra parameters to the Auth::attempt() method, like so:
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// Redirect to required route/url
}
From what I understand, you can do something similar for the deleted_at field as well.
In Auth\AuthController.php, add authenticated() function
use Auth;
class AuthController extends Controller
{
.
.
.
public function authenticated($request, $user) {
if (! $user->active) {
Auth::logout();
return redirect('login')->withErrors([
$this->loginUsername() => 'Your '.$this->loginUsername().' is not active. Please contact Administrators'
]);
}else {
return redirect()->intended($this->redirectPath());
}
}