I've started work on an existing Laravel 5.2 project. I'm needing to build some basic API requests for the front-end of the application to talk with the database. These routes need to be authenticated by the user's session.
I've tried setting up middleware using the auth:api driver, and in ../config/auth.php setting the api['driver'] to 'session'. However, I keep getting 302 redirected to the login page even though the user is authenticated with all permissions and roles.
Can someone recommend some reading or other solution ideas of how to achieve API authentication based on the user session?
from routes/api.php:
Route::group(["middleware" => ["auth:api"]], function () {
// results in 302 redirect to /login
Route::get('test', function(){
return "TEST";
});
});
from config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'session',
'provider' => 'users',
],
app/Http/Kernel.php
<?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 = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\NoCache::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\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
/**
* 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,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'role' => \Zizaco\Entrust\Middleware\EntrustRole::class,
'permission' => \Zizaco\Entrust\Middleware\EntrustPermission::class,
'ability' => \Zizaco\Entrust\Middleware\EntrustAbility::class,
'dashboard' => \App\Http\Middleware\dashboardMiddleware::class,
'system' => \App\Http\Middleware\systemMiddleware::class
];
}
This is be old post but from my experience and what I've learned we don't use session based mechanism authentication for API request from third party app b/c session based authentication is like if you have not been authenticated yet (like not yet logged in ), it will redirect you to login page (like the author of this question said "getting 302 redirected to the login page").
But API request is XMLHttpRequest (ajax request), not browser request, if so, how could it redirect you to login page?
(check network tab in Developer tool and you will see, the response from this request is 302 and the response body is the login html page).
Therefore, we should only use token based mechanism for API authentication, which will return message like "you're not authenticated" as json response, which can be consumed by your 3rd party app.
For laravel specifically, you can use session provider for api guard, but you need to go to Kernel.php
and add
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class
,
which are core of laravel session based authentication,
to your api middleware group. Otherwise, even if you're already logged in, you'll still be unauthenticated
because laravel api routes by default don't have necessary middlewares for session based authentication.
protected $middlewareGroups = [
'web' => [
// ...
],
'api' => [
\App\Http\Middleware\EncryptCookies::class, // <------- ADD THIS
\Illuminate\Session\Middleware\StartSession::class, // <------ ADD THIS
// ...
],
];
Ref:
https://laracasts.com/discuss/channels/laravel/protecting-api-routes-with-session-guard
https://laravel.io/forum/02-09-2016-52-ajax-auth-not-picking-up-session
The issue is that you are not passing any authentication headers in the request, therefore, it is obvious that it is redirecting you to the login screen, even when you hit the API using postman.
To prove this, try removing the auth middleware from the following line Route::group(["middleware" => ["auth:api"]], ... in your routes file, and you should be able to get TEST as a response if you hit your test route. Also, please do have in mind that our api automatically binds the api/ prefix to all api routes. Therefore, you should make a request to /api/test and you should get TEST back.
If you want to include auth to your api, please read Laravel's Passport docs. If you want something much simpler, start with the onceBasic Auth middleware in the docs.
I hope this points you in the right way!
Let me know if you have any other questions.
Cheers!
Related
I just started working in Laravel and ran into an issue with Sanctum Authentication. I have a website setup using blades that shows the front-end site and forms. I use web.php to route the front-end pages and for authentication for an account section. I am using jQuery to send requests to the API (api.php) for registration, login, and verification. The API sends over the credentials and creates a Bearer token which I store in local storage for later API requests. After login I redirect the user to the /account page which is guarded with auth. The issue is that web.php doesn't find the authentication token set from the api and redirects the user back to the login page. Here is some code showing how the site is setup, please let me know if I missed something in my files:
api.php - this is the api route to create the login token. This is in the ajax request.
Route::post('/login', [AuthController::class, 'login']);
web.php - this is the account view that needs to be secure and get the api token before displaying (currently not getting the token correctly)
Route::middleware('auth')->group(function(){
Route::get('/account',[UserController::class, 'index']);
});
AuthController.php - this is the logic for creating the token and sending it back via the api request. I store this in local storage for other api calls
public function login(Request $request)
{
if (!Auth::attempt($request->only('email', 'password'))) {
return response()->json([
'message' => 'Invalid login details'
], 401);
}
$user = User::where('email', $request['email'])->firstOrFail();
$user->tokens()->delete();
$token = $user->createToken('token')->plainTextToken;
return response()->json([
'result' => 'success',
'token' => $token,
'token_type' => 'Bearer',
]);
}
UserController.php - this is the basic account view being returned after the token has been authenticated
public function index() {
return view('account/dashboard', ['title' => 'Account Dashboard | Exclusive Member | Mint Auction']);
}
auth.php - this shows how my guards are setup inside of the Auth config file
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'sanctum',
'provider' => 'users',
'hash' => false,
],
],
Figured it out...
I was sending the wrong CRSF Token in my ajax request so it kept coming back as mismatched.
I also adjusted the following code:
web.php - web routes (made auth:web)
Route::middleware('auth:web')->group(function(){
Route::get('/account',[UserController::class, 'index']);
});
Kernel.php - I added middleware for the api middlewaregroup
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
'throttle:api',
],
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 recently updated Laravel and dependencies from 5.7 to 6.0 and am having a problem with a user logging into Laravel and the Session persisting throughout the application.
I can log a user in manually using
Auth()->loginUsingId(theID) in a controller where I'm trying to access User data but a user is not logged in throughout the App. I'm wondering if my Sessions are totally broken or if it has something to do with Middleware. I'm using the standard file system method via Laravel for Session storage.
I don't have my routes in Middleware as I want my API routes open and handle permissions within controllers.
This could have to do with some config issue but I just can't seem to find it. I have also tried a fresh download of Laravel 6 and added my code to it but the problem remains.
Didn't post much code because idk what is most relevant but can if it is useful.
kernel.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\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::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' => [
'throttle:60,1',
'bindings',
],
];
/**
* 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,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* #var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
I had the same problem thinking it had something to do with Laravel 6. For me it turned out to be that I use a non-standard primary key for my users table, and needed to declare that on the User model.
For session troubleshooting, keep in mind that changes to config/session.php wipe the current session. I found it helpful to run php artisan config:clear and php artisan cache:clear between tests.
So what I eventually discovered is this:
I have an api.php file for all of my api routes. For the most part, I have separate files for each domain-specific route (e.g., api/user.php), and I include these files in the master api.php file.
One of these domain route patterns was "api/settings". When I moved my settings routes into their own file and included it in api.php, that's what broke everything. I have no idea why, but placing the individual routes back in api.php (and not with a separate file include) solved the problem. I imagine it is some sort of routing conflict with Laravel. No idea. That one took me awhile.
I'm fixing a bug in a system. A blocked user can access pages that should be restricted for them if they know the URL to go the page. I'm using Laravel and PHP 7.1, I'm still new, but know how to create a basic middleware.
IN kernel,
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' =>\Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'admin' => \App\Http\Middleware\Admin::class,
];
In controller, well i want to put something as parameter to block user only with id=3 (my code is totally wrong, i know)
public function __construct(BlockRestricType::$ID)
{
if('ID'= 3)
$this->middleware('auth');
}
The authentication middleware in laravel is auth
You can use for authenticated logged user to handle what you want!
Route::group(['middleware' => ['auth']], function () {
// put the routes here
Route::get('dashboard','HomeController#index');
});
or
Route::get('dashboard','HomeController#index')->middleware('auth')
i am using laravel 5.2 whit the predefined middelwares 'api' and 'web'. in the kernel file its stated that web would use quite some while api only checks throttle:
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,
],
'api' => [
'throttle:60,1',
],
];
in my routes i grouped the api routes and applied only 'api'
Route::group(array('prefix' => 'api', 'middleware' => ['api']), function(){
Route::post('test', 'TestController#testfunction');
}); // End of api Group
but when i send a post to /api/test it throws the crsf token mismatch. if i put the 'api/test' to the exception in the VerifyCsrftoken.php it works again. i cant figure out why the crsf token gets checked if not defined as a middelware for the route. Does anyone has an idea why?
CSRF is a "middleware" registered globally in App\Http\Kernel.php. Removing it will default to no CSRF protection (Laravel4 behavior).
To enable it in a route:
Create a short-hand key in your
app/Providers/RouteServiceProvider.php:
protected $middleware = [
// ....
'csrf' => 'Illuminate\Foundation\Http\Middleware\VerifyCsrfToken',
];
You can now enable it to any Route:
$router->post('url', ['middleware' => 'csrf', function() {
...
}]);
Not really elegant but may be a spot on on your question, try it.