Am using laravel with android and whenever a 401 error is triggered in laravel i would like to attach to the 401 error a custom header
WWW-Authenticate: xBasic realm=32334
Whenever a 401 response is returned to android i get
com.android.volley.NoConnectionError: java.io.IOException: No authentication challenges found
So after aresearch i found out the problem is due to the fact that i need to add a header to the response given i laravel
So am using the default passport oauth/token route which routes are set in authservice provider like
public function boot()
{
$this->registerPolicies();
Route::group(['middleware'=>'appconnection'], function(){
Passport::routes();
});
}
As from above ive added an appconnection middleware to passport routes now i want to handle the response to check if 401 is ever returned and add the custom header
so in my middleware am stuck at adding the header
class AppConnectionMiddleware
{
public function handle($request, Closure $next)
{
$returned = $next($request);
//check if $returned has a 401 status response
//am stuck here
}
}
So how do i manipulate the response to include the custom response header
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->status() == 401) {
$response->header('WWW-Authenticate', 'xBasic realm=32334')
}
return $response;
}
Related
I'm strugling with authorization middleware in Slim4. Here's my code:
$app = AppFactory::create();
$app->add(new Authentication());
$app->group('/providers', function(RouteCollectorProxy $group){
$group->get('/', 'Project\Controller\ProviderController:get');
})->add(new SuperuserAuthorization());
Authentication middleware checks the user and works fine.
The method get in ProviderController is
public function get(Request $request, Response $response): Response{
$payload = [];
foreach(Provider::all() as $provider){
$payload[] = [
'id' => $provider->id,
'name' => $provider->name,
];
}
$response->getBody()->write(json_encode($payload));
return $response;
}
The SuperuserAuthorization looks like this
class SuperuserAuthorization{
public function __invoke(Request $request, RequestHandler $handler): Response{
$response = $handler->handle($request);
$authorization = explode(" ", $request->getHeader('Authorization')[0]);
$user = User::getUserByApiKey($authorization[1]);
if(! Role::isSuperuser($user)){
return $response->withStatus(403);//Forbidden
}
return $response;
}
}
The thing is that even though the user is not a superuser, the application continues executing. As a result I get json with all the providers and http code 403 :/
Shouldn't route middleware stop the request from getting into the app and just return 403 right away?
I know that I can create new empty response with status 403, so the data won't come out, but the point is that the request should never get beyond this middleware, am I right or did I just misunderstand something hereā¦
Any help will be appreciated :)
------------- SOLUTION ----------------
Thanks to #Nima I solved it. The updated version of middleware is:
class SuperuserAuthorization{
public function __invoke(Request $request, RequestHandler $handler): Response{
$authorization = explode(" ", $request->getHeader('Authorization')[0]);
$user = User::getUserByApiKey($authorization[1]);
if(! Role::isSuperuser($user)){
$response = new Response();
return $response->withStatus(403);//Forbidden
}
return $handler->handle($request);
}
}
Shouldn't route middleware stop the request from getting into the app and just return 403 right away?
Slim 4 uses PSR-15 compatible middlewares. There is good example of how to implement an authorization middleware in PSR-15 meta document. You need to avoid calling $handler->handle($request) if you don't want the request to be processed any further.
As you can see in the example, if the request is not authorized, a response different from the return value of $handler->handle($request) is returned. This means your point saying:
I know that I can create new empty response with status 403, so the data won't come out, but the point is that the request should never get beyond this middleware
is somehow correct, but you should prevent the request from going further by returning appropriate response before invoking the handler, or throwing an exception and let the error handler handle it.
Here is a simple middleware that randomly authorizes some of requests and throws an exception for others:
$app->group('/protected', function($group){
$group->get('/', function($request, $response){
$response->getBody()->write('Some protected response...');
return $response;
});
})->add(function($request, $handler){
// randomly authorize/reject requests
if(rand() % 2) {
// Instead of throwing an exception, you can return an appropriate response
throw new \Slim\Exception\HttpForbiddenException($request);
}
$response = $handler->handle($request);
$response->getBody()->write('(this request was authorized by the middleware)');
return $response;
});
To see different responses, please visit /protected/ path a few times (remember the middleware acts randomly)
I am using one laravel 5.7 authentication with custom check and session.
I have 5 type of user types
Session::put('user_type', $user_type);
Session::put('user_id', $user_id);
When I tried to check session data in constructor I am facing one issue,Please hele me to solve this,
ErrorException (E_NOTICE) Trying to get property 'headers' of
non-object
public function __construct()
{
$this->middleware(function ($request, $next) {
$utype=Session::get('user_type');
if($utype != 'ProjectAdmin'){
return redirect()->route('login');
}else{
$this->objShareContract = shareContract::getShareContract(TRUE);
}
});
}
Middleware must return a Response. You are not returning a Response from this custom Closure based middleware. You have 2 logical paths and only one of them is returning a Response of some sort.
There are other middleware before this one that are expecting a Response to come back through the pipeline. That is what the return $next($request); is about.
I have a user profile page and user profile/settings page
the problem is I made a middleware for settings page to prevent any auth user from entering other users settings page or update them Unless the ID OR SLUG IS MATCHED to the auth user but I'm using Vue whenever I use the API routes to fetch or update the data it says unauthorized 401 or 500.
middleware :
public function handle($request, Closure $next)
{
if ($request->slug != auth()->user()->slug) {
return redirect()->to('/');
}
return $next($request);
}
API route :
Route::get('/profile/{slug}','ProfilePrivateController#show')->middleware('editProfile');;
VueJs :
update(){
axios.put(`/api/profile/${this.id}`,{
email : this.email,
username : this.name,
password : this.password,
education_level : this.education_level,
fb_url : this.fb_url,
twitter_url : this.twitter_url,
field : this.field
})
.then(res=>console.log(res))
}
Controller :
public function show($slug)
{
$user = User::findBySlugOrFail($slug);
return response()->json($user);
}
public function update(Request $request, $slug)
{
$user = User::findBySlug($slug);
$user->update([
'email'=>$request->email,
'education_level'=>$request->education_level,
'field'=>$request->field,
'school'=>$request->school,
'fb_url'=>$request->fb_url,
'twitter_url'=>$request->twitter_url,
]);
if($request->has('password')){
$user->save([
'password'=>$request->password
]);
}
return response()->json('user updated',200);
}
I Want to let the user update his settings and secure the API at the same time.
I'm really lost at this point Any help is appreciated!
You have a GET request for the API route, but using a PUT request in Vue.
Updating Route::get to Route::put should solve the problem.
Also, since its an AJAX request, you should be returning a JSON response so it can easily be consumed. You can return something similar to:
return response()->json(['error' => 'unauthorized'], 401);
I'm using Laravel 5.3 & Passport.
When using Postman to test any route I have set in api.php file it always returns the login page. Here is an example of my testing route:
Route::get('/getKey', function() {
return 'hello';
})->middleware('client_credentials');
Postman params:
Accept application/json
Authorization Bearer <then my key>
I have set middleware to 'auth:api' per another solution I found while searching for the answer.
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('auth:api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
I've tried just about every solution that has worked for others but still no luck. Any suggestions would be much appreciated.
UPDATE
So I finally got something to work. I created a consumer app and created a few test functions. I was able to consume the api, with verification of token. However, hitting this Route no longer returns my login page, but instead now returns nothing. So its still not working for whatever reason.
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('client_credentials');
The redirection to the defined login route is occurring in the app\Exceptions\Handler.php class.
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(route('login'));
}
The function tries to detect whether it is being called from an API (it which case it returns a 401 Unauthorized reponse with JSON message) and if not it will redirect to the login page according to the comments it
Converts an authentication exception into an unauthenticated response
To resolve the issue in postman, on the request click on the Headers tab and add:
key: Accept
value: application/json
I'm pretty new to this so am not sure whether this is a header we should be adding when testing all API calls with Postman or just a nusience with how this laravel method is setup.
Anyway this would solve your issue with being redirected to the login page, however it's a sign your underlying authentication isn't working
You need to add Authorization: Bearer YOUR_TOKEN to your every request's header. Also, for latest version Laravel 5.5 or above. You need to add Accept: application/json to request header too.
Add this code on Headers on postman.
key Value
Accept application/json
It is coded to check whether the request comes from Ajax. In that case you will receive the following json if authentication fails:
{
"error": "Unauthenticated."
}
Otherwise it will assume you are using a browser and it will redirect to Html login page for authentication.
You can add the following header to your request to simulate an Ajax request:
X-Requested-With = XMLHttpRequest
From laravel 5.8 till the current 6.0 version there is a a middleware located at the app/http/Middleware which is \App\Http\Middleware\Authenticate, it has a method
redirectTo
with the code
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
Re-write this to
protected function redirectTo($request)
{
if (! ($request->expectsJson() || collect($request->route()->middleware())->contains('api'))) {
return route('login');
}
}
What this code does is to return a Illuminate\Routing\Redirector instance and sets it as the \Illuminate\Auth\AuthenticationException $redirectTo parameter . which is passed to \App\Exceptions#render by laravel.
At the render function you can write a logic to catch an \Illuminate\Auth\AuthenticationException exception.
Here is my own implementation
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
/**
* Render an Authentification exception when user trying to viditing a route or
* Perform an action is not properly authenticated
*/
if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
return $this->unauthenticated($request,$exception);
}
}
/**
* Convert an authentication exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, \Illuminate\Auth\AuthenticationException $exception)
{
return $exception->redirectTo()
? redirect()->guest($exception->redirectTo())
: response()->json(['message' => $exception->getMessage()], 401);
}
As noted in many of the answers the reason this happens is indeed the code:
if (! $request->expectsJson()) {
return route('login');
}
in app\Http\Middleware\Authenticate.php
One way to solve this is to wrap your api requests in Middleware that adds 'Accept: application/json' to the header of those requests.
I got this idea from this article: https://medium.com/#martin.riedweg/laravel-5-7-api-authentification-with-laravel-passport-92b909e12528
Laravel version: 9.*
// app/Http/Middleware/Authenticate.php
protected function redirectTo($request)
{
return '/api/unauthenticated'; // You can name whatever route you want
}
// routes/api.php
Route::get('/unauthenticated', function () {
return response()->json(['message' => 'Unauthenticated.'], 403);
});
Hope this helps.
Happy coding!
If you don't want to enforce your api consumer to pass Accept - application/json in all the request headers you can add the following method in App\Exceptions\Handlers.php; to customize error response
//use Illuminate\Auth\AuthenticationException;
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->is('api/*')) {
return response()->json(['error' => 'Invalid token', 'status' => false], 401);
}
return redirect()->guest(route('login'));
}
if you are using username instead of email for credential; insert this method at your User model:
function findForPassport($username) {
return $this->where('username', $username)->first();
}
I tried to set the http status in my custom API when a request is being made.
protected $statusCode = 200;
public function setStatusCode($statusCode) {
$this->statusCode = $statusCode;
return $this;
}
public function respond($data, $headers = []) {
return response()->json($data, $this->getStatusCode(), $headers);
}
public function respondCreated($message) {
return $this->setStatusCode(201)->respond([
'message' => $message
]);
}
$this->respondCreated("Incident was created");
But when I make my server request in POSTMAN, I see status 200 and not 201 as set in the code above and the message is not appearing at all. Do I need to do it differently?
I am using the Laravel framework and implemented the functions by the book "Build APIs you won't hate"
I used the http_response_code() method as suggested and set the code like this:
public function respondCreated($message) {
$this->setStatusCode(201)->respond([
'message' => $message
]);
http_response_code(201);
return $this;
}
When I then return the response code it shows properly, but the POSTMAN Status is still 200?
The helper method by laravel is response() and is described as:
Returning a full Response instance allows you to customize the response's HTTP status code and headers. A Response instance inherits from the Symfony\Component\HttpFoundation\Response class, providing a variety of methods for building HTTP responses:
use Illuminate\Http\Response;
Route::get('home', function () {
return (new Response($content, $status))
->header('Content-Type', $value);
});
For convenience, you may also use the response helper:
Route::get('home', function () {
return response($content, $status)
->header('Content-Type', $value);
});
You can set the HTTP Response Code as stated on PHP documentation.
<?php
// Get the current default response code
var_dump(http_response_code()); // int(200)
// Set our response code
http_response_code(404);
// Get our new response code
var_dump(http_response_code()); // int(404)
?>