Nafath Authentication Laravel SSO Guide
Integrate Nafath authentication in Laravel the right way. SSO architecture, callback handling, and the practical lessons official docs skip for Saudi apps.
Umar Farooq
System Architect & Full-Stack Engineer

Nafath Authentication in Laravel: National SSO Integration
Saudi applications that handle contracts, payments, or government-adjacent workflows eventually face the same requirement: verify the user is who they claim to be, using the national digital identity. That means Nafath authentication in Laravel, and it does not behave like the OAuth flows most Laravel developers already know.
Quick Answer: Nafath is Saudi Arabia's national single sign-on: the user approves the login inside the Nafath app instead of typing a password on your site. In Laravel, integrate it as an SSO-style redirect flow: send the user to the national identity gateway, receive a signed callback, verify it server-side, then create your local session. Request the exact endpoints and credentials through the official Nafath onboarding process, since they are issued per project, not published publicly.
How Nafath Differs from Standard OAuth
With Google OAuth the pattern is familiar: redirect, grant permission, redirect back with a code, exchange for tokens. Nafath follows the same skeleton but the middle step differs: the user receives a push in the Nafath app, verifies with device biometrics, and approves. Your application waits.
This changes the UX you must design around. The approval is asynchronous: users switch to their phone, sometimes take a minute, sometimes abandon the flow. Your callback handling needs pending states, clear "check your Nafath app" messaging in Arabic and English, and a timeout with a retry path. I treat Nafath like a payment authorization flow, because user behavior is nearly identical.
The identity data you receive is government-verified, not self-asserted: national ID number, verified Arabic and English names, sometimes date of birth. That makes it powerful for contracts or merchant onboarding, and it pulls PDPL obligations directly into your codebase. If you build Nafath login, read my companion guide on Saudi PDPL compliance in Laravel before storing a single field.
The Integration Architecture
The flow has four stages. First, your application creates a login request with a unique request ID, a callback URL, and the identity attributes you need, stored server-side with a short expiry (fifteen minutes is reasonable). Second, redirect the user to the national gateway. Third, the user approves in the Nafath app. Fourth, the gateway calls your callback with a signed payload: verify the signature, match the request ID, extract the identity, then create or link the local user.
Keep the Nafath-specific code behind an interface. Government integrations change: field names shift, sandbox drifts from production. When that happens you want one class to update, not callbacks scattered across controllers.
Handling the Callback in Laravel
The callback endpoint is the security-critical piece: verify before trusting anything. The shape below does signature verification first, request matching second, user provisioning last. Exact signing details come from your official Nafath onboarding credentials; project values live in config.
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Services\Nafath\NafathService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class NafathCallbackController extends Controller
{
public function __invoke(Request $request, NafathService $nafath)
{
// 1. Verify the gateway signature BEFORE reading any payload data.
// Reject silently on failure: never reveal why verification failed.
if (! $nafath->verifySignature($request)) {
abort(403);
}
// 2. Match the callback to a login request WE created.
// Unknown or expired request IDs are rejected outright.
$loginRequest = $nafath->findPendingRequest(
$request->input('request_id')
);
abort_unless($loginRequest && ! $loginRequest->isExpired(), 403);
// 3. Extract the verified national identity attributes.
$identity = $nafath->parseIdentity($request);
// 4. Link to an existing user or provision a new one.
// National ID is the stable key, never the phone number.
$user = $nafath->findOrCreateUser($identity);
$loginRequest->markCompleted();
Auth::login($user, remember: false);
return redirect()->intended('/dashboard');
}
}
<?php
// config/services.php: project credentials come from official onboarding.
// Never commit real values; they are issued per environment.
'nafath' => [
'environment' => env('NAFATH_ENV', 'sandbox'),
'gateway_url' => env('NAFATH_GATEWAY_URL'),
'client_id' => env('NAFATH_CLIENT_ID'),
'private_key' => env('NAFATH_PRIVATE_KEY_PATH'),
'callback' => env('APP_URL').'/auth/nafath/callback',
'request_ttl' => 900, // seconds a login request stays valid
],
Two decisions deserve explanation. The national ID is the stable user key because phone numbers change and emails are self-asserted, while the national ID is what the gateway guarantees. And Nafath sessions skip "remember me": a national-identity session should be short-lived, and re-verification for sensitive actions is a feature, not friction.
What the Docs Don't Tell You
This is the practical half. First, the sandbox lies politely: approvals taking seconds in sandbox can take a minute or more in production. Design your pending screen for the slow case, with a visible countdown and a one-tap "resend request" that creates a fresh request ID.
Second, Arabic names arrive in forms your database may not expect: four-part names, tatweel characters, inconsistent spacing. Normalize on ingest and never use the name as a unique identifier.
Third, handle the decline path as a first-class flow. Users reject requests more often than expected, usually from opening the wrong notification. A rejection should land on a clear screen with a retry button, not a generic 403.
Fourth, test the expiry path: let a request sit sixteen minutes, then approve it. Your system must reject the stale callback cleanly.
Nafath vs standard OAuth vs email OTP at a glance:
Dimension | Nafath SSO | Standard OAuth (Google/GitHub) | Email/SMS OTP |
|---|---|---|---|
Identity assurance | Government-verified national ID | Self-asserted profile | Device possession only |
User action | Approve in Nafath app | Grant permission on web page | Type a 6-digit code |
Drop-off risk | Medium (app switching) | Low | Low-medium |
Data sensitivity | Very high (national ID) | Medium | Low |
Integration effort | High (onboarding + custom flow) | Low (Socialite, one afternoon) | Low |
Right for | Contracts, finance, gov-adjacent | General SaaS login | Low-risk verification |
Choose Nafath when the business risk of a fake identity exceeds the integration cost. For content sites it is overkill; for money or regulated onboarding in Saudi Arabia it is the right answer. Building it well is the kind of specialized work I do for Laravel teams across Saudi Arabia.
Do I need government approval to use Nafath?
Yes, in practice. Private-sector integration goes through official onboarding where your project is registered and issued credentials, not a self-serve portal. Start early: it runs on administrative timelines.
Can I use Laravel Socialite for Nafath?
Not directly. Socialite assumes standard OAuth 2.0 code exchange; Nafath's app-approval step and signed callbacks need a dedicated service class.
What identity data should I store?
Store the minimum: national ID (hashed, encrypted at rest), verified name, and your internal user link. Never store the full gateway payload or log identity attributes.
How do I handle users without the Nafath app?
Always provide a fallback like document upload with manual review. Nafath-only login excludes legitimate users, so gate only high-trust actions behind it.
Summary & Production Takeaways
Nafath authentication in Laravel is a redirect-and-callback SSO flow with approval in the national app. Build it as an isolated service class, verify signatures first, key users by national ID, and treat stored identity as PDPL-regulated data. Start onboarding early. For verified identity done right, book a technical scoping call.

Umar Farooq
Author & ConsultantSpecializes in Laravel, Next.js, and AI products. 5+ years enterprise experience with 80+ delivered platforms and full source code ownership.
Related Engineering Insights
Saudi PDPL Compliance Laravel Guide
Implement Saudi PDPL compliance in Laravel with real code: consent logging, true deletion routines, retention policies, and data residency checks.

Hire Laravel Developer Saudi Arabia Guide
Hiring a Laravel developer in Saudi Arabia? Interview questions, technical filters and red flags that actually predict performance, from a Riyadh engineer.

ZATCA Fatoora API Error Handling
ZATCA clearance failures decoded: Fatoora API error handling in Laravel with queue-based retries, dead-letter queues and reconciliation for B2B invoices.