Laravel API with Bearer token - Middleware auth:api - php

I'm trying to create an API with Bearer Token but I can't figure it out:
What does the route::middleware('auth:api') do
Where's the code of route::middleware('auth:api')
So, I have the following code in my Routes\Api.php file:
Route::get('/login', function (Request $request)
{
if(Auth::guard()->attempt(['email' => $request->email, 'password' => $request->password]) == FALSE)
return response()->json(['status' => FALSE]);
$user = Users::select('id', 'name', 'api_token', 'created_at')->where('email', $request->email)->firstOrFail();
return response()->json(['status' => TRUE, 'user' => $user]);
});
Route::middleware('auth:api')->get('/bookings', function (Request $request)
{
return response()->json(['its working!']);
});
I'm able to successfully connect to the route /login and retrieve the api_token. Now this token must be used in the /bookings route in order to authenticate.
I was hopping the middleware('auth:api')verify my CURL headers for the Authorization: Bearer zzzzzzzzz, but its not working.
So basically I need to understand how do I change the code logic behind auth:api or if I should create a new middleware and check for the request headers?

Diclamer
If you need custom code to handle authentication you should create your own middleware and authentication guard and use it instead of the default one that Laravel provides.
Your questions
What does the route::middleware('auth:api') do
It states that the route should implement the middleware "auth" and the middleware group "api".
Where's the code of route::middleware('auth:api')
All middleware in Laravel is defined in app/Http/Kernel.php.
In there you will probably see something like
protected $middlewareGroups = [
....,
'api' => [
'throttle:60,1',
'bindings',
],
];
and
protected $routeMiddleware = [
...,
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
This means that a route using the middleware auth:api implements the api middleware group (in this case the ThrottleRequests and SubstituteBinding middleware) and the auth middleware (Authenticate).
The actual authentication guard used depends on the configuration in your auth.php config file:
'guards' => [
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
In the case above a TokenGuard is used (laravel/framework/src/Illuminate/Auth/TokenGuard.php).
So to answer your question, the code for the auth middleware can be found at
laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php

Related

CSRF Token Mismatch On Axios Post Requests In Laravel

I am working on a project with a React front-end and a Laravel back-end. I am trying to set up my authentication system. I am utilizing SPA authentication using Sanctum. I am successfully utilizing the sanctum/csrf-cookie route, where the XSRF-Token cookie is given. When I then try to follow that up with a login, I get a 419 error, CSRF token mismatch. There is no XSRF-Token. What is interesting is that if I do a get request, as in the 'testing' route below, the XSRF cookie is present. However, when I do a post request, as in posting to the login route, the cookie is not present and I get a 419 error.
I am running this locally right now. The front-end is running at localhost:3000, with the back-end running at localhost:8888. Here are various relevant segments of code.
LoginForm.js
let data = {
email: e.target[0].value,
password: e.target[1].value
}
axios.get('http://localhost:8888/sanctum/csrf-cookie')
.then((res) => {
axios.post('http://localhost:8888/login', data)
.then((res) => {
axios.get('http://localhost:8888/user')
})
})
Kernel.php
protected $middleware = [
\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,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\HandleInertiaRequests::class,
],
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
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,
];
.env
SESSION_DRIVER=cookie
CLIENT_URL=http://localhost:3000
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=http://localhost:3000
Bootstrap.js
axios = require('axios');
axios.defaults.headers.common['Accept'] = 'application/json';
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.withCredentials = true;
Web.php
Route::get('/testing', function () {
return "Testing.";
});
Route::post('/login', function(Request $request) {
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
$id = Auth::id();
$user = User::find($id);
return $user;
}
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
]);
});
Sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,localhost:8888,
Sanctum::currentApplicationUrlWithPort()
))),
Cors.php
'paths' => [
'api/*',
'sanctum/csrf-cookie',
'login',
'logout',
'register',
'user/password',
'forgot-password',
'reset-password',
'user/profile-information',
'email/verification-notification',
'testing',
'user',
'checkAuth'
],
'allowed_methods' => ['*'],
'allowed_origins' => [env('CLIENT_URL')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
Let's review one by one what is needed to apply SPA authentication using sanctum.
First, they must share same top-level domains, however each could be hosted on subdomains, on you case you are using localhost, so this should be fine.
Next, you should configure which domains your SPA will be making requests from. SANCTUM_STATEFUL_DOMAINS. If you are accessing your application via a URL that includes a port (127.0.0.1:8000), you should ensure that you include the port number with the domain. Here, it seems you are missing the port.
Next, you should add middleware as mentioned in https://laravel.com/docs/9.x/sanctum#sanctum-middleware which seems you have already done it.
Next fix cors issue: First, you need to set supports_credentials to be true on config/cors.php. Next, you should enable the withCredentials option on your application's global axios instance. axios.defaults.withCredentials = true;
Finally, you should ensure your application's session cookie domain configuration supports any subdomain of your root domain. SESSION_DOMAIN, looks like you have already set localhost here.
Overall, it seems you need to fix to things:
you need to add port if you are accessing it from port.
you need to set supports_credentials to true on config/cors.php
For more detail, you can follow: https://laravel.com/docs/9.x/sanctum#spa-authentication
As per the Laravel Sanctum documentation :
Additionally, you should ensure that you send the Accept: application/json header with your request.
Since we cannot find misconfigurations in your setup, I will recommend adding a custom middleware in your api group that automatically adds the application/json header :
/* app/Http/Middleware/AjaxHeader.php */
namespace App\Http\Middleware;
use Closure;
class AjaxHeader
{
/**
* Handle incoming request
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$request->headers->add([
'accept' => 'application/json',
]);
return $next($request);
}
}
And add it to your 'api' middlewareGroups :
/* app/Htpp/Kernel.php */
// ...
protected $middlewareGroups = [
// ...
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\AjaxHeader::class,
],
];
// ...

Custom multi-auth guard logging & routing

I'm developing a Laravel 6 app with 4 different users(with a different table for each). For this, I am using multi auth guard and created my first guard following this tutorial online (https://pusher.com/tutorials/multiple-authentication-guards-laravel).
Here is my custom guard "student"
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'student' => [
'driver' => 'session',
'provider' => 'students',
],
],
And Providers
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'students' => [
'driver' => 'eloquent',
'model' => App\Models\Student::class,
],
],
I have a custom login controller using a custom auth guard named 'student'
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Auth;
use Student;
class LoginController extends Controller
{
protected $redirectTo = '/dashboard';
public function __construct()
{
$this->middleware('guest')->except('destroy');
$this->middleware('student')->except('destroy');
}
public function studentLogin(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|min:5'
]);
if (Auth::guard('student')->attempt(['email' => $request->email, 'password' => $request->password], $request->get('remember'))) {
return redirect()->intended('admin/home')->with('smessage', 'Logged in successfully...!');
}
return back()->withInput($request->only('email', 'remember'));
}...
}
Now, Whenever I try to log in, It seems like I am logged in and redirected to the URL defined in the middleware/RedirectIfAuthenticated.php but this error pops up "Trying to get property of non-object" which is from the common header template where no data of logged user are available on auth()->user().
If I use 'middleware'=>'auth:student', I can access the route as student and have the logged user data via auth()->user(). same for admin using 'middleware'=>'auth' but passing both auth and auth:student as an array I can't get the logged user data via auth()->user() for both user.
Here are my routes:
Route::get('/', function () {
return view('welcome');
});
Route::get('/login', 'LoginController#create')->name('login');
Route::post('login', 'LoginController#store');
Route::get('/login/student', 'LoginController#create')->name('studentlogin');
Route::post('/login/student', 'LoginController#studentLogin');
Route::get('/logout', 'LoginController#destroy')->name('logout');
Route::group(['prefix'=>'admin', 'namespace' => 'Dashboard', 'middleware'=> 'auth' ], function () {
Route::get('home', 'DashboardController#display')->name('admin.home');
Route::get('users', 'UserlistController#display')->name('admin.userlist');
Route::post('users', 'UserlistController#store')->name('admin.create');
Route::delete('users/{id}', 'UserlistController#destroy')->name('admin.deleteuser');
});
Route::group(['prefix'=>'student', 'namespace' => 'Students', 'middleware'=> 'auth:student' ], function () {
Route::get('all', 'StudentsController#display')->name('student.all');
Route::delete('all/{id}', 'StudentsController#destroy')->name('student.delete');
Route::post('all', 'StudentsController#store')->name('student.store');
});
Couldn't find any solutions from other related topics on StackOverflow. Please do correct me if I have any mistakes somewhere or let me know if you want to inspect any other files for the code.
Thanks in advance.
You don't have any data on auth()->user() because you're querying the default guard which is web.
If you want to retrieve the logged user through student guard, you have to do auth()->guard('student')->user();
Also remember to pass everytime your guard to auth and middlewares that uses auth.
I.e.: if you need to make some routes restricted only to authenticated users you will need to do:
Route::group(['middleware' => 'auth:student'], function() {
// Your restricted routes
})
Omitting :student will use the default web guard as always
Note that if you need to restrict your routes to a group of users belonging to different guards you can pass those guards to auth:
Route::group(['middleware' => 'auth:web,student'], function() {
// Your restricted routes
}];
In this way Laravel will check for both web and student guards and will automatically set the one that belongs to the authenticated user. You will also be able to retrieve your user only by doing auth()->user() forgetting to pass the right guard

