Passing two models to the Authorize middleware (also called can) - laravel

I am trying to pass two models to the Authorize middleware, used under the name can.
routes/api.php
Route::middleware('can:reach,profile,photo')->resource('users/{user}/profiles/{profile}/photos', 'PhotoController');
Then I try to retrieve arguments like so:
app/Policies/PhotoPolicy.php
public function reach(User $user, Profile $profile, Photo $photo)
{
return $profile->id === $photo->profile->id;
}
But the middleware is totally ignored. I checked the definition of the middleware and I cannot see why this would not work.
Thanks in advance.

I think you have to add the middleware to the resource, not the opposite. Or, you can create a group.
Example:
Route::group(['middleware' => 'can:reach,profile,photo'], function($router){
$router->resource('users/{user}/profiles/{profile}/photos', 'PhotoController');
});

Related

Laravel using user id in route

So I have a route profile/{user_id}
How do I redirect user to that URL when they click on link?
Here's my controller:
{
function checkid($user_id) {
if (Auth::check())
{
$user_id = Auth::id();
return view('profile', [
'id' => $user_id
]);
}
}
}
Bit confused with the question but Laravel uses ID as default for dependency injection and changing it is easy: just change the routeKey in the model BUT in your instance, you're using the signed in user. So forgot the id!
<?php
namespace App\Http\Controllers;
class RandomController extends Controller {
public function index()
{
return view('profile');//use auth facade in blade.
}
}
In your routes use a middleware to prevent none authenticated users from reaching this method
<?php
Route::group('/loggedin', [
'middleware' => 'auth',
], function() {
Route::get('/profile', 'RandomController#index')->name('profile.index');
});
Now to redirect in your blade file use the route function, don't forget to clear your cache if you've cached your routes!
<h1>Hi! {{Auth::user()->name}}</h1>
View profile
Because I used the name method on the Route I can pass that route name into the route function. Using php artisan route:list will list all your route parameters which is cool because it will also tell you the names and middlewares etc.
if I had a route which required a parameter; the route function accepts an array of params as the second parameter. route('profile.index', ['I_am_a_fake_param' => $user->id,]).
Let me know if you need help with anything else.
You can redirect with the redirect() helper method like this:
return redirect()->url('/profile/' . $user_id);
But I'm not really following your usecase? Why do you want to redirect? Do you always want the user to go to their own profile? Because right now you are using the id from the authenticated user, so the user_id parameter is pretty much useless.

Set username as prefix in URIs Laravel

I want to set prefix in all of the authenticated URIs in Laravel 5.3 app. If I use Route::group(['prefix' => {username}]), then how can I get the username in web.php file.
Assuming you have defined routes like this:
Route::group(['prefix' => '/{user}'], function() {
Route::get('/profile', 'UserController#showProfile')->name('user.profile');
});
You can use Laravel's route-model binding to pass an User instance directly into your routes:
View Profile
Then in the controller , you can easily grab that model instance:
class UserController extends Controller
{
public function showProfile(User $user)
{
return view('user.profile.index', compact('user'));
}
}
Check out the documentation here.
EDIT: By default, Laravel uses the id column when retrieving a given model class. You can change that to any column, in your case username , easily by overriding the getRouteKeyName() method on your User model.
public function getRouteKeyName()
{
return 'username';
}
You can't and you shouldn't hardcore routes with usernames. If you have 100 users, do you plan to create 100 route groups for each user? Assuming each user has 2 routes that's 200 routes.
What you need to do is generate routes with username segment and detect the user on the fly based on the user name.
Route::group(['prefix' => '{username}'], function () {
Route::get('profile', 'UserController#profile');
Route::get('setting', 'UserController#setting');
});
The routes generated would be like
http://example.app/{username}/profile
http://example.app/{username}/setting
So a user with username adam with get the links.
http://example.app/adam/profile
http://example.app/adam/setting
Similarly you can have username based links for all your users and you need to detect them in your controller using route model binding or the standard way.
Edit
For closure based routes you can get the url segment like so
Route::get('{username}/profile', function ($username) {
return 'Username is '.$username;
});
With controllers you get them as the parameters.
public function profile($username)
{
return $username;
}

Laravel default Auth::routes() inside of group prefix

