Slim Basic Authentication - php

Good day everyone!
I have a working slim code here with slim-basic-auth and when I go to a restricted directory, this shows up:
Everything works, but what I wanted to do is to redirect it to my login page instead of showing a popup login box. Here is my login page:
My slim code:
$pdo = new \PDO("mysql:host=localhost;dbname=databasename", "username");
$app->add(new \Slim\Middleware\HttpBasicAuthentication([
"path" => "/main",
"realm" => "Protected",
"authenticator" => new PdoAuthenticator([
"pdo" => $pdo,
"table" => "accounts",
"user" => "accountUsername",
"hash" => "accountPassword"
]),
"callback" => function ($request, $response, $arguments) use ($app) {
return $response->withRedirect('/main/contacts');
}
When I try to login using the popup login box, it works but I really want to redirect it to my login page instead of that.
Any help would be much appreciated.

The middleware implements HTTP Basic Access Authentication. Authentication dialog is triggered via response header. It is up to the browser vendor to decide how credentials are asked. Most browsers use the popup login dialog you described.
What you are trying to do is a bit unorthodox way of using HTTP Basic Authentication. However you can suppress the login dialog by removing the WWW-Authenticate header from the response. Note the you need at least version 2.0.2 for this to work.
$app->add(new \Slim\Middleware\HttpBasicAuthentication([
"path" => ["/main"],
"authenticator" => new PdoAuthenticator([
"pdo" => $pdo,
"table" => "accounts",
"user" => "accountUsername",
"hash" => "accountPassword"
]),
"error" => function ($request, $response, $arguments) {
return $response
->withRedirect("/auth/login")
->withoutHeader("WWW-Authenticate");
}
]));
However with code above you still have to set the Authentication: Basic request header somehow. One way to do is using an AJAX request.
$.ajax({
url: "http://example.com/auth/login",
username: $("username").val(),
password: $("password").val(),
success: function(result) {
alert("Authorization header should now be set...");
}
});

At this point it looks like you're not trying to use the Http Basic Authenticator but rather a normal login process so you'll need to use sessions and such.
A very simple example is adding this close to the bottom of your middleware stack.(meaning it will be executed first as it will be at the top of the stack)
$middleware = function (Request $request, Response $response, $next) {
if (!isset($_SESSION['__user'])) {
//don't interfere with unmatched routes
$route = $request->getAttribute('route');
if ($route && !in_array($route->getName(), ['login'])) {
return $response->withStatus(403)->withHeader('Location', $this->router->pathFor('login'));
}
}
return $next($request, $response);
};
$app->add($middleware);
Looking at the HttpBasicAuthentication middleware it will always send the WWW-Authenticate header making your login form useless as it will trigger the auth pop-up.

Related

laravel reponse json message not translated

I am using laravel 8 with sanctum api (vue 3 spa).
I have never used localization before....and for this app I need that.
I created a middleware SetLocale with this content
public function handle($request, Closure $next)
{
app()->setLocale(config('app.locale'));
if(session()->has('locale')) {
app()->setLocale(session('locale'));
}
return $next($request);
}
and registered in both web and api (app/Http/Kernel.php) #middlewareGroups
From the login form there is a select that change locale and make a post request to server to change the locale
Session()->put('locale', $request->input('locale'));
app()->setLocale(session('locale'));
return response()->json(['locale' => app()->currentLocale()]);
In the login form the server validation responds with the locale messages.... but the problem is the customized message (Customize laravel sanctum unauthorize response) it is not translated - it gives me in 'en' ... even if the locale is 'de'
$this->renderable(function (\Illuminate\Auth\AuthenticationException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => trans('_custom.not_authenticated')
], 401);
}
});
The file is there for every language _custom.php (resources/lang/de, resources/lang/en, etc )
example for _custom.php "de":
<?php
return [
'not_authenticated' => 'Nicht berechtigt'
];
The message is still in en ... but if press login (without changing anything) .... the validation errors are in 'de'
Why ? What I missed ?
Could be that the message is returned before the middleware runs ?
*** UPDATE 1 ***
It seems to work partially using in request Accept Language header .... but there is another problem.
In vue I have this approach for the axios:
const api = axios.create({
withCredentials: true,
headers: {
common: {
'Accept-Language': document.documentElement.lang
}
}
});
export default api;
In the vue app when changing the language .... I also update the document.documentElement.lang value ... but when making a request it still the default value 'en'
Is there any way I can change dynamic the value for the 'Accept-Language' header instead of adding for each request the header ?

getting intended target url without losing the values in middleware

