Laravel Sanctum provides an authentication system for SPAs (single page applications), mobile applications, and simple, token based APIs. Sanctum allows each user of your application to generate multiple API tokens for their account. These tokens may be granted abilities / scopes which specify which actions the tokens are allowed to perform.
folders
App->[
Http->[
Controllers\Api\Auth->[
AuthController,
OTPController,
PasswordResetController
],Requests\Auth->[
LoginUserRequest,
RegisterUserRequest,
ResetPasswordRequest,
SendOTPRequest,
VerifyOTPRequest
],Resources->[
SuccessResource,
ErrorResource,
User->[
AuthResource,
UserResource]
],Jobs->[SendMailJob],
Mail->[OTPMail]
1: Install Laravel 12 and Sanctum
# Create new Laravel 12 project
laravel new sanctum-auth
cd sanctum-auth
# Install Sanctum
php artisan install:api
# Run migrations (creates personal_access_tokens table)
php artisan migrate
2: Configure Sanctum
config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort(),
))),
Stateful domains tell Sanctum which domains can use cookies and sessions for authentication, allowing the frontend to communicate with the API seamlessly!
.env
SANCTUM_STATEFUL_DOMAINS=localhost,127.0.0.1,127.0.0.1:3000
3: Update User Model
- app/Models/User.php
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
'phone',
'email_verified_at',
'otp',
'otp_expires_at',
'otp_verified_at'
];
protected $hidden = [
'password',
'remember_token',
'otp'
];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'otp_expires_at' => 'datetime',
'otp_verified_at' => 'datetime',
];
}
}
4: Create Auth Controller, Register And Login Request, Auth And User Resource
Auth Controller
php artisan make:controller Api/Auth/AuthController
- app/Http/Controllers/AuthController.php
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginUserRequest;
use App\Http\Requests\Auth\RegisterUserRequest;
use App\Http\Resources\User\AuthResource;
use App\Http\Resources\ErrorResource;
use App\Http\Resources\SuccessResource;
use App\Http\Resources\User\UserResource;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class AuthController extends Controller
{
public function register(RegisterUserRequest $request)
{
$validated = $request->validated();
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
'phone' => $validated['phone'],
]);
$token = $user->createToken('auth_token')->plainTextToken;
$authData = [
'user' => $user,
'token' => $token,
'token_type' => 'Bearer'
];
return new AuthResource($authData);
}
public function login(LoginUserRequest $request)
{
$validated = $request->validated();
$user = User::where('email', $validated['email'])->first();
if (!$user || !Hash::check($validated['password'], $user->password)) {
return new ErrorResource([
'message' => 'Invalid credentials',
'status_code' => 401
]);
}
$token = $user->createToken('auth_token')->plainTextToken;
$authData = [
'user' => $user,
'token' => $token,
'token_type' => 'Bearer'
];
return new AuthResource($authData);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return new SuccessResource([
'message' => 'Logged out successfully'
]);
}
//The user() function retrieves the authenticated user's profile data using their Bearer token.
public function user(Request $request)
{
return new SuccessResource([
'message' => 'User data retrieved successfully',
'data' => new UserResource($request->user())
]);
}
}
$token = $user->createToken('auth_token');
return $token;
- the json respone
Create RegisterUserRequest class
php artisan make:request Auth/RegisterUserRequest
-app/Http/Requests/Auth/RegisterUserRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class RegisterUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // Set to true to allow all users
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
'phone' => 'required|string|max:20'
];
}
/**
* Get custom messages for validator errors.
*
* @return array<string, string>
*/
public function messages(): array
{
return [
'name.required' => 'The name field is required.',
'name.string' => 'The name must be a string.',
'name.max' => 'The name may not be greater than 255 characters.',
'email.required' => 'The email field is required.',
'email.email' => 'Please enter a valid email address.',
'email.unique' => 'This email is already registered.',
'password.required' => 'The password field is required.',
'password.min' => 'The password must be at least 8 characters.',
'password.confirmed' => 'Password confirmation does not match.',
'phone.required' => 'The phone field is required.',
'phone.string' => 'The phone must be a string.',
'phone.max' => 'The phone may not be greater than 20 characters.',
];
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(
response()->json([
'status' => 'error',
'message' => 'Validation failed',
'errors' => $validator->errors()
], 422)
);
}
}
Login Request
-php artisan make:request Auth/LoginUserRequest
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class LoginUserRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'email' => 'required|email',
'password' => 'required'
];
}
public function messages(): array
{
return [
'email.required' => 'The email field is required.',
'email.email' => 'Please enter a valid email address.',
'password.required' => 'The password field is required.',
];
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(
response()->json([
'status' => 'error',
'message' => 'Validation failed',
'errors' => $validator->errors()
], 422)
);
}
}
Auth Resource
php artisan make:resource User/AuthResource
app/Http/Resources/User/AuthResource.php
<?php
namespace App\Http\Resources\User;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class AuthResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'user' => new UserResource($this['user']),
'access_token' => $this['token'],
'token_type' => $this['token_type'] ?? 'Bearer',
];
}
}
User Resource
php artisan make:resource USer/UserResource
app/Http/Resources/User/UserResource.php
<?php
namespace App\Http\Resources\User;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'phone' => $this->phone,
'email_verified_at' => $this->email_verified_at?->toDateTimeString(),
'created_at' => $this->created_at?->toDateTimeString(),
'updated_at' => $this->updated_at?->toDateTimeString(),
];
}
}
Success Resource
php artisan make:resource SuccessResource
- app/Http/Resources/SuccessResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class SuccessResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'status' => 'success',
'message' => $this['message'],
'data' => $this['data'] ?? null,
];
}
public function withResponse($request, $response)
{
$response->setStatusCode($this['status_code'] ?? 200);
}
}
Error Resource
php artisan make:resource ErrorResource
-app/Http/Resources/ErrorResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ErrorResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'status' => 'error',
'message' => $this['message'],
'errors' => $this['errors'] ?? null,
];
}
public function withResponse($request, $response)
{
$response->setStatusCode($this['status_code'] ?? 400);
}
}
*Create OTP Controller, OTP Requests *
OTP Controller
This OTPController class is a One-Time Password (OTP) authentication controller for a Laravel application. Here's what it does:
Main Purpose
1- Email verification during registration
2- Two-factor authentication (2FA)
3- Password reset verification
4- Account recovery
-php artisan make:controller Api/Auth/OTPController
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\SendOTPRequest;
use App\Http\Requests\Auth\VerifyOTPRequest;
use App\Http\Resources\ErrorResource;
use App\Http\Resources\SuccessResource;
use App\Jobs\SendMailJob;
use App\Mail\OTPMail;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Mail;
class OTPController extends Controller
{
public function sendOTP(SendOTPRequest $request)
{
$validated = $request->validated();
$user = User::where('email', $validated['email'])->first();
$otp = rand(100000, 999999);
$otpExpires = Carbon::now()->addMinutes(10);
try {
// Send OTP via Email
SendMailJob::dispatch(
$user->name,
$user->email,
$otp
);
$user->update([
'otp' => $otp,
'otp_expires_at' => $otpExpires
]);
return new SuccessResource([
'message' => 'OTP sent successfully to your email',
'data' => [
'expires_in' => 10,
'email' => $user->email
]
]);
} catch (\Exception $e) {
return new ErrorResource([
'message' => 'Failed to send OTP. Please try again.',
'error_code' => 'EMAIL_SEND_FAILED',
'status_code' => 500
]);
}
}
public function verifyOTP(VerifyOTPRequest $request)
{
$validated = $request->validated();
$user = User::where('email', $validated['email'])
->where('otp', $validated['otp'])
->where('otp_expires_at', '>', Carbon::now())
->first();
if (!$user) {
return new ErrorResource([
'message' => 'Invalid or expired OTP',
'status_code' => 400
]);
}
$user->update([
'otp' => null,
'otp_expires_at' => null,
'email_verified_at' => Carbon::now()
]);
return new SuccessResource([
'message' => 'OTP verified successfully'
]);
}
}
OTP Requests
php artisan make:request Auth/SendOTPRequest
php artisan make:request Auth/VerifyOTPRequest
-app/Http/Requests/Auth/SendOTPRequest.php
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class SendOTPRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'email' => 'required|email|exists:users,email'
];
}
public function messages(): array
{
return [
'email.required' => 'The email field is required.',
'email.email' => 'Please enter a valid email address.',
'email.exists' => 'No account found with this email address.',
];
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(
response()->json([
'status' => 'error',
'message' => 'Validation failed',
'errors' => $validator->errors()
], 422)
);
}
}
-app/Http/Requests/Auth/VerifyOTPRequest.php
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class VerifyOTPRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'email' => 'required|email|exists:users,email',
'otp' => 'required|digits:6'
];
}
public function messages(): array
{
return [
// Email field messages
'email.required' => 'Email address is required.',
'email.email' => 'Please provide a valid email address.',
'email.exists' => 'This email address is not registered in our system.',
// OTP field messages
'otp.required' => 'OTP code is required.',
'otp.digits' => 'OTP code must be exactly 6 digits.',
];
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(
response()->json([
'status' => 'error',
'message' => 'Validation failed',
'errors' => $validator->errors()
], 422)
);
}
}
Queue for Sending Email With Database Driver
Benefits:
-Improved Application Performance -> User doesn't wait for email to send
- Automatic Retries -> If sending fails, it retries automatically
- Error Handling -> Failed jobs are logged and can be retried
- Data Safety -> Jobs are stored even if server restarts
Complete Setup
i: Environment Setup
.env
QUEUE_CONNECTION=database
ii: create queue tables if they don't exist:
php artisan queue:table
php artisan queue:failed-table
php artisan migrate
php artisan make:job SendMailJob
<?php
namespace App\Jobs;
use App\Mail\OTPMail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
class SendMailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $name;
protected $email;
protected $otp;
public function __construct($name, $email, $otp)
{
$this->name = $name;
$this->email = $email;
$this->otp = $otp;
}
public function handle(): void
{
Mail::to($this->email)->send(new OTPMail(
otp: $this->otp,
userName: $this->name,
expiresIn: 10,
purpose: 'verification'
));
}
public function failed(\Throwable $exception): void
{
Log::error('SendMailJob failed: ' . $exception->getMessage());
Log::error('Exception details: ', [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString()
]);
}
}
Running the Queue Worker
php artisan queue:work
Complete SendGrid Setup Guide
1: Create SendGrid Account
- Go to SendGrid.com
- Sign up for a free account (100 emails/day)
- Verify your email address
- Complete account setup
2: Get SendGrid API Key
- Login to SendGrid dashboard
- Go to Settings → API Keys
- Click Create API Key
- Give it a name (e.g., "Laravel App")
- Choose Full Access or Restricted Access
- Copy the API key (you won't see it again!)
- .env file
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendgrid.net
MAIL_PORT=587
MAIL_USERNAME=apikey
MAIL_PASSWORD=your_sendgrid_api_key_here
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=from@example.com
MAIL_FROM_NAME="Your App"
4: Sender Authentication
Option A: Single Sender Verification (Quick Start)
1- Go to Settings → Sender Authentication
2- Click Verify a Single Sender
3-Fill in the form:
From Email: your-email@example.com
From Name: Your Name
Reply To: your-email@example.com
Address: Your physical address (required by law)
Click Create
4-Check your email and click verification link
Option B: Domain Authentication (Recommended for Production)
1- Go to Settings → Sender Authentication
2- Click Authenticate Your Domain
3- Enter your domain (e.g., example.com)
4- Choose Default Link Branding
5- SendGrid provides DNS records - add these to your domain's DNS
6- Wait for verification (can take 24-48 hours)
5: Create Mail Class in Laravel
php artisan make:mail OTPMail
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class OTPMail extends Mailable
{
use Queueable, SerializesModels;
public $otp;
public $userName;
public $expiresIn;
public $purpose;
public function __construct($otp, $userName = null, $expiresIn = 10, $purpose = 'verification')
{
$this->otp = $otp;
$this->userName = $userName;
$this->expiresIn = $expiresIn;
$this->purpose = $purpose;
}
public function content(): Content
{
return new Content(
view: 'emails.otp',
);
}
public function attachments(): array
{
return [];
}
}
Create Email Template
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OTP Verification - {{ config('app.name') }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 500px;
margin: 0 auto;
}
.email-wrapper {
background: white;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.content {
padding: 40px 30px;
}
.greeting {
font-size: 18px;
margin-bottom: 25px;
color: #4B5563;
}
.otp-container {
text-align: center;
margin: 30px 0;
}
.otp-label {
font-size: 16px;
color: #6B7280;
margin-bottom: 15px;
}
.otp-code {
font-size: 48px;
font-weight: bold;
color: #4F46E5;
letter-spacing: 8px;
font-family: 'Courier New', monospace;
background: #F8FAFC;
padding: 20px;
border-radius: 10px;
border: 2px dashed #E5E7EB;
margin: 15px 0;
}
.info-box {
background: #F0F9FF;
border: 1px solid #BAE6FD;
border-radius: 10px;
padding: 20px;
margin: 25px 0;
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.info-item:last-child {
margin-bottom: 0;
}
.info-icon {
font-size: 18px;
margin-right: 12px;
}
.footer {
text-align: center;
padding: 30px;
background: #F8FAFC;
border-top: 1px solid #E5E7EB;
}
.footer p {
color: #6B7280;
font-size: 14px;
margin-bottom: 5px;
}
.app-name {
color: #4F46E5;
font-weight: 600;
}
@media (max-width: 600px) {
.content {
padding: 30px 20px;
}
.otp-code {
font-size: 36px;
letter-spacing: 6px;
padding: 15px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="email-wrapper">
<div class="content">
<div class="greeting">
@if ($userName)
Hello <strong>{{ $userName }}</strong>,
@else
Hello,
@endif
</div>
<p>You're just one step away! Use the following verification code to complete your request:</p>
<div class="otp-container">
<div class="otp-label">Your verification code:</div>
<div class="otp-code">{{ $otp }}</div>
</div>
<div class="info-box">
<div class="info-item">
<span class="info-icon">⏰</span>
<span><strong>Expires in:</strong> {{ $expiresIn }} minutes</span>
</div>
<div class="info-item">
<span class="info-icon">🚀</span>
<span><strong>Purpose:</strong>
@if ($purpose === 'verification')
Account Verification
@elseif($purpose === 'password_reset')
Password Reset
@elseif($purpose === 'login')
Login Verification
@else
Security Verification
@endif
</span>
</div>
</div>
</div>
<div class="footer">
<p>© {{ date('Y') }} <span class="app-name">{{ config('app.name') }}</span>. All rights
reserved.</p>
<p>This email was sent via Secure OTP System</p>
</div>
</div>
</div>
</body>
</html>
Password Reset Controller, Send OTP And Reset password Request
php artisan make:controller Api/Auth/PasswordResetController
- app/Http/Controllers/Api/Auth/PasswordResetController.php
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\ResetPasswordRequest;
use App\Http\Requests\Auth\SendOTPRequest;
use App\Http\Resources\ErrorResource;
use App\Http\Resources\SuccessResource;
use App\Jobs\SendMailJob;
use App\Mail\OTPMail;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
class PasswordResetController extends Controller
{
public function forgotPassword(SendOTPRequest $request)
{
$validated = $request->validated();
$user = User::where('email', $validated['email'])->first();
$otp = rand(100000, 999999);
$otpExpires = Carbon::now()->addMinutes(15);
try {
// Send OTP via Email
SendMailJob::dispatch(
$user->name,
$user->email,
$otp
);
$user->update([
'otp' => $otp,
'otp_expires_at' => $otpExpires
]);
return new SuccessResource([
'message' => 'OTP sent successfully to your email',
'data' => [
'expires_in' => 10,
'email' => $user->email
]
]);
} catch (\Exception $e) {
return new ErrorResource([
'message' => 'Failed to send OTP. Please try again.',
'error_code' => 'EMAIL_SEND_FAILED',
'status_code' => 500
]);
}
}
public function resetPassword(ResetPasswordRequest $request)
{
$validated = $request->validated();
$user = User::where('email', $validated['email'])
->where('otp', $validated['otp'])
->where('otp_expires_at', '>', Carbon::now())
->first();
if (!$user) {
return new ErrorResource([
'message' => 'Invalid or expired OTP',
'status_code' => 400
]);
}
$user->update([
'password' => Hash::make($validated['password']),
'otp' => null,
'otp_expires_at' => null
]);
$user->tokens()->delete();
return new SuccessResource([
'message' => 'Password reset successfully'
]);
}
}
Why $user->tokens()->delete();?
Security measure during password reset to:
- Invalidate all active sessions
- Force re-login with new password
- Prevent token reuse if account was compromised
- Log out all devices
Result:
- User must login again on all devices
- Old tokens become useless
- Fresh start with new password
Reset Password Request
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class ResetPasswordRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'email' => 'required|email|exists:users,email',
'otp' => 'required|digits:6',
'password' => 'required|string|min:8|confirmed',
];
}
public function messages(): array
{
return [
'email.required' => 'The email field is required.',
'email.email' => 'Please enter a valid email address.',
'email.exists' => 'No account found with this email address.',
'password.required' => 'The password field is required.',
'password.min' => 'The password must be at least 8 characters.',
'password.confirmed' => 'Password confirmation does not match.',
'otp.required' => 'OTP code is required.',
'otp.digits' => 'OTP code must be exactly 6 digits.',
];
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(
response()->json([
'status' => 'error',
'message' => 'Validation failed',
'errors' => $validator->errors()
], 422)
);
}
}
Create API Routes
- routes/api.php
// Include Authentication Routes
require __DIR__ . '/auth.php';
- routes/auth.php
<?php
use App\Http\Controllers\Api\Auth\AuthController;
use App\Http\Controllers\Api\Auth\OTPController;
use App\Http\Controllers\Api\Auth\PasswordResetController;
use Illuminate\Support\Facades\Route;
// Public routes
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
// OTP routes
Route::post('/send-otp', [OTPController::class, 'sendOTP']);
Route::post('/verify-otp', [OTPController::class, 'verifyOTP']);
// Password reset routes
Route::post('/forgot-password', [PasswordResetController::class, 'forgotPassword']);
Route::post('/reset-password', [PasswordResetController::class, 'resetPassword']);
//Protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::delete('/logout', [AuthController::class, 'logout']);
Route::get('/user', [AuthController::class, 'user']);
});
API Authentication System Implementation
Postman Environment Variables
-Two key variables configured:

- url: Base API endpoint for all requests
- access-token: Authentication token automatically saved after login/register
OTP
password










Top comments (0)