Skip to content

Commit

Permalink
Add expires_at functionality to tokens.
Browse files Browse the repository at this point in the history
  • Loading branch information
Bart Hijmans committed Feb 23, 2021
1 parent caf221a commit 5cc7cab
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 3 deletions.
Expand Up @@ -20,6 +20,7 @@ public function up()
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/Guard.php
Expand Up @@ -65,6 +65,8 @@ public function __invoke(Request $request)
if (! $accessToken ||
($this->expiration &&
$accessToken->created_at->lte(now()->subMinutes($this->expiration))) ||
($accessToken->expires_at &&
$accessToken->expires_at->isPast()) ||
! $this->hasValidProvider($accessToken->tokenable)) {
return;
}
Expand Down
5 changes: 4 additions & 1 deletion src/HasApiTokens.php
Expand Up @@ -2,6 +2,7 @@

namespace Laravel\Sanctum;

use Illuminate\Support\Carbon;
use Illuminate\Support\Str;

trait HasApiTokens
Expand Down Expand Up @@ -39,14 +40,16 @@ public function tokenCan(string $ability)
*
* @param string $name
* @param array $abilities
* @param Carbon|null $expires_at
* @return \Laravel\Sanctum\NewAccessToken
*/
public function createToken(string $name, array $abilities = ['*'])
public function createToken(string $name, array $abilities = ['*'], Carbon $expires_at = null)
{
$token = $this->tokens()->create([
'name' => $name,
'token' => hash('sha256', $plainTextToken = Str::random(40)),
'abilities' => $abilities,
'expires_at' => $expires_at,
]);

return new NewAccessToken($token, $token->id.'|'.$plainTextToken);
Expand Down
2 changes: 2 additions & 0 deletions src/PersonalAccessToken.php
Expand Up @@ -15,6 +15,7 @@ class PersonalAccessToken extends Model implements HasAbilities
protected $casts = [
'abilities' => 'json',
'last_used_at' => 'datetime',
'expires_at' => 'datetime',
];

/**
Expand All @@ -26,6 +27,7 @@ class PersonalAccessToken extends Model implements HasAbilities
'name',
'token',
'abilities',
'expires_at'
];

/**
Expand Down
80 changes: 80 additions & 0 deletions tests/GuardTest.php
Expand Up @@ -120,6 +120,86 @@ public function test_authentication_with_token_fails_if_expired()
$this->assertNull($user);
}

public function test_authentication_with_token_fails_if_expires_at_has_passed()
{
$this->loadLaravelMigrations(['--database' => 'testbench']);
$this->artisan('migrate', ['--database' => 'testbench'])->run();

$factory = Mockery::mock(AuthFactory::class);

$guard = new Guard($factory, null, 'users');

$webGuard = Mockery::mock(stdClass::class);

$factory->shouldReceive('guard')
->with('web')
->andReturn($webGuard);

$webGuard->shouldReceive('user')->once()->andReturn(null);

$request = Request::create('/', 'GET');
$request->headers->set('Authorization', 'Bearer test');

$user = User::forceCreate([
'name' => 'Taylor Otwell',
'email' => 'taylor@laravel.com',
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'remember_token' => Str::random(10),
]);

$token = PersonalAccessToken::forceCreate([
'tokenable_id' => $user->id,
'tokenable_type' => get_class($user),
'name' => 'Test',
'token' => hash('sha256', 'test'),
'expires_at' => now()->subMinutes(60),
]);

$user = $guard->__invoke($request);

$this->assertNull($user);
}

public function test_authentication_with_token_succeeds_if_expires_at_not_passed()
{
$this->loadLaravelMigrations(['--database' => 'testbench']);
$this->artisan('migrate', ['--database' => 'testbench'])->run();

$factory = Mockery::mock(AuthFactory::class);

$guard = new Guard($factory, null, 'users');

$webGuard = Mockery::mock(stdClass::class);

$factory->shouldReceive('guard')
->with('web')
->andReturn($webGuard);

$webGuard->shouldReceive('user')->once()->andReturn(null);

$request = Request::create('/', 'GET');
$request->headers->set('Authorization', 'Bearer test');

$user = User::forceCreate([
'name' => 'Taylor Otwell',
'email' => 'taylor@laravel.com',
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'remember_token' => Str::random(10),
]);

$token = PersonalAccessToken::forceCreate([
'tokenable_id' => $user->id,
'tokenable_type' => get_class($user),
'name' => 'Test',
'token' => hash('sha256', 'test'),
'expires_at' => now()->addMinutes(60),
]);

$user = $guard->__invoke($request);

$this->assertNull($user);
}

public function test_authentication_is_successful_with_token_if_no_session_present()
{
$this->loadLaravelMigrations(['--database' => 'testbench']);
Expand Down
11 changes: 9 additions & 2 deletions tests/HasApiTokensTest.php
Expand Up @@ -2,18 +2,20 @@

namespace Laravel\Sanctum\Tests;

use Illuminate\Support\Carbon;
use Laravel\Sanctum\HasApiTokens;
use Laravel\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\TransientToken;
use PHPUnit\Framework\TestCase;
use Orchestra\Testbench\TestCase;

class HasApiTokensTest extends TestCase
{
public function test_tokens_can_be_created()
{
$class = new ClassThatHasApiTokens;
$time = Carbon::now();

$newToken = $class->createToken('test', ['foo']);
$newToken = $class->createToken('test', ['foo'], $time);

[$id, $token] = explode('|', $newToken->plainTextToken);

Expand All @@ -26,6 +28,11 @@ public function test_tokens_can_be_created()
$newToken->accessToken->id,
$id
);

$this->assertEquals(
$time->toDateTimeString(),
$newToken->accessToken->expires_at->toDateTimeString()
);
}

public function test_can_check_token_abilities()
Expand Down

0 comments on commit 5cc7cab

Please sign in to comment.