tymondesigns / jwt-auth

🔐 JSON Web Token Authentication for Laravel & Lumen
https://jwt-auth.com
MIT License
11.31k stars 1.54k forks source link

actingAs() does not set a token #1912

Open sdalmeida opened 4 years ago

sdalmeida commented 4 years ago

Subject of the issue

When using "$this->actingAs($user)" from "browser-kit-testing" (https://github.com/laravel/browser-kit-testing), the "Auth::guard()->setUser($user)" function is called, but the token is not generated nor is it set

image

As you can see in the image above, actingAs() will call be() with calls Auth::guard()->setUser($user). This only sets the user, but not the token.

A way around this issue, is to manually set the token after the user has been set

image

Your environment

Q A
Bug? yes
New Feature? no
Framework Laravel
Framework version 5.8
Package version 1.0.0-rc.5
PHP version 7.2.24

Steps to reproduce

Install phpunit test and browser-kit-testing. Create a test function and call "$this->actingAs($user)"

Expected behaviour

The user and token should be set.

Actual behaviour

The token is not set. When calling "Auth::payload()", an exception is thrown because there are no tokens set (even though the user is)

sdalmeida commented 4 years ago

Ideally, setUser() should also set the token. @tymondesigns is this the expected behaviour?

specialtactics commented 4 years ago

You can just override these functions to do it yourself. For example;

    public function actingAsUser($credentials)
    {
        $token = $this->loginUsingCredentials($credentials);

        $this->withHeaders(['Authorization' => 'Bearer ' . $token]);

        return $this;
    }

    public function loginUsingCredentials($credentials)
    {
        $token = \JWTAuth::attempt($credentials);

        $this::$auth = ['Authorization' => 'Bearer ' . $token];

        return $token;
    }

You can do the same for user object I'm pretty sure, there is a function called fromUser

williamdes commented 4 years ago

$token = JWTAuth::fromUser($user);

asjamsuri commented 4 years ago

As @specialtactics said, it worked for me with a little bit modifications (maybe due to different version usage).

I'm currently using "tymon/jwt-auth": "^1.0", and since JWTAuth::fromUser is now inaccessible, so I modify it to:

public function actingAsUser()
{
    $password = 'some-password';

    // every generated e-mail will be accepted
    $user = factory(User::class)->create([
        'password' => bcrypt($password)
    ]);

    $token = auth('api')->attempt([
        'email' => $user->email,
        'password' => $password
    ]);

    $this->withHeaders(
        array_merge([
            $this->defaultHeaders,
            ['Authorization' => 'Bearer ' . $token]
        ])
    );

    return $this;
}

And it could be used like:

$this->actingAsUser()->json('....')

Didn't know if it was a good practice or not. But it worked for me.

Thanks to @specialtactics !

agm1984 commented 4 years ago

I added a comment related to this, in this thread: https://github.com/tymondesigns/jwt-auth/issues/1246

For me JWTAuth::fromUser($user) did work.

stale[bot] commented 3 years ago

Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

derekrprice commented 3 years ago

None of these workarounds are working for me. Anyone have any other suggestions?

derekrprice commented 3 years ago

Used a mix of the above suggestions and a blog I stumbled on to get it working this way, in a single test:

public function testCanLoginAndLogout()
{
    $client = \App\Client::where(['password_client' => 1])->first();
    $response = $this->json("POST", '/oauth/token', [
        'client_secret' => $client->secret,
        'client_id' => $client->id,
        'grant_type' => 'password',
        'username' => $this->user->email,
        'password' => 'secret',
    ])->assertStatus(200);

    $token = $response->json()['access_token'];
    $this->withHeaders(['Authorization' => 'Bearer ' . $token]);

    $this->assertNotEmpty($this->user->tokens()->get());
    $this->assertDatabaseHas('oauth_access_tokens', ['revoked' => 0]);
    $this->assertDatabaseHas('oauth_refresh_tokens', ['revoked' => 0]);

    $this->json('POST', "{$this->base_path}/auth/logout")
        ->assertStatus(200);

    $this->assertDatabaseMissing('oauth_access_tokens', ['revoked' => 0]);
    $this->assertDatabaseMissing('oauth_refresh_tokens', ['revoked' => 0]);
}
derekrprice commented 3 years ago

Managed something even easier, as long as you don't need to parse the JWT. Also, fixes all tests that need tokens:

use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Laravel\Passport\Token;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

    /**
     * @inheritDoc
     *
     * If we don't add a token, our extended auth middleware chokes during tests because it
     * expects a $user->token().
     */
    public function actingAs(UserContract $user, $driver = null)
    {
        parent::actingAs($user, $driver);
        $user->withAccessToken(new Token());
        return $this;
    }
}