Laravel Testing assertRedirect for unauthenticated user - php

I write here after many attempts but my problem isn't solved yet.
I want to create a test using PHPUnit on Laravel my class has function described like below:
public function test_not_connected_user_can_not_create_new_task() {
$this->withoutExceptionHandling();
//Given we have a task object
$task = Task::factory()->make();
// When unauthenticated user submits post request to create task endpoint
// He should be redirected to login page
$this->post('/tasks/store',$task->toArray())
->assertRedirect(route('login'));
}
Here is my route:
Route::post('/tasks/store', [App\Http\Controllers\TaskController::class, 'store'])
->name('store');
And my controller functions:
public function __construct() {
$this->middleware('auth')->except(['index','show']);
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request) {
$task = Task::create([
'title' => $request->get('title'),
'description' => $request->get('description'),
'user_id' => Auth::id()
]);
return redirect()->route('show', [$task->id]);
}
We have a middleware here to manage authentication.
When I run the test:
vendor/bin/phpunit --filter test_connected_user_can_create_new_task
I get this error:
Tests\Feature\TasksTest::test_not_connected_user_can_not_create_new_task
Illuminate\Auth\AuthenticationException: Unauthenticated.
And it's pointing to this line:
$this->post('/tasks/store', $task->toArray())
The expected behavior is that it should redirect to login but here the test fail and I can't see why.
Thanks

The issue is super easy to fix. You have $this->withoutExceptionHandling(); and that is literally throwing the error, what you want is to catch it and let everything continue. To do so, remove that line of code and your test will work.

Related

Laravel 8 - Multi Auth with two diffirent registration form?

I'm using laravel breeze as auth scaffolding package I want to know How can I
create two diffirent registration form for two User Types here is a simple explanation of hwat I want to achieve:
resources/auth/developer :
developer-register.blade.php
resources/auth/designer :
designer-register.blade.php
if the Visitor choose to register as "developer" it will display a diffirent form. and same thing for if the Visitor choose to register as "designer" it will display a diffirent form with fields.
I wish you understand what I want to achieve with this easy explanation.
Ok, so i've not used laravel/breeze myself (yet) but it shouldn't be much different from doing it in standard Laravel!
Views
By default, it looks like the breeze scaffolding is going to hit a create() method on the RegisteredUserController which will return a single view like so:
RegisteredUserController.php
/**
* Display the registration view.
*
* #return \Illuminate\View\View
*/
public function create()
{
return view('auth.register');
}
You have a few options here:
Replace this view with another
Add some logic to change the view which is returned based on the request being made (you can inject a Request object into the route like any other)
public function create(Request $request)
{
if ($request->has('developer')) {
return view('auth.developer-register');
} else {
return view('auth.designer-register');
}
}
Keep the original auth.register view and handle the logic in the blade template.
Registration
The forms on each of your registration pages will have an action that points to a controller route. This will likely be the RegisteredUserController within which you will find a store() method that handles the creation of a User model.
RegisteredUserController.php
/**
* Handle an incoming registration request.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse
*
* #throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|confirmed|min:8',
]);
Auth::login($user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]));
event(new Registered($user));
return redirect(RouteServiceProvider::HOME);
}
As you can see, this store() method is handling the creation of a User model and then authenticating it before redirecting the user to the home route.
What you could do, is check the request for the the requested user type and then use a switch statement to change the type of use being created.
switch ($request->get('user_type'))
case 'developer':
$user = Developer::create([ /* add details here */ ]);
break;
case 'designer':
$user = Designer::create([ /* add details here */ ]);
break;
Auth::login($user);
I hope this will at least inspire you with your own solution!

Laravel listener only sends when I dd a variable in the class being called

