rtrim( strtr(base64_encode(json_encode($data, JSON_UNESCAPED_SLASHES) ?: ''), '+/', '-_'), '=' ); return $encode(['alg' => 'none', 'typ' => 'JWT']).'.'.$encode($claims).'.'; } } it('upserts user by tid+oid and regenerates the session on success', function () { config()->set('services.microsoft.client_id', 'test-client'); config()->set('services.microsoft.client_secret', 'test-secret'); config()->set('services.microsoft.redirect', 'http://localhost/auth/entra/callback'); config()->set('services.microsoft.tenant', 'organizations'); $state = 'state-123'; $tokenBefore = 'token-before'; Http::fake([ 'https://login.microsoftonline.com/*/oauth2/v2.0/token' => Http::response([ 'id_token' => entra_build_jwt([ 'tid' => 'tenant-1', 'oid' => 'object-1', 'preferred_username' => 'user@example.com', 'name' => 'Test User', ]), ]), ]); $response = $this ->withSession(['entra_state' => $state, '_token' => $tokenBefore]) ->get(route('auth.entra.callback', ['code' => 'code-123', 'state' => $state])); $response->assertRedirect('/admin/no-access'); $user = User::query() ->where('entra_tenant_id', 'tenant-1') ->where('entra_object_id', 'object-1') ->firstOrFail(); expect($user->email)->toBe('user@example.com'); expect($user->name)->toBe('Test User'); expect(session('entra_state'))->toBeNull(); expect(session()->token())->not->toBe($tokenBefore); });