I have seen in documentation that for secure routes Laravel provides function secure_url over just url and it provide redirect method
redirect()->secure('/path');
Now this seem very confusing as to switch protocol we need change code. And worst part is we may need to change pre-built Auth controllers in laravel.
My question is if there is any better way to switch to https from configurations. And if not possible than why? If required we can fork laravel and add this feature.
The solution I used for this was to create a secure middleware. It checks to see if the request is secure. If not redirect it to a secure one.
Firstly create a middleware in your middleware directory
<?php namespace App\Http\Middleware;
use Closure;
class SecureMiddleware {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (!$request->secure()) {
return redirect()->secure($request->getRequestUri());
}
return $next($request);
}
}
Register it in your Kernel.php file
protected $routeMiddleware = [
'auth' => 'App\Http\Middleware\Authenticate',
'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
'securage' => 'App\Http\Middleware\SecureMiddleware',
];
Then use a route group to specify where you want the secure routes.
Route::group(['middleware' => 'securage'], function() {
/* Routes here */
});
Related
I am using a middleware to check for the existence of a login token from another site. If the login token is present and the user is not already logged in, I would like to use the token to log the user in and send them to their intended page. If they are already logged in, I would like it to do nothing.
As suggested, should this be a ServiceProvider instead?
Here is my middleware:
<?php
namespace App\Http\Middleware;
use Session;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Auth\LoginController;
class CheckRepLoginToken
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next){
$loginToken = $request->repLoginToken;
$goto = '/'.$request->path();
if(isset($loginToken) && Auth::guest()){
(new LoginController)->login($loginToken,$goto);
}
return $next($request);
}
}
The problem is that I need this to all run prior to the $middlewareGroups and $routeMiddleware so the user IS NOT sent to the login screen if Auth::guest() is true but the token is present.
I currently have the middleware in the protected $middleware section of the Kernel and everyone seems to be a "guest" whether or not they are logged in.
This is the kernel file:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* #var array
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\CheckRepLoginToken::class,
// 'checkStatus' => \App\Http\Middleware\CheckStatus::class,
];
/**
* The application's route middleware groups.
*
* #var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}
How can I achieve my desired result without messing with the current authentication?
At first, middleware seems like the right tool for a one-time login token feature, but the implementation can be tricky without an understanding of Laravel's request pipeline. At the end of this answer, we'll look at a simple alternative that uses custom authentication functionality instead.
For traditional web requests from a browser, Laravel's authentication services depend on the existence of a session identified by a cookie. The question's CheckRepLoginToken middleware is declared in the global middleware array in Kernel.php, and these handlers execute before route middleware which include the StartSession middleware in the 'web' group.
Since the StartSession middleware initializes session state for a request, authentication context for a global CheckRepLoginToken middleware is not available yet. Calling Auth::guest() from the global middleware will always return true for the configuration shown in the question. I'm not sure what the LoginController::login() method does in your particular project, but I imagine that the auth state set-up by attempting to invoke that method from global middleware may disappear when the standard session and auth middleware run afterward.
Depending on what your LoginController::login() method does, it may be enough to move the declaration for the CheckRepLoginToken middleware below StartSession in the 'web' group. As an aside, some might consider it bad practice to instantiate the controller to call the method directly. We can achieve a similar outcome without much code:
public function handle(Request $request, Closure $next)
{
if ($request->has('repLoginToken') && Auth::guest()) {
$user = // ...try to fetch a user with $request->repLoginToken...
if ($user !== null) {
Auth::login($user);
}
}
return $next($request);
}
A more complete solution takes advantage of Laravel's pluggable authentication system. We can wrap Laravel's standard authentication guard with a custom implementation that handles the token.
First, we'll update config/auth.php to switch the default 'web' guard to use a custom driver that we'll implement below. We rename the original 'web' guard to 'session' so that we can refer to it later.
'guards' => [
'web' => [
'driver' => 'rep-token',
],
'session' => [
'driver' => 'session',
'provider' => 'users',
]
],
Laravel's AuthManager includes a helper method—viaRequest()—that simplifies the creation of a Guard that authenticates a user with data from the request context without the need to fully-implement Illuminate\Contracts\Auth\Guard. We bind our custom guard in the boot() method in AuthServiceProvider.php:
public function boot()
{
Auth::viaRequest('rep-token', function ($request) {
$baseGuard = Auth::guard('session');
if ($request->has('repLoginToken') && $baseGuard->guest()) {
$user = // ...try to fetch a user with $request->repLoginToken...
if ($user !== null) {
$baseGuard->login($user);
}
}
return $baseGuard->user();
});
}
As we can see, this wrapper reuses the functionality of Laravel's standard session-based authentication and handles the special case for the presence of repLoginToken. It does not need any additional middleware.
Since this post is a public, I feel obligated to emphasize a point from Mtxz's answer. Exercise great caution in the design and implementation of a third-party authentication scheme. In general, anyone who obtains a valid token—including the third-party—has complete access to a user's account. The simplicity of the authentication flow described in this question suggests vulnerabilities that may not be acceptable for many applications.
try to name your route and do like this here:
Route::get('/login/{loginToken}', 'LoginController#login')->name('login.route');
if (isset($loginToken) && Auth::guest()) {
return redirect()->route('login.route', [
'token' => $loginToken
])
}
Actually, for Laravel to know if the user is logged in or not, the request needs to passe the auth middleware. So your custom middleware would need to be triggered after the auth one.
So if your middleware need to know if the user is logged in or not, the auth middleware have to be passed first (also meaning it'll only work for routes or route groups under the auth middleware - as I see the auth middleware is not in your default app middleware stack).
And if the auth middleware redirects the user, then yours is never called.
override the Auth middleware
as your business is "auth related", you could easily override the authenticate middleware you are using. Copy or extend the vendor class, replace the auth middleware class with yours in the Kernel, and add your custom business after the default middleware business identified or not the user as logged in.
I guess you could also go by creating a custom authentication Guard and use it instead of the default one, but I think the custom auth middleware is faster.
I don't know much about your custom auth business, but be careful about tokens in URL (that can be sent by mail, or saved elsewhere) that authenticate a user: those tokens should expire after a delay and after use. Also, you should prevent brute-forcing of this parameter.
I have installed this github Laravel 5 Saml2 package:
https://github.com/aacotroneo/laravel-saml2
I've got the login working, authentication is happening and the data is being passed back correctly. I have a LoginListener that is successfully catching the correct user information and returning a valid user from the database. However when I attempt to use Auth::login it does not persist outside of the listener handle function and will go into an endless loop between the SAML authentication and the listener.
Here is my Listener:
namespace App\Listeners;
use \Aacotroneo\Saml2\Events\Saml2LoginEvent;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Auth;
use Illuminate\Session\Middleware\StartSession;
use App\Http\Controllers\HomeController;
class LoginListener
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
}
/**
* Handle the event.
*
* #param Saml2LoginEvent $event
* #return void
*/
public function handle(Saml2LoginEvent $event)
{
$user = $event->getSaml2User();
$userData = [
'id' => $user->getUserId(),
'attributes' => $user->getAttributes(),
'assertion' => $user->getRawSamlAssertion()
];
//check if email already exists and fetch user
$user = \App\Models\User::where('username', $userData['attributes']['NameID'][0])->first();
Auth::guard('web')->login($user);
Session::save();
}
}
I have added 'web' to my routesMiddleware setting in the saml2-settings file as is suggested in multiple places with no effect. Does someone have a working example of this somewhere that I can dig through to see what I am doing wrong?
The solution to this turned out to be in the middleware. I have several custom middleware files setup and they were interferring with the web middleware begin the solution to the issue.
To solve this I created a custom middlewareGroup in the Kernel called listener and removed all of the extra middleware that I had added to web:
'listener' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
],
Then in the saml2_settings config file I changed the routesMiddleware variable to listener:
'routesMiddleware' => ['listener'],
Hopefully this helps someone else in the troubleshooting process.
I'm pretty out of any ideas right now.
The case is: I have a route for an API-endpoint (working fine, responding JSON etc.).
If I now apply the built-in 'auth' middleware to the route, I'm redirected ALWAYS to the /home route. Looks like I'm doing sth. wrong with the auth? I think wrong, because:
This curious redirect also kicks in, if I don't use 'auth' but a custom middleware, that contains NOTHING but
public function handle($request, Closure $next)
{
print "WTF";
throw new AuthenticationException('Unauthenticated.');
}
[the print and the Exception are never thrown! I'm landing again without errors at /home.]
Not all middleware is producing this error: For example, 'auth.basic' and 'web' are just working fine with this route.
I also applied 'web' and my own middleware both to the route according to some results I found, that said that using 'web' solved similar problems for them, but guess what, nothing changed.
TL:DR: If I use my own middleware or 'auth' I'm getting redirected, BEFORE the middleware itself is executed.
Update:
After fiddling around with the code and the great tipp from mnies, I found this very curious Bug:
If I just uncomment AuthenticationException, suddenly my code is working as intended. It may be that loading the Exception-Class calls RedirectIfAuthenticated Middleware?! - which is definitely called!
The easy solution is now, using a different Exception for my custom middleware, but the Problem is that the default 'auth'-MW is also using this Exception and so causing the same Bug.
Remember: I am not using other middleware than just this own one, the bug seems really loading the Exception, like WTF?
So I still need help why this is happening!
Bug: using
throw new AuthenticationException('Unauthenticated.', []);
causes 'guest'-MW (RedirectIfAuthenticated) being called instead of intended MW-stack! (nothing of the original MW-stack is being executed, no matter the order.
Update 2:
It seems that RedirectIfAuthenticated is thrown only because I got redirected before to the /login route (and from there as described to /home through it), but that doesn't change the issue that this random redirect occurs.
[I'm trying atm to reproduce this Bug in a fresh installation]
Update 3: I was not able to reproduce the bug in a fresh installation with Laravel 5.4.19.... Trying to compare both installations now. D:
Using Laravel 5.3.30.
Some of my code for context:
Route:
Route::get('/admin/user/get', ['middleware' => ['simpleauth', 'web'], 'uses' => 'UserEditAdmin\Controllers\Controller#get']);
Custom middleware:
class SimpleAuth
{
public function __construct(){}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
* #throws AuthenticationException
*/
public function handle($request, Closure $next)
{
print "WTF";
throw new AuthenticationException('Unauthenticated.');
}
}
'web' from Kernel.php:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
Have a look at your \App\Http\Kernel.php. It looks like you're always calling the \App\Http\Middleware\RedirectIfAuthenticated middleware (aliased to guest). It you want to debug, you could just throw an exception in that middleware to get a stacktrace of what is called when.
I'm new to Laravel and as said in the title I can't find the Authenticate Middleware. I know it should be in app/http/middleware/Authenticate, as it was in previous projects, but it's not there. The ones that are there are: Encrypt.. , RedirectifAuth.. and VerifyCsrf...
I hope you can help me locate it.
Unless you have a solid understanding of what you're doing it is not recommended that you move or overwrite files within the vendor folder.
That being said, you CAN overwrite the Authenticate.php file and just modify your Kernel.php file:
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class, <--Change this Directory
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
...
];
To:
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class, <--- There you go
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
...
];
Be sure to copy and paste the code from the Authenticate file in the vendor folder into the Authenticate file in your app\http\middleware directory to resume the same functionality.
Again it's not recommended you do this unless you have a solid understanding of what you're doing and how it all works.
Laravel 5.2
'app/http/middleware/Authenticate.php'
Laravel 5.3
'app/exceptions/handler.php'
I'm building an international website, therefore I managed to have URLs looking like /{language}/{other_stuff} thank to some manipulation in RouteServiceProvider
/**
* Define the routes for the application.
*
* #param \Illuminate\Routing\Router $router
* #return void
*/
public function map(Router $router, Request $request)
{
$locale = $request->segment(1);
$this->app->setLocale($locale);
/**
* Internationalization routing system
*/
$router->group(['namespace' => $this->namespace, 'prefix' => $locale], function($router) use ($locale) {
if ($locale == 'en') require app_path('Http/routes_en.php');
elseif ($locale == 'el') require app_path('Http/routes_el.php');
});
}
Works like a charm. Every language will have his own route file, it's a choice.
Let's say we go to /en/ and you're an admin, I created another namespace within Http/route_en.php to focus on the admin section :
Route::group(['namespace' => 'Admin', 'prefix' => 'admin'], function() {
Route::controller('', 'DashboardController');
Route::controller('brands', 'BrandsController');
Route::controller('contents', 'ContentsController');
Route::controller('downloads', 'DownloadsController');
Route::controller('news', 'NewsController');
Route::controller('products', 'ProductsController');
Route::controller('settings', 'SettingsController');
Route::controller('users', 'UsersController');
});
So now I should access easily sections such as /en/admin/brands but it fails. I generate all my links dynamically thanks to the HTML class
{!! HTML::linkAction('Admin\BrandsController#getIndex', 'Brands') !!}
The generation works fine when I go to /en/admin which means Admin\BrandsController#getIndex is detected by this package, but when you click on it
Sorry, the page you are looking for could not be found.
I tested some stuff and when I just simply set the route outside group() it works fine.
Route::controller('admin/brands', 'Admin\BrandsController');
What am I missing here ? Shouldn't the HTML class and the routing system agree with each others ? Is there any mistake I made ? Maybe there's an issue ?
EDIT : I opened an issue for this problem on GitHub
So nobody tried to help me.
After a few days, an issue and many tests I understood the problem by myself : you have to put the DashboardController route at the end otherwise the routing system will take it first and ignore the other ones.
/**
* Admin
*/
Route::group(['namespace' => 'Admin', 'prefix' => 'admin', 'middleware' => 'is.admin'], function() {
Route::controller('news', 'NewsController');
Route::controller('brands', 'BrandsController');
Route::controller('products', 'ProductsController');
Route::controller('users', 'UsersController');
Route::controller('downloads', 'DownloadsController');
Route::controller('settings', 'SettingsController');
Route::controller('contents', 'ContentsController');
Route::controller('', 'DashboardController');
});
NOTE : Everything will seem alright in the route listing, and even in the HTML/Form packages, but it's not.
I let it here for anybody that would have similar problems.