I'm attempting to create a prefix with a variable for "companies" to login to the platform. All users are tied to a company so the normal /login isn't desired. I'd like to use something like `/acme-company-name/login
I am using the default Laravel auth via: php artisan make:auth on a fresh install.
Route::group(['prefix' => '{company}'], function () {
Auth::routes();
});
When I try navigating to /company-name/login I see the following error:
Missing required parameters for [Route: login] [URI: {company}/login].
Looking inside the auto-generated login.blade.php I see the function call route('login') and this seems to be where everything is breaking. I think I need some way to supply a variable to that function or redefine what the "login" route is in some fashion ? I'd rather not have to replace the call Auth::routes() but will certainly do so if that is required to fix this issue.
I should note, i've tried defining the group 'as' => 'company' and changing route('company.login') but then I am told the route company.login is not defined.
Can you try by passing $company variable to the function as well?
Route::group(['prefix' => '{company}'], function ($company) {
Auth::routes();
});
And make sure you pass the company-name when calling the route as it's a required parameter.
In login.blade.php use {{ url("$company/login") }} instead of route('login').
route() helper has more than one parameter ;)
route('login', ['company' => $company])
you need to share your prefix in your views and set route as the following:
Route::group(['prefix' => '{company}'], function ($company) {
view()->share('company', $company); // share $company in views
Auth::routes();
});
now you have $company which is instance of Router and you need access to route prefix value for this you need get current route and get company parameter so you should rewrite your route() helper function as the following:
{{ route('login',['company'=>$company->getCurrentRoute()->__get('company')]) }}
// getCurrentRoute Get the currently dispatched route instance
//__get Dynamically access route parameters
EDIT:
you can write custom helper function as the following:
/**
* Generate the URL to a named route for Company Auth.
*
* #param string $name
* #param Router $company
* #return string
*/
function companyRoute($name,$company)
{
return app('url')->route($name, ['company'=>$company->getCurrentRoute()->__get('company')] ,true);
}
Please check the code that is working fine for me.
Route::group(['prefix' => '/{company}'], function () {
// ensure that auth controllers exists in right place (here it is App\Http\Controllers\Auth\LoginController)
// in LoginController funtion
Auth::routes();
//or you can try using custom routing like this
Route::get('{something}', function($company, $something)
{
var_dump($company, $something);
});
});
If you define this route at the end of the file then the error that you have mentioned we face.
ErrorException in UrlGenerationException.php line 17:
Missing required parameters for [Route: login] [URI: {company}/login]. (View: \resources\views\auth\login.blade.php)
Try with defining this route as first route in web.php and check.
Thanks
Looking inside the auto-generated login.blade.php I see the function call route('login') and this seems to be where everything is breaking.
Yes. By doing
Route::group(['prefix' => '{company}'], function () {
Auth::routes();
});
you are making all auth routes take a company parameter. So, in your views, instead of route('login'), you can do route('login', ['company' => Request::route('company')]). (And the same for all auth routes).
Then probably in your App\Http\Controllers\Auth\LoginController you will need to override the login method accordingly:
public function login(Request $request, $company)
{
// you can copy some behaviour from
// https://github.com/laravel/framework/blob/5.4/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php#L28
}

How to have a route for both authenticated users and non-authenticated users

I have an issue with auth:api middleware!
We have a request that is accessible for both authenticated users and non-authenticated users when I define a route like this for non-authenticated users:
Route::post('{user}/leads', 'UsersController#getContact');
It's ok everything work fine when a guest user requesting this route.
is and I can access user with $request->user();
but if pass token with bearer header and get the user with $request->user() of course it doesn't work! because we didn't use auth:api on this route, and if we do we can't access this route with guest users!
So I can't find a way that we define one route for both authenticated users that if user is authenticated we get $request->user() and none authenticated users can access that route too!
Thanks in advance.
I found a way to do that I just wrote this:
$middleware = ['api'];
if (\Request::header('Authorization'))
$middleware = array_merge(['auth:api']);
Route::group(['prefix' => 'v1', 'namespace' => 'Api', 'middleware' => $middleware], function () {
//routes here
});
In api.php route file and it works fine.
Thanks
This is because Auth uses the default web guard. You have to check the api guard manually:
$user = Auth::user() ?? Auth::guard("api")->user();
Then you don't use any auth middleware. $user will be null if the user is a guest, otherwise it should be set.
The solution I used was to create a new middleware for auth:
public function handle($request, Closure $next, ...$guards)
{
try
{
$this->authenticate($request, $guards);
}
catch(AuthenticationException $ex)
{
}
return $next($request);
}
and in at the BOTTOM of my route I did:
Route::middleware('auth_optional:api')->group(function () {
Route::get('services', [ServiceController::class,'index']);
});
This way if Auth was needed ,it would assign the correct user to request, otherwise it would proceed as guest. I did need to do a $request->user() === null to make sure the user is guest
If you want the routes are visible to only Authenticate users you can put all routes in auth middleware that is default provided by laravel you can put like this:-
enter code here
Route::group(['middleware' => ['auth']], function () {
Route::post('{user}/leads', 'UsersController#getContact');
});
And if you want to show the route both authenticate and non-authenticate user
You can simply put outside the middleware
Lik that:-
Route::match(['get', 'post'], '/cms-page','CmsController#cms');
Hope you understand
I would like to use additional routes both authenticated and non-authenticated users,
But regarding the topic I add one simple way :
On the __constructor function of the Controller add those lines :
$authorizationHeader = \request()->header('Authorization');
if(isset($authorizationHeader)) {
$this->middleware('auth:api');
}
But I don't concentrate this way as best practice, this brokes Single Responsibility Principle.
If you are using Laravel Passport then this way can be more cleaner.
In controller you can directly get user by
$user = $request->user('api');
This will get you the authenticated user and if bearer token is invalid then it will not throw 'unauthenticated' error but result in null user.
Reference: How to authenticate user without auth:api middleware in laravel 5.3?

Auth on create resource causing routing problems

I am trying to require auth before reaching the create resource and have seperated my resource routes accordingly.
Route::resource('posts','PostsController', ['except' => ['store','edit','update','destroy','create']]);
Route::group(['before'=>'auth'], function() {
Route::resource('posts','PostsController', ['only' => ['store','edit','update','destroy','create']]);});
Now for some reason when going to posts/create it redirects me to the show route. The auth is working fine on all other routes, and when create is removed it asks for login upon posting the create, but obviously I would like this section to be off limits regardless.
I would suggest you use controller filters instead.
This simplifies the routing to this:
Route::resource('posts', 'PostsController');
And in your post controller's constructor, you can configure the filter:
public function __construct()
{
$this->beforeFilter('auth', array('except' => array('index', 'show')));
}

Resources