Slim 3 Middleware Redirect - php

I want to check if a user is logged in. Therefor I have an Class witch returns true or false. Now I want a middleware which checks if the user is logged in.
$app->get('/login', '\Controller\AccountController:loginGet')->add(Auth::class)->setName('login');
$app->post('/login', '\Controller\AccountController:loginPost')->add(Auth::class);
Auth Class
class Auth {
protected $ci;
private $account;
//Constructor
public function __construct(ContainerInterface $ci) {
$this->ci = $ci;
$this->account = new \Account($this->ci);
}
public function __invoke($request, \Slim\Http\Response $response, $next) {
if($this->account->login_check()) {
$response = $next($request, $response);
return $response;
} else {
//Redirect to Homepage
}
}
}
So when the user is logged in the page will render correctly. But when the user is not autoriesed I want to redirect to the homepage. But how?!
$response->withRedirect($router->pathFor('home');
This doesn't work!

You need to return the response. Don't forget that the request and response objects are immutable.
return $response = $response->withRedirect(...);
I have a similar auth middleware and this is how I do it which also adds a 403 (unauthorized) header.
$uri = $request->getUri()->withPath($this->router->pathFor('home'));
return $response = $response->withRedirect($uri, 403);

Building off of tflight's answer, you will need to do the following to make everything work as intended. I tried to submit this as a revision, given that the code provided in tflight's answer would not work on the framework out of the box, but it was declined, so providing it in a separate answer:
You will need the following addition to your middleware:
protected $router;
public function __construct($router)
{
$this->router = $router;
}
Additionally, when declaring the middleware, you would need to add the following the constructor:
$app->getContainer()->get('router')
Something similar to:
$app->add(new YourMiddleware($app->getContainer()->get('router')));
Without these changes, the solution will not work and you will get an error that $this->router does not exist.
With those changes in place you can then utilize the code provided by tflight
$uri = $request->getUri()->withPath($this->router->pathFor('home'));
return $response = $response->withRedirect($uri, 403);

make basic Middleware and inject $container into it so all your middleware can extends it.
Class Middleware
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function __get($property)
{
if (isset($this->container->{$property})) {
return $this->container->{$property};
}
// error
}
}
make sure your Auth middleware on the same folder with basic middleware or you can use namespacing.
class Auth extends Middleware
{
public function __invoke($request, $response, $next)
{
if (!$this->account->login_check()) {
return $response->withRedirect($this->router->pathFor('home'));
}
return $next($request, $response);
}
}

Use:
http_response_code(303);
header('Location: ' . $url);
exit;

Related

Tenant Multi tenancy Laravel Unit Testing issue

Here is the test case that i created for category testing. I am getting 404 on this route while i have correctly configured the tenant test case and this route is exist on subdomain that was created on chrome browser.
public function test_example()
{
$response = $this->call('GET', '/categories/6/edit');
$this->assertEquals(200, $response->getStatusCode(),$response->exception->getMessage());
}
My TestCase.php
protected $tenancy = false;
public function setUp(): void
{
parent::setUp();
if ($this->tenancy) {
$this->initializeTenancy();
}
}
public function initializeTenancy()
{
$tenant = Tenant::create();
tenancy()->initialize($tenant);
}
Documentation I am following
https://tenancyforlaravel.com/docs/v3/testing
Result:
I want 302 response means redirect to login code.
I hope this piece of code can help you
https://github.com/archtechx/tenancy/issues/635#issuecomment-939226522
tenancy()->initialize($tenant);
URL::forceRootUrl('http://' . $tenant->domains[0]['domain']);

Laravel cookies not available in service provider during unit testing