Laravel Passport API call always return Unauthenticated

So I have setup my API with Passport and tried to make GET request for almost a week now but still getting the response bellow :
{
"message": "Unauthenticated."
}
Below are my configuration :
Auth.php
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'token',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
//'hash' => true,
],
],
AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
//
Passport::routes();
Passport::tokensExpireIn(Carbon::now()->addDays(7));
Passport::refreshTokensExpireIn(Carbon::now()->addDays(14));
}
RouteServiceProvider
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('auth:api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
Controller : Token request function using user credentials as per laravel doc
public function callback(Request $request)
{
$http = new Client();
$token_url=url('oauth/token');
$response = $http->post($token_url, [
'form_params' => [
'grant_type' => 'password',
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'username'=>'my-username',
'password'=>'my-password',
'scope' =>'*',
],
]);
return json_decode((string) $response->getBody(), true);
}
Which returns an access_token that I use in my request in my request . I tried all the solution listed below and none of them worked :
.htaccess
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Passport.php line 167
public static function tokensExpireIn(DateTimeInterface $date = null)
{
if (is_null($date)) {
return static::$tokensExpireAt
? Carbon::now()->diff(static::$tokensExpireAt)
: new DateInterval('P1Y');
}
static::$tokensExpireAt = $date;
return new static;
}
Please help , I'm desperate now :)
The problem is how you authenticate and which token you are using.
There are 2 ways to generate token
Users login and you use create token to get the token if user is authenticated.
Generate a client and use PRE built routes by passport to get token
Now in the API routes you have to tell in your method how you are authenticating for example
// Below one is for authenticating Client token
Route::get('/test', 'Api\TestController#index')->middleware('client_credentials');
// Below one is for the User token
Route::get('/test', 'Api\TestController#index')->middleware('auth:api');
And Remember if you are using client authentication, you have to add the below line in routemiddleware in App/http/kernel.php
'client_credentials' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class
I hope that solves issues
Try creating a new password grant client with:
php artisan passport:client --password
You'll get an output like:
What should we name the password grant client? [Laravel Password Grant Client]:
>
Password grant client created successfully.
Client ID: 4
Client secret: WlRYEbA5lt5esbi0MuFyJPzPDmHDGsk3iC5QGw7d
Use those credentials to fill your client id and secret. Standard client credentials created through the Vue component interface do not work for password grants.

Laravel send password reset link to a separate authentication guard

In Laravel 5.4, is there a way to send the password reset link to a separate authentication guard instead of the default one. I am using the default PasswordResetController which does the job in this way
public function company(Request $request)
{
$this->validate(request(), [
'email' => 'required|email',
]);
$response = Password::sendResetLink([
'email' => $request->email
]);
//overridden if condition
if($response == "passwords.sent")
{
return back()->with('message','Password reset link has been sent, please check your email');
}
return back()->with('message', 'No such email address in our records, try again');
}
The sendResetLink() method checks and sends the reset link to the default guard, but I am defining a new guard in auth.php called web
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'companies',
],
sendResetLink method is like this
public function sendResetLink(array $credentials)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
if (is_null($user)) {
return static::INVALID_USER;
}
Any way for this method to check in a separate table or use a separate auth guard?
Here is my method of how to send password reset link to a guard which is a part of multi-auth system.
I am going to assume you have properly set your new guard in config/auth.php
which may look like below code:
I use the word admin for the new guard's name for better understanding.
'guards' => [
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
]
'providers' => [
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
]
'passwords' => [
'admins' => [
'provider' => 'admins',
'table' => 'password_resets',
'expire' => 15,
],
]
You will have to create new controllers(AdminForgotPasswordController and AdminResetPasswordController) for your new guard
They both use the Password facade and AdminResetPasswordController uses Auth facade as well.
Modify the construct function according to your new guard.
So add this to both controllers, because we have specific type of guest users.
public function __construct()
{
$this->middleware('guest:admin');
}
Now we need to tell AdminResetPasswordController to use the proper guard for authenticating.
So add this method to the controller
protected function guard()
{
return Auth::guard('admin');
}
Now add this piece of code to both controllers.
protected function broker()
{
return Password::broker('admins'); //set password broker name according to guard which you have set in config/auth.php
}
Notice: It's not the only step for implementing password reset for guard you will have to take other steps like creating new routes, notifications, forms and corresponding views.
In your auth.php configuration file, you may configure multiple "guards", which may be used to define authentication behavior for multiple user tables. You can customize the included ResetPasswordController to use the guard of your choice by overriding the guard method on the controller. This method should return a guard instance:
protected function guard()
{
return Auth::guard('guard-name');
}
The magic lies, in using the broker - the PasswordBroker for your custom guard. But, you make sure to set up multiple password brokers in your auth.php configuration file.
protected function broker()
{
return Password::broker('name');
}
One simple way could be
$response = Password::broker('guard-name')->sendResetLink([
'email' => $request->email
]);
also don't miss reset function
$response = Password::broker('guard-name')->reset([
'email' => $request->email
],function(){// save changes})

How to use token authentication in laravel web page

I am trying to use JWT for laravel web page instead of session. so I made some changes.
Installed jwt-auth and configure
Then changed default guard as api in config/auth.php
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
...
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
Now I am getting error
(1/1) FatalErrorException Call to undefined method
Illuminate\Auth\TokenGuard::attempt() in AuthenticatesUsers.php (line
75)
How to fix this and start token authentication for laravel web page(blades not API).
I'm also using jwt protecting our api. You should change your config like below:
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
...
'api' => [
'driver' => 'jwt', // KEY POINT!!!
'provider' => 'users',
],
],
Make sure the jwt library installed correctly:
Tymon\JWTAuth\Providers\LaravelServiceProvider::class is added in your config/app.php.
Your user model implements JWTSubject interface if you use eloquent model in your provider.
I found the solution here : https://github.com/tymondesigns/jwt-auth/issues/860
In /routes/api.php - added a few basic authentication routes
Route::post('login', 'Auth\LoginController#login');
Route::get('/user', function (Request $request) {
$user = $request->user();
return dd($user);
})->middleware('auth:api');
In /app/http/Controller/auth/LoginController.php
and then override methods in login contoller
public function login(Request $request)
{
$credentials = $request->only(["email","password"]);
if ($token = $this->guard()->attempt($credentials)) {
return $this->sendLoginResponse($request, $token);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
protected function sendLoginResponse(Request $request, $token)
{
$this->clearLoginAttempts($request);
return $this->authenticated($request, $this->guard()->user(), $token);
}
protected function authenticated(Request $request, $user, $token)
{
setcookie("jwt_token", $token);
return redirect('/');
return response()->json([
'token' => $token,
]);
}
protected function sendFailedLoginResponse(Request $request)
{
return response()->json([
'message' => "not found",
], 401);
}
Adding middleware AddToken
public function handle($request, Closure $next)
{
$token = isset($_COOKIE["jwt_token"])?$_COOKIE["jwt_token"]:"";
//$request['token'] = $token;//this is working
$request->headers->set("Authorization", "Bearer $token");//this is working
$response = $next($request);
//$response->header('header name', 'header value');
return $response;
}
Register middleware in Kernel.php
protected $middleware = [
....
\App\Http\Middleware\AddToken::class,
];
I think you can try this :
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
EDIT
You can find some help from the step by step example. In this example you need to focus on how to configure and use that token base authentication.
Hope this help you well.
Please refer this link. If you are using api as default then laravel authentication will throw an error.
Laravel uses default Session based authentication out of the box with the default scaffolding users-view-controller that you already have. You have additional means of adding your own custom guard in the doc, so you can make use of the guard as needed.
Therefore as #KevinPatel suggested, revert back to the default configuration, then in your route: group the route you want to be under JWT authentication, add the JWTMiddleware, in this case you have to update the controller responsible for your authentication to use the JWTAuth instead of the default auth.
You should check this answer if you need to understand it better check this answer on Laracasts
One recommended way to incorporate the JWTAuth is to go for Dingo API (of course you are not building api, but) because Dingo already added some flesh to the authentication and other routes management - so things are pretty easy to use and configure

Categories