Skip to content

Commit 65e254f

Browse files
committed
refactor: session based auth
1 parent a293584 commit 65e254f

File tree

13 files changed

+79
-124
lines changed

13 files changed

+79
-124
lines changed

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ APP_LOCALE=en
1717
APP_FALLBACK_LOCALE=en
1818
APP_FAKER_LOCALE=en_US
1919

20-
AUTH_GUARD=api
20+
AUTH_GUARD=web
2121

2222
APP_MAINTENANCE_DRIVER=file
2323
APP_MAINTENANCE_STORE=database
@@ -43,6 +43,8 @@ SESSION_LIFETIME=120
4343
SESSION_ENCRYPT=false
4444
SESSION_PATH=/
4545
SESSION_DOMAIN=null
46+
SANCTUM_STATEFUL_DOMAINS=localhost
47+
SESSION_SECURE_COOKIE=false
4648

4749
BROADCAST_CONNECTION=log
4850
FILESYSTEM_DISK=local

app/Http/Controllers/AuthController.php

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Helpers\Utils;
56
use App\Models\User;
67
use App\Models\UserProvider;
78
use Illuminate\Auth\Events\PasswordReset;
@@ -10,7 +11,7 @@
1011
use Illuminate\Http\JsonResponse;
1112
use Illuminate\Http\RedirectResponse;
1213
use Illuminate\Http\Request;
13-
use Illuminate\Support\Facades\Crypt;
14+
use Illuminate\Support\Facades\Auth;
1415
use Illuminate\Support\Facades\Hash;
1516
use Illuminate\Support\Facades\Password;
1617
use Illuminate\Support\Str;
@@ -112,23 +113,19 @@ public function callback(Request $request, string $provider): View
112113
$user = $userProvider->user;
113114
}
114115

115-
$token = $user->createDeviceToken(
116-
device: $request->deviceName(),
117-
ip: $request->ip(),
118-
remember: true
119-
);
116+
Auth::login($user, true);
117+
$request->session()->regenerate();
120118

121119
return view('oauth', [
122120
'message' => [
123121
'ok' => true,
124122
'provider' => $provider,
125-
'token' => $token,
126123
],
127124
]);
128125
}
129126

130127
/**
131-
* Generate sanctum token on successful login
128+
* Login user
132129
* @throws ValidationException
133130
*/
134131
public function login(Request $request): JsonResponse
@@ -138,23 +135,16 @@ public function login(Request $request): JsonResponse
138135
'password' => ['required', 'string'],
139136
]);
140137

141-
$user = User::select(['id', 'password'])->where('email', $request->email)->first();
142-
143-
if (!$user || !Hash::check($request->password, $user->password)) {
138+
if (!Auth::attempt($request->only('email', 'password'), $request->remember)) {
144139
throw ValidationException::withMessages([
145140
'email' => __('auth.failed'),
146141
]);
147142
}
148143

149-
$token = $user->createDeviceToken(
150-
device: $request->deviceName(),
151-
ip: $request->ip(),
152-
remember: $request->input('remember', false)
153-
);
144+
$request->session()->regenerate();
154145

155146
return response()->json([
156147
'ok' => true,
157-
'token' => $token,
158148
]);
159149
}
160150

