I have a Laravel API (actually a Lumen API) serving a VueJS front-end. The Vue app allows users to login to Google. The Google token is then sent back to the Lumen API, which verifies the token with Google, then verifies that the email address is a valid user. Then it generates a token, stores that in the DB with the user and returns the user object.
I'm not using Passport or jwt-auth or anything like that. So now, how do I go about using the default Auth middleware to verify the token header that the (now logged in) user will be returning with every request? (i.e. is the token in the DB and is it expired?). And is there a way to do this more efficiently, so Laravel caches the valid tokens and doesn't have to hit the DB for every request?
You can use the Authentication Middleware. Pass with your request an api_token. A more detailed answer can be found here.
Add a api_token column to your User table.
$table->string('api_token', 60)->unique();
Have an authentication middleware as such:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class Authenticate
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->guest()) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('auth/login');
}
}
return $next($request);
}
}
And use Route Group
Route::group(['middleware' => 'auth:api'], function () {
Route::resource('api/v1/foo/bar', 'API\FooBarController');
}
Related
i am trying to start a simple API with Laravel Passport.
i am following this tutorial
i have sucessfully completed it, however when i try accessing "http://localhost:8000/api/CEO" in a browser without specifying a bearer token, i am getting the error:
Symfony\Component\Routing\Exception\RouteNotFoundException Route [login] not defined.
now i understand bearer token is needed to authenticate requests, but how can i verify that the api requests have a token? and if they dont, show an error. Instead of redirecting to a login page that doesn't exists.
If you take a look at laravel git repository you will find the Authenticate.php middleware: https://github.com/laravel/laravel/blob/8.x/app/Http/Middleware/Authenticate.php
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
What's happening at your end is that you are missing a very important header in your request Accept: application/json, therefore Laravel is trying to redirect to the login page which is not defined in your api.php routes.
Just hit your endpoint with postman or something similar by adding that header and you will receive an Unauthenticated error message.
If you want to open that URL from your browser you can also add a middleware to process the request and return a 401 error message.
I made my own middleware wich attaches the token sent by URL to my request headers.
If no token is present in URL then abort by throwing a 401 error
<?php
namespace App\Http\Middleware;
use Closure;
class ParseTokenFromUrl
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($request->has('access_token') && !$request->headers->get('Authorization')) $request->headers->set('Authorization', 'Bearer ' . $request->get('access_token'));
if (!$request->headers->get('Authorization')) abort(401);
return $next($request);
}
}
Please also refer to Laravel Passport Route [login] not defined
The "auth:api" middleware in api.php corresponds to App/Http/Middleware/Authenticate.php.
(this is verifiable by checking $routeMiddleware in App/Http/Kernel.php)
in this middleware we check :
if (! $request->expectsJson()) { return route('login'); }
change the return to
return abort(502, 'Invalid request');
to instead show an error.
I've configured Laravel Sanctum and everything is working with my SPA, however if a user create a token, they can inspect my website and call my internal SPA routes (which is not intended for their usage), what i have now is:
Route::middleware('auth:sanctum')->group(callback: function () {
Route::get('/user', function (Request $request) {
return new UserResource($request->user());
});
});
How can i prevent all users except my SPA to access this route?
After a bit of testing, when Sanctum authenticates UI requests, it sets session on the $request while when users call there is no session set for them.
I've created a middleware and gave it to my internal routes, here is the code:
class InternalApi
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
if ($request->hasSession()) {
return $next($request);
} else {
return response()->json([], 401);
}
}
}
and my api.php looks like this now:
Route::middleware('auth:sanctum')->group(callback: function () {
Route::get('/user', function (Request $request) {
return new UserResource($request->user());
})->middleware('internal');
});
Update: As described in documentation, for SPA requests $request->user()->tokenCan() will return true no matter what, even if users doesn't have any abilities assigned.
I would like a group of routes to be accessible with either the standard auth:api middleware, or via the CheckClientCredentials middleware.
I can't see how this would be possible as there is no ability to set middleware as only requiring one of the listed middleware.
Is there a Passport middleware which allows any type of API authentication that I don't know about?
Or is there a clean way of creating a custom middleware which tests for either of the middleware?
I took Joshuas advice about the similar answer here and created a new middleware encompassing the two auth middleware.
Working Middleware class for anyone else hitting this issue below. This will try the Auth guard middleware first and if this fails to authenticate the request it will then try to authenticate using the Client Credentials Passport middleware.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Auth\Middleware\Authenticate as AuthGuardMiddleware;
use Laravel\Passport\Http\Middleware\CheckClientCredentials as ClientCredMiddleware;
class AuthenticateWithApiOrClientCreds
{
/**
* Authenticate a request with either Authenticate OR CheckClientCredentials Middleware
*
* #param $request
* #param Closure $next
* #param mixed ...$scopes
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function handle($request, Closure $next, ...$scopes)
{
$auth_guard_middleware = app()->make(AuthGuardMiddleware::class);
try {
$response = $auth_guard_middleware->handle($request, $next, 'api');
} catch (AuthenticationException $e) {
$client_cred_middleware = app()->make(ClientCredMiddleware::class);
$response = $client_cred_middleware->handle($request, $next, ...$scopes);
}
return $response;
}
}
Laravel doesn't provide the OR middleware. Although there are several workarounds like it was previously asked here.
If you are looking for the way to change the api.php default middleware (default to auth:api), you can see it in directory: app\Providers\RouteServiceProvider.php with function name: mapApiRoutes().
I ended up storing all the routes that need authentication in a separate route file called api-authenticated.php. In RouteServiceProvider.php I'm applying the web and api middleware groups for those routes conditionally based on the presence of the Authorization header. When the Authorization header is set, you know the user is an API client.
In Providers/RouteServiceProvider.php:
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* #return void
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->apiNamespace)
->name('api.')
->group(__DIR__ . '/../../routes/api.php');
}
/**
* Define the authenticated "api" routes for the application.
*
* These routes are typically stateless, but are also used in
* a stateful context as the first-party uses cookies for
* authenticaton
*
* #return void
*/
public function mapAuthenticatedApiRoutes()
{
$stateless = request()->hasHeader('Authorization');
Route::prefix('api')
->middleware($stateless ? ['api', 'auth:api'] : ['web', 'auth'])
->namespace($this->apiNamespace)
->name('api.')
->group(__DIR__ . '/../../routes/api-authenticated.php');
}
I've set up a laravel app with client authentification. I send it my client id and client secret and it gives me a token. I'm able to log in to my laravel app, but I can't figure out how to get the id of the client that's been authorized.
I've seen hints to use auth()->user()->Token()->getAttribute('client_id') to get the client id, but since I'm only using clients there is no user and I get an error about trying to call Token() on a null object. Auth::id() also returned nothing. I grabbed the token from the header with Request::header('Authorization'), but it didn't match anything in the database.
I'm assuming you're using client credentials grant tokens, and the CheckClientCredentials middleware.
You can get this information from the bearer token, but it's not that straightforward. You would need to create a new PSR7 request with the token, and send it off to the oauth server to have it converted to readable data.
This is already done inside the CheckClientCredentials middleware provided by Passport. So, one way to do this would be to extend the CheckClientCredentials middleware and just manually set the needed fields on the request object from inside the middleware.
First, create app/Http/Middleware/MyCheckClientCredentials.php:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Laravel\Passport\Http\Middleware\CheckClientCredentials;
class MyCheckClientCredentials extends CheckClientCredentials
{
/**
* The Resource Server instance.
*
* #var \League\OAuth2\Server\ResourceServer
*/
private $server;
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param mixed ...$scopes
* #return mixed
* #throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, ...$scopes)
{
$psr = (new DiactorosFactory)->createRequest($request);
try {
$psr = $this->server->validateAuthenticatedRequest($psr);
// This is the custom line. Set an "oauth_client_id" field on the
// request with the client id determined by the bearer token.
$request['oauth_client_id'] = $psr->getAttribute('oauth_client_id');
} catch (OAuthServerException $e) {
throw new AuthenticationException;
}
$this->validateScopes($psr, $scopes);
return $next($request);
}
}
Next, update your app/Http/Kernel.php to use your custom middleware instead of the build in Passport middleware:
protected $routeMiddleware = [
'client' => \App\Http\Middleware\MyCheckClientCredentials::class,
];
Apply the middleware to your route as normal:
Route::get('/user', function(Request $request) {
// Should show "oauth_client_id" field.
dd($request->all());
})->middleware('client');
If you don't want to do this inside a middleware, you can study how the Passport middleware works and reuse this code in some type of service if you'd like.
NB: all untested.
I had to do something similar in a logger middleware of mine:
.......................
$user = $request->user();
if($user) {
// assume the authorization header exists, since the user is authenticated
$header = $request->headers->get('authorization');
if($header) { // authorization header is not set when testing via Passport::actingAs()
/**
* Stolen from League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator#63
*/
// Get the actual jwt string from the header
$jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $header));
// Parse the token from the string
$token = (new Lcobucci\JWT\Parser())->parse($jwt);
// Get the ID from the token
$oauthClientId = $token->getClaim('aud');
}
}
.......................
I have a route that where I'm using an auth middleware and it works great.
Route::group(['prefix' => 'v1','middleware' => ['auth:api']], function()
{
Route::resource('user', 'v1\MyController');
});
The problem is that I would also like this route to be accessible to non-authenticated users as well. With the above, I get a 401 Unauthorized error and I can't return any content for unauthenticated users. So how can I authenticate this route (so it passes down the user data) while also allowing the route to proceed even if the user is NOT authenticated?
(I tried doing a conditional Auth check on the router page but it seems the user has gone through authentication yet so it always remains false.)
EDIT: I should also note that I'm using an API route with Password Grant & access tokens.
remove this route from current route group (which applies auth middleware).
then
public function __construct()
{
if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER)) {
$this->middleware('auth:api');
}
}
then
if (Auth::check()) {
// Auth users
} else{
//Guest users
}
I am experiencing the same case.
since the auth middleware only checks for authenticated user, we can use client credentials for the non-authenticated user.
the client credentials have a separated middleware located in Laravel\Passport\Http\Middleware\CheckClientCredentails.
I have created a custom middleware to combine both middleware to allow either one is pass.
here is my custom middleware
namespace Laravel\Passport\Http\Middleware;
use Closure;
use League\OAuth2\Server\ResourceServer;
use Illuminate\Auth\AuthenticationException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
class CheckClientCredentials
{
/**
* The Resource Server instance.
*
* #var ResourceServer
*/
private $server;
/**
* Create a new middleware instance.
*
* #param ResourceServer $server
* #return void
*/
public function __construct(ResourceServer $server)
{
$this->server = $server;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*
* #throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, ...$scopes)
{
$psr = (new DiactorosFactory)->createRequest($request);
try{
$psr = $this->server->validateAuthenticatedRequest($psr);
} catch (OAuthServerException $e) {
throw new AuthenticationException;
}
foreach ($scopes as $scope) {
if (!in_array($scope,$psr->getAttribute('oauth_scopes'))) {
throw new AuthenticationException;
}
}
return $next($request);
}
}
Kernal.php
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.api' => \App\Http\Middleware\APIAuthenticate::class,
....
routes\api.php
Route::group([
'namespace' => 'API',
'middleware' => 'auth.api:api',
], function(){
....
From within an unauthenticated (not assigned the auth:api middleware) route's handler method, try:
Auth::guard("api")->user();
If it's populated, then your unguarded route can treat the access as authenticated. If not, its a random user accessing the route, and can be treated as such.
Dont put those urls whome you want to allow for both guest users and authenticated users in auth middleware. Because auth middleware allow for only authenticated users.
To check for authenticated and unauthenticated user you can use following code in view
#if (Auth::guest())
//For guest users
#else
//for authenticated users
#endif
Edited : In controller use
if (Auth::check()) {
// Auth users
} else{
//Guest users
}
#Yves 's answer is nearly correct. But a small change,
Instead of array_key_exists, we need to check whether key value is not null. Because It always has that key but null value. SO, instead of controller construct check for authorization header like this.
if ($_SERVER['HTTP_AUTHORIZATION']) {
$this->middleware('auth:sanctum');
}
Then you can check for authenticated user like this:
if (auth()->check()) {
// User is logged in and you can access using
// auth()->user()
} else {
// Unauthenticated
}
for sanctum use:
if (Auth::guard('sanctum')->check()) {
// user is logged in
/** #var User $user */ $user = Auth::guard('sanctum')->user();
} else {
// user is not logged in (no auth or invalid token)
}
You can use this example:
#if(Auth::user())
echo "authincatesd user";
#else
echo "unauthorised";
#endif