I have a service provider which instantiates a CartCookie class which generates a unique cookie for saving shopping carts. It's a singleton class and it's injected into the service container.
CartCookieServiceProvider.php
public function boot(Request $request)
{
$this->app->singleton(CartCookie::class, function ($app) use ($request) {
return new CartCookie($request);
});
}
CartCookie.php
use App\Cart;
use Illuminate\Http\Request;
class CartCookie
{
private $id;
private $request;
function __construct(Request $request)
{
$this->request = $request;
if ($request->cookie('cart_id')) {
$this->id = $request->cookie('cart_id');
} else {
$this->id = $this->generateUniqueCartId();
}
}
public function id()
{
return $this->id;
}
private function generateUniqueCartId()
{
do {
$id = md5(time() . 'cart' . rand(100000000000000, 9999999999999999));
} while (Cart::find($id));
return $id;
}
}
In the CartCookie class I check for the existence of a cart_id cookie. Works perfectly fine when using the application!
My issue is that during unit tests, the cart_id cookie is empty, but only when the Request comes from the service provider. If I obtain the Request from a Controller later on in the lifecycle for example, the cookie is present.
Here is an example of a test:
/** #test */
public function get__store_checkout__checkout_displays_database_cart_correctly()
{
$cart = $this->createDatabaseCart();
$cookie = ['cart_id' => Crypt::encrypt($this->cartCookie)];
$response = $this->call('get', route('root.store.checkout'), [
'seller_id' => $cart->seller->id,
], $cookie);
$cart->seller->items()->each(function ($item) use ($response) {
$this->assertContains($beat->item, $response->getContent());
});
}
I can tell the existence when I dd() the request cookies in both the service provider and the controller that handles the cart functionality. For some reason, only during unit tests, the request doesn't contain the cookie yet in the service provider.
Hope this makes sense.
From here: link
Try:
/** #test */
public function get__store_checkout__checkout_displays_database_cart_correctly()
{
$cart = $this->createDatabaseCart();
$cookie = ['cart_id' => Crypt::encrypt($this->cartCookie)];
//#TODO you must get the current request
//#TODO you must set $cookie to $request
//Or simply find a way to create the CartCookie you need using the $cookie from above
$cartCookie = new CartCookie($request);
//hopefully will swap the CartCookie::class instance
app()->instance(CartCookie::class, $cartCookie);
//Now that you have the CartCookie
$response = $this->call('get', route('root.store.checkout'), [
'seller_id' => $cart->seller->id,
], $cookie);
$cart->seller->items()->each(function ($item) use ($response) {
$this->assertContains($beat->item, $response->getContent());
});
}

Change email view path for password reset on Laravel

Using Laravel 5, I need 2 different views for password reset email. The default path to the email view is emails.password. But upon some conditions, I want to send emails.password_alternative.
How can I do this? (with PasswordBroker from Laravel)
This is my current code:
public function __construct(Guard $auth, PasswordBroker $passwords)
{
$this->auth = $auth;
$this->passwords = $passwords;
}
public function sendReset(PasswordResetRequest $request)
{
//HERE : If something, use another email view instead of the default one from the config file
$response = $this->passwords->sendResetLink($request->only('email'), function($m)
{
$m->subject($this->getEmailSubject());
});
}
For anyone interested in Laravel 5.2 you can set a custom html and text email view for password reset by adding
config(['auth.passwords.users.email' => ['auth.emails.password.html', 'auth.emails.password.text']]);
to the PasswordController.php in the constructor before the middleware call.
This overrides the app/config/auth.php setup for the PasswordBroker.
Blade Template for the password reset email is then located at:
yourprojectname/resources/views/auth/emails/password/html.blade.php
yourprojectname/resources/views/auth/emails/password/text.blade.php
Took me long enough.
Credits:
http://ericlbarnes.com/2015/10/14/how-to-send-both-html-and-plain-text-password-reset-emails-in-laravel-5-1/
http://academe.co.uk/2014/01/laravel-multipart-registration-and-reminder-emails/
Using PasswordBroker and based on the Illuminate/Auth/Passwords/PasswordBroker.php class, the $emailView is a protected variable, so you can't change the value once the class is instantiated.
However, you have a couple of solutions:
You can create your own class that extends PasswordBroker and use that.
class MyPasswordBroker extends PasswordBroker {
public function setEmailView($view) {
$this->emailView = $view;
}
}
// (...)
public function __construct(Guard $auth, MyPasswordBroker $passwords)
{
$this->auth = $auth;
$this->passwords = $passwords;
}
public function sendReset(PasswordResetRequest $request)
{
if ($someConditionHere) {
$this->passwords->setEmailView('emails.password_alternative');
}
$response = $this->passwords->sendResetLink($request->only('email'), function($m)
{
$m->subject($this->getEmailSubject());
});
}
You could create the PasswordBroker within your method, without using Dependency Injection.
public function sendReset(PasswordResetRequest $request)
{
$emailView = 'emails.password';
if ($someConditionHere) {
$emailView = 'emails.password_alternative';
}
$passwords = new PasswordBroker(
App::make('TokenRepositoryInterface'),
App::make('UserProvider'),
App::make('MailerContract'),
$emailView
);
$response = $passwords->sendResetLink($request->only('email'), function($m)
{
$m->subject($this->getEmailSubject());
});
}
This is an uglier solution and if you have automated tests this will be a pain to work with.
Disclaimer: I've not tested any of this code.