I have a custom class in Laravel that tracks the analytics of my app through Segment (using this package for php: https://github.com/AltThree/Segment).
Here is a snippet of my class and a function I am calling through my listener to track a login:
class Tracking {
private function segmentTrack(User $user, string $event, array $properties = null) {
$segment = Segment::track([
"userId" => $user->id,
"event" => $event,
"properties" => $properties
]);
dd($segment);
}
/**
* Handle Login tracking
*
* #param User $user
* #return void
*/
public function login (User $user) {
$this->segmentTrack($user, "Login");
}
}
Notice the dd in the segmentTrack function. When I run the Laravel queue and I then trigger the Tracking->login() event through my app, the listener goes off fine and with the dd function, it will send that data to Segment and I can see it in their live debugger, all is well.
However, when I remove that dd, and the listener goes off and shows as successful - the data is never seen in Segment.
Can someone tell me what i'm missing? This is my first time using the Laravel queue system so a little confused why it might not be working.
For queued jobs, use:
Segment::track($payload);
Segment::flush();

creation of unit tests with users

I want to test the next method of my controller
function index(){
if(Auth::User()->can('view_roles'))
{
$roles = Role::all();
return response()->json(['data' => $roles], 200);
}
return response()->json(['Not_authorized'], 401);
}
it is already configured for authentication (tymondesigns / jwt-auth) and the management of roles (spatie / laravel-permission), testing with postman works, I just want to do it in an automated way.
This is the test code, if I remove the conditional function of the controller the TEST passes, but I would like to do a test using a user but I have no idea how to do it.
public function testIndexRole()
{
$this->json('GET', '/role')->seeJson([
'name' => 'admin',
'name' => 'secratary'
]);
}
Depends on what kind of app are you building.
A - Using Laravel for the entire app
If your using Laravel for frontend/backend, well to simulate a logged-in user you could use the awesome Laravel Dusk package, made by the Laravel team. You can check the documentation here.
This package has some helpful methods to mock login sessions amongs a lot more of other things, you can use:
$this->browse(function ($first, $second) {
$first->loginAs(User::find(1))
->visit('/home');
});
That way you hit an endpoint with a logged-in user of id=1. And a lot more of stuff.
B - Using Laravel as a backend
Now, this is mainly how I use Laravel.
To identify a user that hits an endpoint, the request must send an access_token. This token helps your app to identify the user. So, you will need to make and API call to that endpoint attaching the token.
I made a couple of helper functions to simply reuse this in every Test class. I wrote a Utils trait that is being used in the TestCase.php and given this class is extended by the rest of the Test classes it will be available everywhere.
1. Create the helper methods.
path/to/your/project/ tests/Utils.php
Trait Utils {
/**
* Make an API call as a User
*
* #param $user
* #param $method
* #param $uri
* #param array $data
* #param array $headers
* #return TestResponse
*/
protected function apiAs($user, $method, $uri, array $data = [], array $headers = []): TestResponse
{
$headers = array_merge([
'Authorization' => 'Bearer ' . \JWTAuth::fromUser($user),
'Accept' => 'application/json'
], $headers);
return $this->api($method, $uri, $data, $headers);
}
protected function api($method, $uri, array $data = [], array $headers = [])
{
return $this->json($method, $uri, $data, $headers);
}
}
2. Make them available.
Then in your TestCase.php use the trait:
path/to/your/project/tests/TestCase.php
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, Utils; // <-- note `Utils`
// the rest of the code
3. Use them.
So now you can do API calls from your test methods:
/**
* #test
* Test for: Role index
*/
public function a_test_for_role_index()
{
/** Given a registered user */
$user = factory(User::class)->create(['name' => 'John Doe']);
/** When the user makes the request */
$response = $this->apiAs($user,'GET', '/role');
/** Then he should see the data */
$response
->assertStatus(200)
->assertJsonFragment(['name' => 'admin'])
->assertJsonFragment(['name' => 'secretary']);
}
Side note
check that on top of the test methods there is a #test annotation, this indicates Laravel that the method is a test. You can do this or prefix your tests names with test_

Listening for Auth events in Dingo API laravel package

I used Dingo API package to create an api like Telegram api.
In the first step User sends some parameters , then I verify those and if all things was true user informations included a Token returned. like this :
public function signIn (Request $request)
{
$phone_number = $request->get('phone_number');
$phone_code_hash = $request->get('phone_code_hash');
$phone_code = $request->get('phone_code');
if ($this->verifyCode($phone_code_hash, $phone_code, $phone_number)) {
$user = User::where('phone_number', $phone_number)->firstOrFail();
$user->injectToken();
return $this->response->item($user, new UserTransformer);
} else {
return [
'success' => false,
'type' => 'PHONE_NUMBER_IS_NOT_REGISTERED',
'code' => 703,
'message' => 'Phone Code Is Correct But Phone Number Is Not Registered.'
];
}
}
As you can see signIn method has user authentication duty.
Now I do not know how can I listen to authentication events like Illuminate\Auth\Events\Authenticated ,Illuminate\Auth\Events\Login and etc described here.
In fact I want to run an event whenever user login was successfull.
I'm using larvel 5.3.
Any solution ؟
These events are not supported out of the box by Dingo.
You may however, create a custom middleware to fire an Illuminate\Auth\Events\Authenticated event on success like so:
app/Http/Middleware/Auth.php
namespace App\Http\Middleware;
use Dingo\Api\Http\Middleware\Auth as BaseAuth;
use Closure;
class Auth extends BaseAuth {
/**
* Perform authentication before a request is executed.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
*
* #return mixed
*/
public function handle($request, Closure $next)
{
$route = $this->router->getCurrentRoute();
if (! $this->auth->check(false)) {
$this->auth->authenticate($route->getAuthenticationProviders());
event(new \Illuminate\Auth\Events\Authenticated($this->auth->getUser());
}
return $next($request);
}
}
Finally, just register and add this middleware to your routes

Symfony Custom UserProvider Auth + Parse SDK

Heyo!
I know it's a common problem people having problems with custom providers and web service authentication. I'm spending hours trying to figure out how to do that but I'm almost freaking out.
So, the thing is: I'm using the Symfony Firewalls with a custom UserProvider and a AbstractGuardAuthenticator as well. The problem is in the loadUserByUsername($username) function inside the UserProvider implementation.
For security reasons I can't retrieve the user password from Parse (my web service), and the loadUserByUsername($username) function asks for that. Even in the documentation about how to create a custom user provider using web services they are retrieving the user password from the database.
So what's the solution in that case? What can I do when I don't have access to the user password?
My current code is something like that:
$app['app.authenticator'] = function () {
return new Authenticator($app);
};
$app['security.firewalls'] = array(
'login' => array(
'pattern' => '^/login/$',
),
'secured' => array(
'pattern' => '^.*$',
'form' => array('login_path' => '/login/', 'check_path' => '/login/auth/'),
'logout' => array('logout_path' => '/logout/', 'invalidate_session' => true),
'guard' => array(
'authenticators' => array(
'app.authenticator'
),
),
'users' => function () use ($app) {
return new UserProvider($app);
},
)
);
The Authenticator.php is quite big code because extends the AbstractGuardAuthenticator class. But I'm basically using this one from Symfony docs. The only thing Is that I'm sending to the UserProvider class the username AND the password as well, because that way I can check if the user and password are right. Like this:
public function getUser($credentials, UserProviderInterface $userProvider) {
return $userProvider->loadUserByUsername($credentials);
}
And my UserProvider class is the default one, I'm just checking inside the loadUserByUsername function if the credentials comming from my Authenticator are right. Something like this:
public function loadUserByUsername($credentials) {
$encoder = new BCryptPasswordEncoder(13);
try {
$user = ParseUser::logIn($credentials['username'], $credentials['password']);
} catch (ParseException $error) {
throw new UsernameNotFoundException(sprintf('Invalid Credentials.'));
}
return new User($credentials['username'], $encoder->encodePassword($credentials['password'], ''), explode(',', 'ROLE_USER'), true, true, true, true);
}
The problem is: after the login (everything with the login is working fine), Silex calls the loadUserByUsername function in every page which needs to be secured, but just sending the username parameter.
So basically, I don't know what to do guys. I'm really trying to figure out how to get this thing working.
Thanks for your help!
I have a similar implementation and this issue is well known. In my user provider I have the methods loadUserByUsername($username) and refreshUser(UserInterface $user). Since I have the same issue like you, I don't check the user in loadUserByUsername but simple return a new Object with only the username in it to not disturb the flow. loadUserByUsername doesn't make sense for external APIs, so I simply jump over it. The method refreshUser is either beeing called on every request, this is usefull.
In your AbstractGuardAuthenticator you have the method createAuthenticatedToken, which returns an token. There you should have the full authentificated user:
abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface
{
/**
* Shortcut to create a PostAuthenticationGuardToken for you, if you don't really
* care about which authenticated token you're using.
*
* #param UserInterface $user
* #param string $providerKey
*
* #return PostAuthenticationGuardToken
*/
public function createAuthenticatedToken(UserInterface $user, $providerKey)
{
//do login stuff
//save password in user
return new PostAuthenticationGuardToken(
$user,
$providerKey,
$user->getRoles()
);
}
}
Then, I would't use loadUserByUsername but refreshUser instead. Both are called on every request:
/**
* Don't use
* #codeCoverageIgnore
* #param string $username
* #return User
*/
public function loadUserByUsername($username)
{
return new User($username, null, '', ['ROLE_USER'], '');
}
/**
* Refresh user on every subrequest after login
* #param UserInterface $user
* #return User
*/
public function refreshUser(UserInterface $user)
{
$password = $user->getPassword();
$username = $user->getUsername();
//login check
}

Categories