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.
Related
Here's my Authenticated middleware:
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* 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 response()->json(['message' => "Token is expired"], 401);
}
if (! $request->expectsJson()) {
return route('login');
}
}
}
Here's my api.php
Route::middleware(['auth:api'])->prefix('notifications')->namespace('Notification')->group(function () {
Route::post('/send', 'NotificationController#send');
});
I send a request with a postman with headers content-type JSON.
But It always redirects to the homepage. No answers as JSON.
What's wrong with that?
I'm already lost my mind.
in your request header add
Accept --- application/json
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 am trying to handle ajax request that were initiated from idle/expired session(maybe the page was left open and the session got expired). I wrote the below middleware but it's not working as expected:
namespace App\Http\Middleware;
use Closure;
class AjaxSessionCheck
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(!\Auth::check())
{
if($request->ajax())
{
return response()->json(['Session_error'=>'Session Expired'], 401);
}
}
return $next($request);
}
}
I also tried to add this code to the Auth middleware with no luck.
Strangely enough authenticated(user logged in) ajax requests are detected by this.
Lost 2 days finding solutions. Desperate call here.
use optimised code for performance use both auth::check and request->ajax() in same if condition by AND operator. just try session expiry in configuration file
It's because session runs after middleware, you can see the reference here. If you want to check that session expired, I think you should use after middleware instead of before middleware
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');
}