PHPackages                             putheakhem/fsa-sso - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. putheakhem/fsa-sso

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

putheakhem/fsa-sso
==================

Laravel package for integrating FSA SSO authentication

v1.2.1(3w ago)197↓27.8%MITPHPPHP ^8.2

Since May 13Pushed 3w agoCompare

[ Source](https://github.com/putheakhem/fsa-sso)[ Packagist](https://packagist.org/packages/putheakhem/fsa-sso)[ RSS](/packages/putheakhem-fsa-sso/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (5)Dependencies (7)Versions (6)Used By (0)

Laravel FSA SSO Package
=======================

[](#laravel-fsa-sso-package)

[![Latest Stable Version](https://camo.githubusercontent.com/5ece927795e263bcd9c8bbdf6c567a39cfebc1b6b631e551d951047c101079fc/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7075746865616b68656d2f6673612d73736f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/putheakhem/fsa-sso)[![Total Downloads](https://camo.githubusercontent.com/6b040a73c98e6a66888016aead1fa304e77f4efa5c6440813b8a764e9bc15f30/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7075746865616b68656d2f6673612d73736f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/putheakhem/fsa-sso)

> ⚠️ **DISCLAIMER**: This is an **unofficial community package** and is **not affiliated with, endorsed by, or supported by the official FSA SSO Platform** or the Non-Bank Financial Services Authority (FSA). This package is maintained independently. For official support, please contact the FSA SSO Platform directly.

A Laravel package for **FSA SSO authentication** with support for JWT verification via JWKS, user provisioning, and optional web login flow.

---

📋 What This Package Does
------------------------

[](#-what-this-package-does)

- Builds the FSA login URL: `/auth/login?client_code=...`
- Verifies FSA JWT using JWKS public keys (Ed25519 / EdDSA — no shared secret needed)
- Validates token claims: `iss`, `aud`, `exp`, `client_code`
- Upserts local users by stable FSA `sub` claim
- Proxies optional introspect and revoke calls to FSA API
- Registers a `fsa-sso.auth` middleware for per-request bearer token protection
- Registers package-managed web routes for browser login redirect and callback

---

⚙️ Requirements
---------------

[](#️-requirements)

- PHP 8.2+
- Laravel 10, 11, 12, or 13
- `ext-sodium` enabled (for Ed25519 verification)

---

📦 Installation
--------------

[](#-installation)

```
composer require putheakhem/fsa-sso
```

The service provider is auto-discovered via Laravel's package discovery.

### Publish Config and Migrations

[](#publish-config-and-migrations)

```
php artisan vendor:publish --tag=fsa-sso-config
php artisan vendor:publish --tag=fsa-sso-migrations
php artisan migrate
```

If your app has many packages and tag-based publish does not detect resources, use provider-scoped commands:

```
php artisan vendor:publish --provider="PutheaKhem\\FsaSso\\FsaSsoServiceProvider" --tag=fsa-sso-config
php artisan vendor:publish --provider="PutheaKhem\\FsaSso\\FsaSsoServiceProvider" --tag=fsa-sso-migrations
```

The package also registers conventional `config` and `migrations` tags for compatibility.

---

🌐 .env Configuration
--------------------

[](#-env-configuration)

Add these to your `.env` file:

```
# FSA SSO production endpoints
FSA_SSO_FRONTEND_URL=https://sso.fsa.gov.kh
FSA_SSO_API_BASE_URL=https://sso.fsa.gov.kh
FSA_SSO_JWKS_URL=https://sso.fsa.gov.kh/.well-known/jwks.json
FSA_SSO_ISSUER=https://sso.fsa.gov.kh
FSA_SSO_AUDIENCE=https://sso.fsa.gov.kh

# Your portal client code from FSA SSO admin
FSA_SSO_CLIENT_CODE=FSA-XXXXXXXXXXXX

# Optional tuning (defaults shown)
FSA_SSO_ROUTE_PREFIX=auth/sso
FSA_SSO_JWKS_CACHE_TTL_SECONDS=600
FSA_SSO_RETURN_ACCESS_TOKEN=false
FSA_SSO_INCLUDE_CLAIMS_IN_RESPONSE=true
FSA_SSO_USER_MODEL=App\Models\User

# Optional package-managed web flow
FSA_SSO_ENABLE_WEB_ROUTES=true
FSA_SSO_WEB_LOGIN_PATH=fsa-sso/loginUrl
FSA_SSO_WEB_CALLBACK_PATH=sso/callback-success
FSA_SSO_WEB_FALLBACK_CALLBACK_PATH=fsa-sso/callback
FSA_SSO_WEB_LOGIN_ROUTE_NAME=fsaSsoLoginUrl
FSA_SSO_WEB_CALLBACK_ROUTE_NAME=fsaSsoCallbackSuccess
FSA_SSO_WEB_FALLBACK_CALLBACK_ROUTE_NAME=fsaSsoCallback
FSA_SSO_WEB_GUARD=web
FSA_SSO_WEB_INTENDED_ROUTE=dashboard
FSA_SSO_WEB_FAILURE_REDIRECT=/login
```

> ✅ `FSA_SSO_CLIENT_CODE` must exactly match the client code registered in the FSA SSO admin portal.

> ✅ Use single backslashes for `FSA_SSO_USER_MODEL` in `.env` (example: `App\Models\User`).

---

🗄️ User Model — Fillable &amp; Migration
----------------------------------------

[](#️-user-model--fillable--migration)

The published migration adds these columns to your `users` table:

ColumnTypeNotes`sso_id`string, unique, nullableStable FSA `sub` identifier`sso_provider`string, nullablee.g. `camdigikey``kyc_level`string, nullablee.g. `kyc_verified``camdigikey_id`string, unique, nullable`nbfs_id`string, unique, nullableAdd these columns to your `User` model's `$fillable`:

```
// app/Models/User.php
protected $fillable = [
    'name',
    'email',
    'password',
    'sso_id',
    'sso_provider',
    'kyc_level',
    'camdigikey_id',
    'nbfs_id',
];
```

---

🛣️ Package API Endpoints
------------------------

[](#️-package-api-endpoints)

The package auto-registers these routes under the configured prefix (`auth/sso` by default):

MethodPathDescription`GET``/auth/sso/initiate`Returns the FSA SSO login URL`POST``/auth/sso/verify`Verifies JWT, upserts user, returns user + claims`POST``/auth/sso/introspect`Proxies bearer token to FSA introspect API`POST``/auth/sso/revoke`Proxies bearer token to FSA revoke APIThe package also auto-registers web routes (enabled by default):

MethodPathRoute NameDescription`GET``/fsa-sso/loginUrl``fsaSsoLoginUrl`Redirects browser to FSA login URL`GET``/sso/callback-success``fsaSsoCallbackSuccess`Callback that verifies token, logs in user, and redirects`GET``/fsa-sso/callback``fsaSsoCallback`Fallback callback path---

🧪 Integration Guide (Laravel + Inertia)
---------------------------------------

[](#-integration-guide-laravel--inertia)

This package now handles the common web flow internally.

### Step 1 — Configure Callback URL in FSA Admin

[](#step-1--configure-callback-url-in-fsa-admin)

Register the callback URL in FSA SSO admin:

- `https://your-app.com/sso/callback-success`

No custom app controller or app-level web route is required when `FSA_SSO_ENABLE_WEB_ROUTES=true`.

### Using Different Callback URI Per Application

[](#using-different-callback-uri-per-application)

Each application can use its own callback path and route names while sharing the same package.

Example for another app:

```
FSA_SSO_ENABLE_WEB_ROUTES=true
FSA_SSO_WEB_LOGIN_PATH=auth/fsa/login
FSA_SSO_WEB_CALLBACK_PATH=auth/fsa/callback
FSA_SSO_WEB_FALLBACK_CALLBACK_PATH=auth/fsa/callback-alt
FSA_SSO_WEB_LOGIN_ROUTE_NAME=fsa.login
FSA_SSO_WEB_CALLBACK_ROUTE_NAME=fsa.callback
FSA_SSO_WEB_FALLBACK_CALLBACK_ROUTE_NAME=fsa.callback.alt
```

Then register this exact URL in FSA admin for that application:

- `https://other-app.com/auth/fsa/callback`

Notes:

- Callback URL must match exactly between your app env config and FSA admin configuration.
- Keep a distinct `FSA_SSO_CLIENT_CODE` per application if required by your FSA SSO setup.

### Step 2 — Confirm the Login Page Is Rendered by Inertia

[](#step-2--confirm-the-login-page-is-rendered-by-inertia)

```
// app/Http/Controllers/Auth/AuthenticatedSessionController.php

public function create(): Response
{
    return Inertia::render('auth/login', [
        'canResetPassword' => Route::has('password.request'),
        'canRegister'      => Route::has('register'),
        'status'           => session('status'),
    ]);
}
```

### Step 3 — Add the "Sign in with FSA SSO" Button (React / Inertia)

[](#step-3--add-the-sign-in-with-fsa-sso-button-react--inertia)

Use the generated Wayfinder route helper from `@/routes`. Call `.url()` when assigning it to a native ``.

```
// resources/js/pages/auth/login.tsx

import { fsaSsoLoginUrl } from '@/routes';

interface LoginProps {
    status?: string;
    canResetPassword: boolean;
    canRegister: boolean;
}

export default function Login({ status, canResetPassword, canRegister }: LoginProps) {
    return (

                {({ processing, errors }) => (

                        {/* email, password, remember me, submit button */}

                            or

                            Sign in with FSA SSO

                )}

    );
}
```

---

🔄 Complete Authentication Flow
------------------------------

[](#-complete-authentication-flow)

```
1. User clicks "Sign in with FSA SSO"
         ↓
2. Browser navigates to GET /fsa-sso/loginUrl
         ↓
3. Package web login controller redirects away to:
   https://sso.fsa.gov.kh/auth/login?client_code=FSA-XXXXXXXXXXXX
         ↓
4. User authenticates with FSA SSO (CamDigi Key, NBFS, etc.)
         ↓
5. FSA SSO redirects back to your registered callback:
   https://your-app.com/sso/callback-success?authToken=
         ↓
6. Package web callback controller
   ├── Resolves authToken from query string
   ├── Calls FsaSso::verifyAndProvision($token)
   │   ├── Fetches JWKS from https://sso.fsa.gov.kh/.well-known/jwks.json (cached 10 min)
   │   ├── Selects matching key by kid
   │   ├── Verifies EdDSA (Ed25519) signature
   │   ├── Validates iss, aud, exp, client_code claims
   │   └── Upserts user by sub claim → returns { user, claims }
   ├── Auth::login($user, remember: true)
   ├── Session regenerated
   └── Redirects to dashboard

```

---

🛡️ Protecting Routes with the Middleware
----------------------------------------

[](#️-protecting-routes-with-the-middleware)

Use the `fsa-sso.auth` middleware to protect API routes that require a valid FSA SSO bearer token:

```
// routes/api.php

Route::middleware('fsa-sso.auth')->get('/fsa-sso/me', function (Request $request) {
    $claims = $request->attributes->get('fsa_sso_claims', []);

    return response()->json([
        'sub'       => $claims['sub'] ?? null,
        'email'     => $claims['email'] ?? null,
        'kyc_level' => $claims['kyc_level'] ?? null,
        'provider'  => $claims['sso_provider'] ?? null,
    ]);
});
```

The middleware:

- Reads the bearer token from the `Authorization` header
- Verifies the JWT via JWKS (EdDSA)
- Validates `iss`, `aud`, `exp`, and `client_code`
- Attaches claims to `$request->attributes->get('fsa_sso_claims')`
- Returns `401` for invalid or missing tokens

---

🧩 Using the Facade Directly
---------------------------

[](#-using-the-facade-directly)

```
use PutheaKhem\FsaSso\Facades\FsaSso;

// Get the FSA SSO login URL
$response = FsaSso::getLoginUrl();
// ['loginUrl' => 'https://sso.fsa.gov.kh/auth/login?client_code=FSA-XXXXXXXXXXXX']

// Verify a token and provision the user
$result = FsaSso::verifyAndProvision($authToken);
// ['user' => User, 'claims' => ['sub' => '...', 'kyc_level' => 'kyc_verified', ...]]

// Introspect a token (proxied to FSA API)
$status = FsaSso::introspect($token);

// Revoke a token (proxied to FSA API)
FsaSso::revoke($token);
```

---

🔐 Security
----------

[](#-security)

- **No shared secret required** — JWT verification uses EdDSA asymmetric cryptography via JWKS
- **JWKS is cached** (default 10 minutes) to avoid hammering the FSA endpoint on every request
- **Token verification is local** — no sidecar or additional service needed
- **Session is regenerated** after login to prevent session fixation attacks
- Expected algorithm is `EdDSA` (Ed25519 curve) — RS256 and HS256 tokens are rejected

---

⚙️ Configuration Reference
--------------------------

[](#️-configuration-reference)

All options are in `config/fsa-sso.php` after publishing:

KeyDefaultDescription`route_prefix``auth/sso`URL prefix for package routes`route_middleware``['api']`Middleware applied to package routes`web_routes_enabled``true`Enable package-managed web login/callback routes`web_route_middleware``['web']`Middleware applied to package web routes`web_login_path``fsa-sso/loginUrl`Login redirect endpoint path`web_callback_path``sso/callback-success`Primary callback endpoint path`web_fallback_callback_path``fsa-sso/callback`Secondary callback endpoint path`web_login_route_name``fsaSsoLoginUrl`Route name for login redirect endpoint`web_callback_route_name``fsaSsoCallbackSuccess`Route name for primary callback endpoint`web_fallback_callback_route_name``fsaSsoCallback`Route name for fallback callback endpoint`web_guard``web`Auth guard used for session login in callback`web_intended_route``dashboard`Intended route name used after successful callback`web_failure_redirect``/login`Redirect target when callback fails in browser requests`frontend_url``http://localhost:4040`FSA SSO login portal URL`api_base_url``http://localhost:3000`FSA SSO backend API base URL`jwks_url``http://localhost:3000/.well-known/jwks.json`JWKS endpoint`issuer``http://localhost:3000`Expected `iss` claim`audience``http://localhost:3000`Expected `aud` claim`client_code`*(required)*Your FSA portal client code`jwks_cache_ttl_seconds``600`JWKS cache duration in seconds`user_model``App\Models\User`User model to upsert`columns`*see config*Maps JWT claims → user column names`return_access_token``false`Include raw token in verify response`include_claims_in_response``true`Include JWT claims in verify response---

⚠️ Common Mistakes
------------------

[](#️-common-mistakes)

SymptomCauseFix`Missing authentication token`FSA SSO sends `authToken` paramEnsure `resolveToken()` checks `authToken` firstMass assignment error on `sso_id`Missing `$fillable` entriesAdd SSO columns to `User::$fillable`Callback route is not availablePackage web routes were disabledSet `FSA_SSO_ENABLE_WEB_ROUTES=true` or register your own routes/controllersLogin URL not workingWrong URL formatCorrect: `/auth/login?client_code=...`Client code mismatchEnv vs portal mismatch`FSA_SSO_CLIENT_CODE` must match FSA admin exactlyRedirects back to login after callbackInvalid user model class value (often double-escaped in `.env`)Set `FSA_SSO_USER_MODEL=App\Models\User` and clear config cacheJWT verification fails`ext-sodium` not enabledEnable the PHP sodium extension in `php.ini`---

🧪 Testing
---------

[](#-testing)

The package includes standalone tests using Pest + Orchestra Testbench.

Run inside the package directory:

```
cd packages/fsa-sso
composer install
vendor/bin/pest
```

---

Support Me
----------

[](#support-me)

If you find this package useful, consider supporting my work:

- [Buy me a coffee](https://www.buymeacoffee.com/iamputhea)

📄 License
---------

[](#-license)

The MIT License (MIT). Please see [License File](LICENSE) for more information.

---

Built with ❤️ by [Puthea Khem](mailto:puthea.khem@gmail.com)

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance94

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

5

Last Release

26d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/764935335a012f6477a36e9bf81144d2c887becfbd0dea300426a27f7677ab93?d=identicon)[putheakhem](/maintainers/putheakhem)

---

Top Contributors

[![putheakhem](https://avatars.githubusercontent.com/u/8064772?v=4)](https://github.com/putheakhem "putheakhem (5 commits)")

---

Tags

laravelAuthenticationSSOJWKSfsa

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/putheakhem-fsa-sso/health.svg)

```
[![Health](https://phpackages.com/badges/putheakhem-fsa-sso/health.svg)](https://phpackages.com/packages/putheakhem-fsa-sso)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.4k](/packages/larastan-larastan)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45344.0k1](/packages/pressbooks-pressbooks)[simplestats-io/laravel-client

Analytics for Laravel. Track visitors, registrations, and payments. Discover which channels actually drive revenue, not just traffic. Server-side, GDPR compliant, ad-blocker proof.

5019.3k](/packages/simplestats-io-laravel-client)[alajusticia/laravel-logins

Session management in Laravel apps, user notifications on new access, support for multiple separate remember tokens, IP geolocation, User-Agent parser

2013.2k](/packages/alajusticia-laravel-logins)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