@@ -163,7 +153,7 @@ public function login(Request $request): JsonResponse
163153
*/
164154
public function logout(Request $request): JsonResponse
165155
{
166-
$request->user()->currentAccessToken()->delete();
156+
Auth::logout();
167157

168158
return response()->json([
169159
'ok' => true,
@@ -226,7 +216,7 @@ public function resetPassword(Request $request): JsonResponse
226216
{
227217
$request->validate([
228218
'token' => ['required'],
229-
'email' => ['required', 'email', 'exists:'.User::class],
219+
'email' => ['required', 'email', 'exists:' . User::class],
230220
'password' => ['required', 'confirmed', Rules\Password::defaults()],
231221
]);
232222

@@ -287,7 +277,7 @@ public function verificationNotification(Request $request): JsonResponse
287277
'email' => ['required', 'email'],
288278
]);
289279

290-
$user = $request->user()?: User::where('email', $request->email)->whereNull('email_verified_at')->first();
280+
$user = $request->user() ?: User::where('email', $request->email)->whereNull('email_verified_at')->first();
291281

292282
abort_if(!$user, 400);
293283

@@ -306,22 +296,19 @@ public function devices(Request $request): JsonResponse
306296
{
307297
$user = $request->user();
308298

309-
$devices = $user->tokens()
310-
->select('id', 'name', 'ip', 'last_used_at')
311-
->orderBy('last_used_at', 'DESC')
312-
->get();
313-
314-
$currentToken = $user->currentAccessToken();
315-
316-
foreach ($devices as $device) {
317-
$device->hash = Crypt::encryptString($device->id);
299+
$currentSessionId = $request->session()->getId();
318300

319-
if ($currentToken->id === $device->id) {
320-
$device->is_current = true;
321-
}
301+
$devices = $user->sessions()
302+
->select(['id as key', 'ip_address as ip', 'user_agent as name', 'last_activity'])
303+
->orderBy('last_activity', 'DESC')
304+
->get()
305+
->map(function ($device) use ($currentSessionId) {
306+
$device->is_current = $currentSessionId === $device->key;
307+
$device->name = Utils::getDeviceNameFromDetector(Utils::getDeviceDetectorByUserAgent($device->name));
308+
$device->last_used_at = now()->parse($device->last_activity);
322309

323-
unset($device->id);
324-
}
310+
return $device;
311+
});
325312

326313
return response()->json([
327314
'ok' => true,
@@ -330,21 +317,16 @@ public function devices(Request $request): JsonResponse
330317
}
331318

332319
/**
333-
* Revoke token by id
320+
* Disconnect device by id
334321
*/
335322
public function deviceDisconnect(Request $request): JsonResponse
336323
{
337324
$request->validate([
338-
'hash' => 'required',
325+
'key' => 'required|size:40',
339326
]);
340327

341328
$user = $request->user();
342-
343-
$id = (int) Crypt::decryptString($request->hash);
344-
345-
if (!empty($id)) {
346-
$user->tokens()->where('id', $id)->delete();
347-
}
329+
$user->sessions()->where('id', $request->key)->delete();
348330

349331
return response()->json([
350332
'ok' => true,

app/Models/PersonalAccessToken.php

Lines changed: 0 additions & 22 deletions
This file was deleted.

app/Models/Session.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class Session extends Model
8+
{
9+
protected $primaryKey = 'id';
10+
protected $keyType = 'string';
11+
12+
public $timestamps = false;
13+
public $incrementing = false;
14+
15+
protected $casts = [
16+
'payload' => 'string',
17+
'last_activity' => 'integer',
18+
];
19+
20+
protected $hidden = [
21+
'payload',
22+
];
23+
24+
public function user()
25+
{
26+
return $this->belongsTo(User::class);
27+
}
28+
}

app/Models/User.php

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77
use Illuminate\Database\Eloquent\Relations\HasMany;
88
use Illuminate\Foundation\Auth\User as Authenticatable;
99
use Illuminate\Notifications\Notifiable;
10-
use Laravel\Sanctum\HasApiTokens;
1110
use Spatie\Permission\Traits\HasRoles;
1211

1312
class User extends Authenticatable implements MustVerifyEmail
1413
{
15-
use HasApiTokens, HasFactory, HasRoles, Notifiable;
14+
use HasFactory, HasRoles, Notifiable;
1615

1716
/**
1817
* The attributes that are mass assignable.
@@ -56,24 +55,13 @@ public function userProviders(): HasMany
5655
return $this->hasMany(UserProvider::class);
5756
}
5857

59-
public function mustVerifyEmail(): bool
58+
public function sessions(): HasMany
6059
{
61-
return $this instanceof MustVerifyEmail && !$this->hasVerifiedEmail();
60+
return $this->hasMany(Session::class);
6261
}
6362

64-
public function createDeviceToken(string $device, string $ip, bool $remember = false): string
63+
public function mustVerifyEmail(): bool
6564
{
66-
$sanctumToken = $this->createToken(
67-
$device,
68-
['*'],
69-
$remember ?
70-
now()->addMonth() :
71-
now()->addDay()
72-
);
73-
74-
$sanctumToken->accessToken->ip = $ip;
75-
$sanctumToken->accessToken->save();
76-
77-
return $sanctumToken->plainTextToken;
65+
return $this instanceof MustVerifyEmail && !$this->hasVerifiedEmail();
7866
}
7967
}

app/Providers/AppServiceProvider.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace App\Providers;
44

55
use App\Helpers\Image;
6-
use App\Models\PersonalAccessToken;
76
use Illuminate\Auth\Events\Lockout;
87
use Illuminate\Auth\Notifications\ResetPassword;
98
use Illuminate\Auth\Notifications\VerifyEmail;
@@ -14,7 +13,6 @@
1413
use Illuminate\Support\ServiceProvider;
1514
use Illuminate\Support\Str;
1615
use Illuminate\Validation\ValidationException;
17-
use Laravel\Sanctum\Sanctum;
1816

1917
class AppServiceProvider extends ServiceProvider
2018
{
@@ -109,9 +107,5 @@ public function boot(): void
109107
Request::macro('deviceName', function (): string {
110108
return Utils::getDeviceNameFromDetector($this->device());
111109
});
112-
113-
Sanctum::usePersonalAccessTokenModel(
114-
PersonalAccessToken::class
115-
);
116110
}
117111
}

bootstrap/app.php

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,20 @@
88
use Illuminate\Http\Request;
99
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
1010
use Illuminate\Validation\ValidationException;
11+
use Symfony\Component\HttpFoundation\IpUtils;
1112

1213
return Application::configure(basePath: dirname(__DIR__))
1314
->withRouting(
14-
api: __DIR__.'/../routes/api.php',
15+
api: __DIR__ . '/../routes/api.php',
1516
apiPrefix: '',
16-
commands: __DIR__.'/../routes/console.php',
17+
commands: __DIR__ . '/../routes/console.php',
1718
health: '/up',
1819
)
1920
->withMiddleware(function (Middleware $middleware) {
2021
$middleware
2122
->throttleApi(redis: true)
22-
->trustProxies(at: [
23-
'127.0.0.0/8',
24-
'10.0.0.0/8',
25-
'172.16.0.0/12',
26-
'192.168.0.0/16',
27-
])
23+
->trustProxies(at: IpUtils::PRIVATE_SUBNETS)
24+
->statefulApi()
2825
->api(prepend: [
2926
JsonResponse::class,
3027
]);

nuxt/components/auth/Login.vue

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const router = useRouter();
77
const auth = useAuthStore();
88
const form = useTemplateRef<Form<any>>('form');
99
const toast = useToast();
10-
const nuxtApp = useNuxtApp();
1110
1211
const state = reactive({
1312
email: "",
@@ -24,8 +23,6 @@ const { refresh: onSubmit, status: loginStatus } = useHttp<any>("login", {
2423
if (response?.status === 422) {
2524
form.value.setErrors(response._data?.errors);
2625
} else if (response._data?.ok) {
27-
nuxtApp.$token.value = response._data.token;
28-
2926
await auth.fetchUser();
3027
await router.push("/");
3128
}
@@ -39,7 +36,6 @@ async function handleMessage(event: { data: any }): Promise<void> {
3936
4037
if (Object.keys(providers.value).includes(provider) && event.data.token) {
4138
providers.value[provider].loading = false;
42-
nuxtApp.$token.value = event.data.token;
4339
4440
await auth.fetchUser();
4541
await router.push("/");

nuxt/pages/account/devices.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const items = (row: any) => [
3131
await $http("devices/disconnect", {
3232
method: "POST",
3333
body: {
34-
hash: row.hash,
34+
key: row.key,
3535
},
3636
async onFetchResponse({ response }) {
3737
if (response._data?.ok) {

0 commit comments

Comments
 (0)