GuardHelpers not working with customGuard

I have made the following custom guard:
<?php
namespace App\Auth;
use Illuminate\Http\Request;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
class LicenseGuard implements Guard
{
use GuardHelpers;
protected $request;
public function __construct(LicenseUserProvider $provider, Request $request)
{
$this->provider = $provider;
$this->request = $request;
}
public function user ()
{
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (!is_null($this->user))
return $this->user;
$user = null;
$licenseKey = $this->request->json('license_key');
if (!empty($licenseKey)) {
$user = $this->provider->retrieveByLicense($licenseKey);
}
return $this->user = $user;
}
public function validate (Array $credentials = [])
{
/* Validate code */
}
}
?>
In my middleware i have defined the following:
<?php
if($this->auth->guard($guard)->quest())
return response('You have entered an unknown license key', 401);
The error that i am getting is:
Fatal error: Call to undefined method App\Auth\LicenseGuard::quest()
I am using the default GuardHelper trait which has the "quest" method, i just can't find out why this is happening.
I am using PHP7 and Lumen 5.2
Not sure what you are doing there my friend, but I assume quest "isn't the droids you are looking for".

Laravel, Auth and logging out from a model

I'm currently using Laravel 5 Authentification, but I have edited it to allow me to connect to an API server instead of an Eloquent model.
Here is the code of my custom UserProvider:
<?php namespace App\Auth;
use Illuminate\Contracts\Auth\UserProvider as UserProviderInterface;
use WDAL;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Auth\GenericUser;
use Session;
class WolfUserProvider implements UserProviderInterface {
private $_loggedUser;
public function __construct()
{
$this->_loggedUser = null;
$user = Session::get('user');
if (!empty($user)) {
$this->_loggedUser = unserialize($user);
}
}
public function retrieveById($id)
{
return $this->_loggedUser;
}
public function retrieveByToken($identifier, $token)
{
return null;
}
public function updateRememberToken(Authenticatable $user, $token)
{
//dd('updateRememberToken');
}
public function retrieveByCredentials(array $credentials)
{
$user = WDAL::getContactCredentials($credentials['login']);
return $user;
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
if($user->username == $credentials['login'] && $user->password == $credentials['password']){
$this->_loggedUser = $user;
Session::set('user', serialize($user));
return true;
}
else{
return false;
}
}
}
?>
This code might not be perfect as it still in early development ;-) (feel free to suggest me some ideas of improvement if you want to)
So when the user is logged, it has access to the whole platform and to several views and can communicate with the API server to display and edit data.
Sometimes, the API server can return "Invalid Session ID" and when my Model gets this message, the user should be redirected to the login page.
From a Controller it's really easy to handle I can use this code (logout link):
public function getLogout()
{
$this->auth->logout();
Session::flush();
return redirect('/');
}
But do you know how I should proceed from a Model ? I could of course edit all my controllers to check for the value returned by the Model to logout, but cannot it be done thanks to middlewares?
It seems to be really long to edit all my controllers, and this will imply a lot of duplicated code.
One of my tries was to throw an exception from the Controller, and catch in from the auth middleware.
It was not working, because I didn't write use Exception;
I'm now catching the exception, and can now redirect the user from the middleware.
Thank you anyway!

Categories