I have the following two settings page routes one for normal settings and the other for secured and admin related settings which uses the same middleware "password.confirm"
Route::get('/admin/settings',WebsiteInfoController::class,'edit'])->name('settings')->middleware('password.confirm');
Route::get('/settings',[WebsiteInfoController::class, edit'])->name('user.settings')->middleware('password.confirm');
This middleware redirects me to a second page where I have to enter password and then only i can get access to my intended page.
In my middleware I have the following function. I want to make an additional check if the user is intending to access the admin related settings
public function store(Request $request)
{
$this->validate($request, [
'secret_password' => ['sometimes','required'],
'password' => ['required','password:web'],
]);
if(redirect()->intended()->getTargetUrl()==route('settings')){
$secret_password =WebsiteInfo::first()->secret_password;
if (!Hash::check($request->secret_password, $secret_password)) {
throw ValidationException::withMessages([
'secret_password' => __('auth.password'),
]);
}
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended(RouteServiceProvider::HOME);
});
});
}
Everything works fine in this method but the intended URL is lost when all the check is performed and I am redirected to homepage instead of settings page. I also tried to save the URL in a variable and use it later in the redirect command like
$path=redirect()->intended()->getTargetUrl();
if($path==route('settings')){
$secret_password =WebsiteInfo::first()->secret_password;
if (!Hash::check($request->secret_password, $secret_password)) {
throw ValidationException::withMessages([
'secret_password' => __('auth.password'),
]);
}
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended($path);
This method works fine but it also loses the URL if the second validation fails and the user is redirected back to the confirm password page. Now when I try to perform the validation second time it again loses the intended URL and redirects me back to home page.
i also tried the check with $request method.
if($request->route()->named('settings')){
$secret_password =WebsiteInfo::first()->secret_password;
if (!Hash::check($request->secret_password, $secret_password)) {
throw ValidationException::withMessages([
'secret_password' => __('auth.password'),
]);
}
}
This method however, is not able to detect the route in middleware and the validation check is not at all performed.
So, My question is how do i check for the intended URL and perform validation check without losing the intended URL even after multiple failed validation attempts?
Your method is all fine. You just used the wrong method to extract the target website URL. It is true that redirect()->intended()->getTargetUrl() gives you the target page URL but it also removes the target website URL from the session so when you finish performing the checks and want to redirect to the intended page there is no intended page URL found in the session and you get redirected to the default fall back URL. This is what the redirect function does
public function intended($default = '/', $status = 302, $headers = [], $secure = null) {
$path = $this->session->pull('url.intended', $default);
return $this->to($path, $status, $headers, $secure);
}
Here, the $request->route()->named('settings) method does not work since you are not directly interacting with your initial view but instead through a middleware view which does not send the intended page request.
Use the following code and I guess you will be all fine with your validation attempts. It will work even after multiple failed login attempts.
public function store(Request $request) {
$this->validate($request, [
'secret_password' => ['sometimes','required'],
'password' => ['required','password:web'],
]);
$path=session()->get('url.intended', RouteServiceProvider::HOME);
if($path==route('settings')) {
$secret_password =WebsiteInfo::first()->secret_password;
if (!Hash::check($request->secret_password, $secret_password)) {
throw ValidationException::withMessages([
'secret_password' => __('auth.password'),
]);
}
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended($path);
}

How can I log what is going wrong in my controller

I am trying to register a user on my website with the laravel/vue.js/vuex. In my store actions, I'm calling the tryRegister action to post a request. But it keeps responding with a 401 error not authorizaed, and I don't understand why. So I made my controller as basic as it can get to just get a response and even then it keeps throwing the 401 authorization error. I'm new to back-end developing and just can't understand why this happens. I do know for sure that the route is working. How can I make my controller function give a basic response to see if it is working? And why is it giving an authorization error even tho I'm not doing anything with authorization, is that just the standard error a controller gives?
Try register action
tryRegister(context, credentials) {
return new Promise((resolve, reject) => {
axios
.post("/api/auth/register", credentials)
.then(response => {
console.log(response.data);
//context.commit("registerSucces", response.data);
resolve(response.data);
})
.catch(error => {
console.log(error.response);
reject(error);
});
});
}
Authorization controller register function
public function register(Request $request)
{
// $user = User::create([
// 'email' => $request->email,
// 'password' => $request->password,
// ]);
//$token = auth('api')->login($user);
//return $this->respondWithToken($token);
return response()->json(['message' => 'controller register']);
}
your register method of the controller is not accessible because of the auth middleware. so you are getting not authorized error. make it accessible without authorization. in the constructor method of the controller change this line like below.
$this->middleware('auth:api', ['except' => ['login', 'register']]);
login and register are controller's method which will be now accessible without authorization.

Handle Method not allowed in slim 3

Need you help to figure this out. i developed my website in Slim 3 framework. I wanted to handle "Method not allowed. Must be one of: POST" message which i get when i am using browser back and forward buttons.
I want to redirect to a different page when if the route is post and when user clicks on browser back or forward page.
When the post route is called is there a way where i can detect the that it is post method call and redirect him to a different get route.
You can add your own handler for specific errors:
$container['notAllowedHandler'] = function (ServerRequestInterface $request, ResponseInterface $response, array $methods) {
// you can return a redirect response
};
see more here
Another syntax solution
$notAllowedHandler = function ($c) {
return function ($request, $response) use ($c) {
return $response
->withJson(
[
"status" => false,
"message" => "Your custom message",
"data" => [],
]
)
->withStatus(400);
};
};
$app = new App(
[
'notAllowedHandler' => $notAllowedHandler,
]
);

Sending header data to laravel controller?

I'm building an API for a webapp I made to train myself in Laravel. I integrated a token based authentication system by Tappleby, like this:
Route::get('api/v1/auth', 'Tappleby\AuthToken\AuthTokenController#index');
Route::post('api/v1/auth', 'Tappleby\AuthToken\AuthTokenController#store');
Route::delete('api/v1/auth', 'Tappleby\AuthToken\AuthTokenController#destroy');
Route::group(['prefix' => 'api/v1', 'before' => 'auth.token'], function ()
{
Route::resource('user', 'ApiUsersController');
});
In ApiUsersController I, ideally, want to do something like this:
public function index()
{
$payload = $request->header('X-Auth-Token');
if(empty($payload)) {
return $this->respondNotFound('User does not exist.');
}
$user = $this->driver->validate($payload);
return $user;
}
However, header() is not available for the controller. How can I solve this?
In Laravel, you can retrieve the HTTP headers like so:
$value = Request::header('Content-Type');
Add this to your controller and you can then do what you need to with it.
Also, you can change Content-Type to whatever it should be.
Read more here: http://laravel.com/docs/4.2/requests

Categories