I see quite a few people having a similar issue with this, but no final resolved solutions. I have been trying to get this working for about 24 hours now and still no luck!
Goals
Build and API using Laravel 6 and Dingo API
Be able to consume the API externally, authenticating with Passport oAuth.
Be able to consume the API internally, via ajax, using passports self-authenticating feature.
Be able to consume the API internally, with PHP, using dingo's self-consuming methods.
What I have found out so far
Auth provider order
Most solutions I have seen suggest setting up both the passport auth and dingo alongside one another. This is auth:api (passport) and api.auth (dingo).
// API route middleware
$api->group(['middleware' => 'auth:api', 'api.auth'], function (Router $api) {
...
The api.auth here is actually a custom auth provider setup in laravel and configured to dingo, which bridges the passport logic into dingo.
// Auth provider
class DingoPassportAuthProvider extends Authorization
{
protected $guard;
public function __construct(AuthManager $auth)
{
dump('DingoPassportAuthProvider Instantiated');
$this->guard = $auth->guard('api');
}
public function authenticate(Request $request, Route $route)
{
if ($this->guard->check()) {
return $this->guard->user();
}
throw new UnauthorizedHttpException('Not authenticated via Passport.');
}
public function getAuthorizationMethod()
{
return 'Bearer';
}
}
// Configured in dingo (Api.php)
'auth' => [
'passport' => \App\Providers\DingoPassportAuthProvider::class,
],
If we put the dingo API provider first in the middleware stack we get:
Internal API requests work IF you specify the user for the call with the be() method: $this->api->be($request->user())->get('/api/profile')
External API requests and internal AJAX requests authenticate correctly and the user is returned from the custom dingo auth provider, however, for some reason you cannot then access this user from within the API controllers: $user = $request->user(); // null
If we put the Passport API provider first in the middleware stack we get:
Internal API requests do not work at all (401 always returned)
External API requests and internal AJAX requests work as intended.
The authenticate method on the dingo passport provider is no longer called. I think this may have something to do with the 401 returned on internal calls.
I believe the correct way around, is to put the passport authentication first. This way, we authenticate the user before calling the dingo authentication, resulting in 2 things:
Passport works natively as expected.
Dingo internal API calls should now just be able to be called with $this->api->get('/api/profile') (omit defining the user with be()), however this does not work.
At the moment I have the previous configuration. Passport works as intended for external and ajax calls, but the internal dingo calls always return 401.
There are a few boilerplate templates I have checked out and they do not seem to do anything different. I wonder if something changed in L6 to explain why the internal requests do not work.
I have found one work around for now, which gets most of the way there...
Within the custom dingo auth provider:
class DingoPassportAuthProvider extends Authorization
{
public function authenticate(Request $request, Route $route)
{
if (Auth::guard('web')->check()) {
return Auth::guard('web')->user();
}
if (Auth::guard('api')->check()) {
$user = Auth::guard('api')->user();
Passport::actingAs($user);
return $user;
}
throw new UnauthorizedHttpException('Not authenticated via Passport.');
}
public function getAuthorizationMethod()
{
return 'Bearer';
}
}
This now checks to see if the request is coming from either the web guard (internal request) or the api guard (external or ajax request) and returns the correct user.
For the api guard, there seems to be an issue that the user is authenticated but not actually available within the controllers. To get around this I added the Passport::actingAs($user). It is probably not best practice, but the guards are now acting as they should and as are all my different scenarios.
Then in the API route middleware, we only specify the custom dingo provider.
// API route middleware
$api->group(['middleware' => 'api.auth'], function (Router $api) {
...
One thing to note with this, is dingos be() method does not work quite as expected. Instead you need to switch the user as you would in a general laravel app.
\Auth::loginUsingId(2);
$user = $this->api->get('/api/profile');
Related
I haven't been able to find too many answers on specifically authenticating broadcasting using Sanctum. I'm trying to implement a basic event that broadcasts to pusher, but I keep getting a 403 error when trying to connect to /broadcasting/auth. Firstly, yes, I have uncommented the BroadcastServiceProvider in /config/app.php. Here is an extract from my Broadcast service provider:
app/Providers/BroadcastServiceProvider.php
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes(['middleware' => ['auth:sanctum']]);
require base_path('routes/channels.php');
}
}
I authenticate using Sanctum and all of my endpoints live in the api.php file. I'm not sure whether I should be declaring the Broadcast::routes there, as I've seen some people allude to it, but I don't really know what difference it makes as it still gives me the same error. I also don't even know if calling auth:sanctum middleware is valid here. I mean, I know it works for all of my api routes, but I'm not sure about in this Provider file.
I'm trying to hit the /broadcasting/auth endpoint with Postman, while including my Sanctum Authorization header (Bearer [token]) in the request, but it just gives me a 403.
Can someone please point me in the right direction here?
I also had a similar issue but I was able to resolve it by adding the channel on which I'm broadcasting on in the channel.php file inside the route directory.
channel.php
Broadcast::channel('request_channel.{device_id}', function ($user, $device_id) {
return (int) $user->id === (int) \App\UserDevice::find($device_id)->user_id;
});
I am building a REST user-microservice using Laravel 5.5 + Passport.
I am using the standard Passport::routes(), but I have had to modify the Auth::routes in order to make them return JSON responses, and to make them work with Passport.
I have added the following lines to my routes/web.php file:
Route::group(['middleware' => 'auth:api'], function () {
$this->post('logout', 'Auth\LoginController#logout')->name('logout');
});
This allows me to POST https://myapi/logout
If I make the call with the header "Authorization => Bearer TOKEN", I get a successful logout response.
If I provide no header at all, I get a "not authenticated" message (which is good)
However, if I provide the header with a revoked token, I get a recursive deadloop of the function: Illuminate\Auth\RequestGuard->user() (it keeps calling itself recursively until stack-overflow)
This is all done in the auth:api middleware, my logout code is not reached, but my LoginController constructor is called. Constructor code:
public function __construct(Application $app)
{
$this->apiConsumer = $app->make('apiconsumer');
$this->middleware('guest')
->except('logout');
}
I'm struggling to understand if it's my code causing this issue, or some combination of Laravel + passport + auth.
My first thought was that the auth:api middleware fails to authenticate the user, and as a result redirects the user to /home, where for some reason it's triggered again, recursively. But if that was the case, why would it work correctly with no header?
My current thinking is that the token in question does exist in the database, but Laravel is failing to figure out that it's revoked.
Any suggestions appreciated,
I found an answer (if not the answer) after a lot of research. It appears this is a Laravel bug (https://github.com/laravel/passport/issues/440). The solution is to add OAuthServerException to the $dontReport array in app/Exceptions/Handler.php:
class Handler extends ExceptionHandler
{
protected $dontReport = [
...
\League\OAuth2\Server\Exception\OAuthServerException::class,
];
}
This will avoid trying to log user information, thereby avoid the deadloop.
I have faced this in localhost. in my case, I have used xampp server and facing this issue
after creating a virtual host like "testlarave.test" then solve the error
Im building an API in Laravel 5.3. In my routes/api.php file I have 3 endpoints:
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
Several services will call these routes directly, however, when a request comes in from some services, the input data needs to be processed before it can hit these endpoints.
For example, if a request comes in from JIRA, I need to process the input before it hits these endpoints.
Im thinking that the simplest way to do this would be to have a 4th endpoint like the below:
Route::post('/notify/v1/jira', 'Api\Notify\v1\JiraFormatter#formatJiraPost');
The idea being to hit the /notify/v1/jira endpoint, have the formatJiraPost method process the input and then forward the request to /notify/v1/notifications (/alerts, /incidents) as required.
How can I have the /notify/v1/jira endpoint forward the request to the /notify/v1/notifications endpoint?
Do you see a better way to do this?
Depending how your app will work, you could always have your services pointing to /notify/v1/jira and then do the processing like you suggested.
Another alternative is have the JIRA service pointing at the same routes as all the other services but to use a Before middleware group to pre-process your data. Something like
Route::group(['middleware' => ['verifyService']], function () {
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
});
You could check in your middleware your service.
<?php
namespace App\Http\Middleware;
use Closure;
class verifyService
{
public function handle($request, Closure $next)
{
//You could use switch/break case structure
if (isJIRA($request)) {
//Do some processing, it could be outsourced to another class
//$JiraPost = new formatJiraPost($request);
//Keep the requesting going to the routes with processed data
return $next($request);
}
//You could add extra logic to check for more services.
return $next($request);
}
protected function isJIRA(Request $request){
//Logic to check if it is JIRA.
}
}
I've tried to exclude requests from another localhost server (http://localhost:8080/order/placeorder) to another one localhost server (http://localhost:8000)
I don't want to disable all csrf protection by removing
\App\Http\Middleware\VerifyCsrfToken::class in Illuminate\Foundation\Http\Kernel.php
I've tried to modify app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'http://localhost:8080/*',
'http://localhost:8080',
'/order/placeorder/*',
'http://localhost:8080/order/placeorder'
];
and I also tried this way
private $openRoutes = [
'http://localhost:8080/*',
'http://localhost:8080',
'/order/placeorder/*',
'http://localhost:8080/order/placeorder'
];
public function handle($request, Closure $next)
{
//add this condition
foreach($this->openRoutes as $route) {
if ($request->is($route)) {
return $next($request);
}
}
return parent::handle($request, $next);
}
But I still got this error
TokenMismatchException in VerifyCsrfToken.php
Can anyone suggest me what should I do and what I've done wrong?
The exceptions are routes within your own application that are excluded, not the URLs of servers that are requesting it. You will never put localhost, http, or any domain in these exceptions in normal circumstances. If you wish for a request by an external server to be accepted, I would disable CSRF protection for the routes it is accessing (because you want a cross-site request, that's what CSRF prevents).
For example, if you want any external server to be able to send a POST request to /order/placeorder, you would simply add that route to the exclusion. You also need to add any other route you want it to be able to access. If there are a lot, there are other more manageable ways to do this with middleware as well.
To authenticate the server making the request, it should send a token to verify itself. You can create a static token for this purpose (like an API key), or possibly use an OAuth implementation of some sort with access/refresh tokens - there is a package for Laravel for this that makes it easy.
I'm writing an API in Slim 3 that integrates with a legacy system. The client sends a token to my API in the querystring. I want to write a middleware that will authenticate the token and return an object that contains the necessary internal login credentials (which are different from the token) that are used by the legacy system.
I can authenticate the token now, but my problem is that Slim 3 requires that the middleware return a \Psr\Http\Message\ResponseInterface instance. I also want it to return a custom object back to the application.
I think I can achieve this by re-verifying the token outside of the middleware, and only use the middleware as a way to authenticate the token and return an error if it fails. I tend to think this kludgy way could be avoided so I just have to use the token once in the middleware and return the custom object at the same time so I don't have to use the token twice.
I have searched around for solutions, but all of the example middlewares I can find are similar to https://github.com/julionc/slim-basic-auth-middleware, where they are simply authenticating in the middleware but do not have the requirement to return a custom object. The documentation at http://www.slimframework.com/docs/concepts/middleware.html doesn't seem to help much either with this custom requirement.
Any ideas?
You could include a callback in your middleware which you can use to store the custom object somewhere. For example with slim-jwt-auth you can use callback to store the decoded contents of JWT using a callback.
$app->add(new \Slim\Middleware\JwtAuthentication([
"secret" => "supersecretkeyyoushouldnotcommittogithub",
"callback" => function ($request, $response, $arguments) use ($app) {
$app->jwt = $arguments["decoded"];
}
]));
Note that this kind of callback is not a Slim 3 feature. Just something this middleware happens to use.