Skip to content

Commit cfd7fd2

Browse files
authored
Merge pull request #69 from laravelcm/setup-sanctum-api-login
👽 Mis en place de l'api de connexion avec Sanctum
2 parents 7bcf891 + 86c42a9 commit cfd7fd2

File tree

10 files changed

+313
-1
lines changed

10 files changed

+313
-1
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\Auth;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Http\Requests\Api\LoginRequest;
7+
use App\Http\Resources\AuthenticateUserResource;
8+
use App\Models\User;
9+
use Carbon\Carbon;
10+
use Illuminate\Http\JsonResponse;
11+
use Illuminate\Http\Request;
12+
use Illuminate\Support\Facades\Auth;
13+
use Illuminate\Validation\ValidationException;
14+
15+
class LoginController extends Controller
16+
{
17+
public function login(LoginRequest $request): JsonResponse
18+
{
19+
$user = User::query()
20+
->with(['roles', 'permissions'])
21+
->where('email', strtolower($request->input('email')))
22+
->first();
23+
24+
$sanitized = [
25+
'email' => strtolower($request->input('email')),
26+
'password' => $request->input('password'),
27+
];
28+
29+
if (empty($user) || !Auth::attempt($sanitized)) {
30+
throw ValidationException::withMessages([
31+
'email' => 'Les informations d\'identification fournies sont incorrectes.',
32+
]);
33+
}
34+
35+
if (! empty($user->tokens())) {
36+
$user->tokens()->delete();
37+
}
38+
39+
$user->last_login_at = Carbon::now();
40+
$user->last_login_ip = $request->ip();
41+
$user->save();
42+
43+
return response()->json([
44+
'user' => new AuthenticateUserResource($user),
45+
'token' => $user->createToken($request->input('email'))->plainTextToken,
46+
'roles' => $user->roles()->pluck('name'),
47+
'permissions' => $user->permissions()->pluck('name'),
48+
]);
49+
}
50+
51+
public function logout(Request $request): JsonResponse
52+
{
53+
if ($request->user()->currentAccessToken()) {
54+
$request->user()->currentAccessToken()->delete();
55+
}
56+
57+
return response()->json(['message' => 'Déconnecté avec succès']);
58+
}
59+
}

app/Http/Kernel.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class Kernel extends HttpKernel
4141
],
4242

4343
'api' => [
44+
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
4445
'throttle:api',
4546
\Illuminate\Routing\Middleware\SubstituteBindings::class,
4647
],
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace App\Http\Requests\Api;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
class LoginRequest extends FormRequest
8+
{
9+
/**
10+
* Determine if the user is authorized to make this request.
11+
*
12+
* @return bool
13+
*/
14+
public function authorize(): bool
15+
{
16+
return true;
17+
}
18+
19+
/**
20+
* Get the validation rules that apply to the request.
21+
*
22+
* @return array<string, mixed>
23+
*/
24+
public function rules(): array
25+
{
26+
return [
27+
'email' => ['required', 'email', 'exists:users,email'],
28+
'password' => ['required'],
29+
];
30+
}
31+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace App\Http\Resources;
4+
5+
use App\Models\IdeHelperUser;
6+
use Illuminate\Http\Resources\Json\JsonResource;
7+
8+
/**
9+
* @mixin IdeHelperUser
10+
*/
11+
class AuthenticateUserResource extends JsonResource
12+
{
13+
/**
14+
* Transform the resource into an array.
15+
*
16+
* @param \Illuminate\Http\Request $request
17+
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
18+
*/
19+
public function toArray($request): array
20+
{
21+
return [
22+
'id' => $this->id,
23+
'name' => $this->name,
24+
'email' => $this->email,
25+
'username' => $this->username,
26+
'bio' => $this->bio,
27+
'profilePhotoUrl' => $this->profile_photo_url,
28+
'phoneNumber' => $this->phone_number,
29+
'optIn' => $this->opt_in,
30+
'settings' => $this->settings,
31+
'reputation' => $this->reputation,
32+
33+
'lastLoginAt' => $this->last_login_at,
34+
'emailVerifiedAt' => $this->email_verified_at,
35+
'createdAt' => $this->created_at,
36+
'updatedAt' => $this->updated_at,
37+
];
38+
}
39+
}

app/Models/User.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Illuminate\Notifications\Notifiable;
1414
use Illuminate\Notifications\Notification;
1515
use Illuminate\Support\Facades\Auth;
16+
use Laravel\Sanctum\HasApiTokens;
1617
use QCod\Gamify\Gamify;
1718
use Rinvex\Subscriptions\Traits\HasPlanSubscriptions;
1819
use Spatie\MediaLibrary\HasMedia;
@@ -28,6 +29,7 @@ class User extends Authenticatable implements MustVerifyEmail, HasMedia
2829
use HasFactory;
2930
use HasPlanSubscriptions;
3031
use HasProfilePhoto;
32+
use HasApiTokens;
3133
use HasRoles;
3234
use InteractsWithMedia;
3335
use Notifiable;

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"laravel-notification-channels/twitter": "^6.0",
2424
"laravel/fortify": "^1.13",
2525
"laravel/framework": "^9.24",
26+
"laravel/sanctum": "^3.0",
2627
"laravel/slack-notification-channel": "^2.4",
2728
"laravel/socialite": "^5.2",
2829
"laravel/tinker": "^2.5",

composer.lock

Lines changed: 66 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/sanctum.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
use Laravel\Sanctum\Sanctum;
4+
5+
return [
6+
7+
/*
8+
|--------------------------------------------------------------------------
9+
| Stateful Domains
10+
|--------------------------------------------------------------------------
11+
|
12+
| Requests from the following domains / hosts will receive stateful API
13+
| authentication cookies. Typically, these should include your local
14+
| and production domains which access your API via a frontend SPA.
15+
|
16+
*/
17+
18+
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
19+
'%s%s',
20+
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
21+
Sanctum::currentApplicationUrlWithPort()
22+
))),
23+
24+
/*
25+
|--------------------------------------------------------------------------
26+
| Sanctum Guards
27+
|--------------------------------------------------------------------------
28+
|
29+
| This array contains the authentication guards that will be checked when
30+
| Sanctum is trying to authenticate a request. If none of these guards
31+
| are able to authenticate the request, Sanctum will use the bearer
32+
| token that's present on an incoming request for authentication.
33+
|
34+
*/
35+
36+
'guard' => ['web'],
37+
38+
/*
39+
|--------------------------------------------------------------------------
40+
| Expiration Minutes
41+
|--------------------------------------------------------------------------
42+
|
43+
| This value controls the number of minutes until an issued token will be
44+
| considered expired. If this value is null, personal access tokens do
45+
| not expire. This won't tweak the lifetime of first-party sessions.
46+
|
47+
*/
48+
49+
'expiration' => null,
50+
51+
/*
52+
|--------------------------------------------------------------------------
53+
| Sanctum Middleware
54+
|--------------------------------------------------------------------------
55+
|
56+
| When authenticating your first-party SPA with Sanctum you may need to
57+
| customize some of the middleware Sanctum uses while processing the
58+
| request. You may change the middleware listed below as required.
59+
|
60+
*/
61+
62+
'middleware' => [
63+
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
64+
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
65+
],
66+
67+
];
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*
12+
* @return void
13+
*/
14+
public function up()
15+
{
16+
Schema::create('personal_access_tokens', function (Blueprint $table) {
17+
$table->id();
18+
$table->morphs('tokenable');
19+
$table->string('name');
20+
$table->string('token', 64)->unique();
21+
$table->text('abilities')->nullable();
22+
$table->timestamp('last_used_at')->nullable();
23+
$table->timestamp('expires_at')->nullable();
24+
$table->timestamps();
25+
});
26+
}
27+
28+
/**
29+
* Reverse the migrations.
30+
*
31+
* @return void
32+
*/
33+
public function down()
34+
{
35+
Schema::dropIfExists('personal_access_tokens');
36+
}
37+
};

routes/api.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use App\Http\Controllers\Api\Auth\LoginController;
34
use App\Http\Controllers\Api\ReplyController;
45
use App\Http\Controllers\Api\PremiumController;
56
use Illuminate\Support\Facades\Route;
@@ -22,3 +23,12 @@
2223
Route::delete('replies/{id}', [ReplyController::class, 'delete']);
2324

2425
Route::get('premium-users', [PremiumController::class, 'users']);
26+
27+
28+
/** Authentication Routes */
29+
Route::post('login', [LoginController::class, 'login']);
30+
31+
/* Authenticated Routes */
32+
Route::middleware('auth:sanctum')->group(function () {
33+
Route::post('logout', [LoginController::class, 'logout']);
34+
});

0 commit comments

Comments
 (0)