ums-lspl / sso-client
Laravel client package for SmartExam SSO — verify tokens and authenticate users in consumer apps.
Requires
- php: ^8.1
- illuminate/support: ^9.0|^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^7.0|^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^9.5|^10.0|^11.0
README
Laravel package for consumer apps that sign users in with SmartExam SSO.
SmartExam issues a signed token after the user approves login. This package verifies the token, creates or updates a local user, and starts a Laravel session.
Requirements: PHP 8.1+, Laravel 9–13, and an SSO application in SmartExam Admin → SSO → Applications.
Version 2.x — active development on
main. For Laravel 7–8 / PHP 7.2+, use the1.xbranch (composer require ums-lspl/sso-client:^1.0).
Quick start
1. Install
composer require ums-lspl/sso-client:^2.0 php artisan smartexam-sso:install --migrate
This publishes the config and migration, runs migrations, then diagnoses your setup. It adds a nullable, unique smartexam_id column to users.
Equivalent manual steps:
php artisan vendor:publish --tag=smartexam-sso-config php artisan vendor:publish --tag=smartexam-sso-migrations php artisan migrate php artisan smartexam-sso:diagnose
2. Configure .env
SMARTEXAM_URL=https://your-ums-server.example.com SSO_CLIENT_KEY=your-client-key SSO_CLIENT_SECRET=your-client-secret SSO_CALLBACK_URL=https://your-app.example.com/sso/callback SSO_AFTER_LOGIN_REDIRECT=/dashboard SSO_REMEMBER_LOGIN=false
Register the same SSO_CALLBACK_URL in SmartExam Admin for your client key.
3. Update your User model
protected $fillable = [ // ... 'smartexam_id', ];
4. Add SSO to your login page
In your auth layout <head>:
<meta name="csrf-token" content="{{ csrf_token() }}">
In the layout body (no publish step needed):
@include('smartexam-sso::login-script')
On your login button:
<button type="button" onclick="signInWithSmartExam()">Sign in with SmartExam</button>
That’s it. The partial loads the SmartExam overlay, stores CSRF state, opens the popup, and POSTs to the exchange route.
How it works
sequenceDiagram
participant Browser
participant App as Your Laravel app
participant UMS as SmartExam
Browser->>App: Open login page
Browser->>UMS: Popup — user approves SSO
UMS->>Browser: Redirect popup to /sso/callback
Browser->>App: POST /api/sso/exchange
App->>App: Verify token, login user
Browser->>App: Redirect to dashboard
Loading
Recommended: popup + exchange (POST /api/sso/exchange) — handled by @include('smartexam-sso::login-script'). Tokens stay out of top-level browser history.
Alternative: full browser redirect to GET /sso/callback (tokens appear in the URL). Disable with SMARTEXAM_SSO_ALLOW_REDIRECT_CALLBACK=false if you only want overlay exchange.
UMS dashboard launch: when users open your app from Connected Apps on the SmartExam dashboard, SmartExam calls GET /sso/launch/{client} and redirects to your callback with source=issuer. No extra route is needed in this package — the existing GET /sso/callback handles it. Ensure:
SMARTEXAM_SSO_ALLOW_REDIRECT_CALLBACK=true(default)SMARTEXAM_SSO_ALLOW_ISSUER_LAUNCH=true(default)SMARTEXAM_SSO_REQUIRE_STATE=trueis fine — issuer launches skip state whensource=issueris present- Optional
landingquery param (from SmartExam Login Landing URL) overridesSSO_AFTER_LOGIN_REDIRECTafter login
Artisan commands
| Command | Description |
|---|---|
php artisan smartexam-sso:install |
Publish config + migrations, then diagnose |
php artisan smartexam-sso:install --migrate |
Also run migrations |
php artisan smartexam-sso:install --force |
Overwrite previously published files |
php artisan smartexam-sso:install --no-diagnose |
Publish only |
php artisan smartexam-sso:diagnose |
Check env, routes, user model, and smartexam_id |
php artisan smartexam-sso:diagnose --ping |
Also HTTP-check SmartExam /js/sso-overlay.js |
Publish tags
| Tag | Use when |
|---|---|
smartexam-sso-config |
You need to customize routes, provisioner, etc. |
smartexam-sso-migrations |
First install — adds smartexam_id |
smartexam-sso-views |
You want to edit the login script |
| Views are optional. To customize the login script: |
php artisan vendor:publish --tag=smartexam-sso-views
@include('vendor.smartexam-sso.login-script')
Environment variables
| Variable | Required | Description |
|---|---|---|
SMARTEXAM_URL |
Yes | SmartExam server URL (must match token iss) |
SSO_CLIENT_KEY |
Yes | Client key from SmartExam Admin |
SSO_CLIENT_SECRET |
Yes | Secret used to verify token signatures |
SSO_CALLBACK_URL |
Yes | Your callback URL (must match SmartExam registration) |
SSO_AFTER_LOGIN_REDIRECT |
No | Where to go after login (relative path preferred). Default: / |
SSO_AUDIENCE |
No | Token aud claim. Default: origin of SSO_CALLBACK_URL |
SSO_REMEMBER_LOGIN |
No | Laravel “remember me” after SSO. Default: false |
SMARTEXAM_SSO_ROUTES |
No | Enable package routes. Default: true |
SMARTEXAM_SSO_ROUTE_PREFIX |
No | Prefix for SSO routes. Default: empty |
SMARTEXAM_SSO_REQUIRE_STATE |
No | Require session state. Default: true |
SMARTEXAM_SSO_ALLOW_ISSUER_LAUNCH |
No | Accept UMS dashboard launch (source=issuer) without state. Default: true |
SMARTEXAM_SSO_THROTTLE |
No | Rate limit maxAttempts,decayMinutes. Default: 20,1 |
SMARTEXAM_SSO_CLOCK_SKEW |
No | Allowed iat/exp skew in seconds. Default: 60 |
SMARTEXAM_SSO_REPLAY_PROTECTION |
No | Reject reused tokens. Default: true |
SMARTEXAM_SSO_ALLOW_REDIRECT_CALLBACK |
No | Allow top-level GET callback login. Default: true |
SMARTEXAM_SSO_GENERIC_ERRORS |
No | Hide detailed errors from browsers. Default: true in production |
SMARTEXAM_SSO_REQUIRE_HTTPS |
No | Fail diagnose if issuer/callback use http in production. Default: true |
Routes
Registered automatically (customize in config/smartexam-sso.php):
| Method | Path | Name |
|---|---|---|
| GET | /sso/callback |
smartexam-sso.callback |
| POST | /api/sso/exchange |
smartexam-sso.exchange |
| POST | /sso/logout |
smartexam-sso.logout |
All SSO routes are rate-limited (SMARTEXAM_SSO_THROTTLE, default 20,1).
Security features
| Feature | Behaviour |
|---|---|
| Replay protection | Each token can be exchanged once (cached until exp) |
| Rate limiting | Throttle on callback + exchange |
Clock skew / iat |
Rejects future iat and expired exp with configurable skew |
| Base64url | Accepts standard base64 and base64url payloads |
| Safe redirect | SSO_AFTER_LOGIN_REDIRECT must be relative or same-host |
| Generic errors | Production browsers get SSO login failed. (details in logs) |
| Session binding | Stores issuer session_id in the local session |
| HTTPS assert | smartexam-sso:diagnose fails on http:// URLs in production |
| Overlay preference | Prefer resolveOnly + exchange so tokens are not left in the address bar |
Local logout
<form method="POST" action="{{ route('smartexam-sso.logout') }}"> @csrf <button type="submit">Log out</button> </form> {{-- Also send the user to SmartExam sign-in --}} <form method="POST" action="{{ route('smartexam-sso.logout', ['issuer' => 1]) }}"> @csrf <button type="submit">Log out everywhere</button> </form>
use SmartExam\SsoClient\Services\SsoAuthenticationService; app(SsoAuthenticationService::class)->boundIssuerSessionId(); // issuer session_id if present
Customization
Custom user provisioning
Default behavior: find or create user by email, set smartexam_id from token sub.
For custom logic, implement SmartExam\SsoClient\Contracts\SsoUserProvisioner:
// config/smartexam-sso.php 'user_provisioner' => App\Services\YourSsoUserProvisioner::class,
Post-login hook
use SmartExam\SsoClient\Events\SmartExamSsoAuthenticated; Event::listen(SmartExamSsoAuthenticated::class, function ($event) { // $event->user, $event->payload });
URL helpers
use SmartExam\SsoClient\Support\SsoUrl; use SmartExam\SsoClient\Support\SafeRedirect; SsoUrl::overlayScript(); // SmartExam /js/sso-overlay.js URL SsoUrl::callbackUrl(); // Host-aware redirectUrl for the overlay SsoUrl::connect($state); // Full-page SSO connect URL SsoUrl::issuerSignIn(); // SmartExam sign-in page SafeRedirect::afterLogin(); // Safe post-login path
Troubleshooting
| Error | Fix |
|---|---|
| Unexpected token audience | Set SSO_AUDIENCE to match your app Base URL |
| Unexpected token issuer | SMARTEXAM_URL must match token issuer |
| SSO token has already been used | Token replay — start a new SSO login |
| Invalid / missing SSO state | Use @include('smartexam-sso::login-script') or reload login page |
| CSRF token missing | Add <meta name="csrf-token"> to layout |
| SSO client secret is not configured | Set SSO_CLIENT_SECRET, run php artisan config:clear |
| Too Many Attempts | Raise SMARTEXAM_SSO_THROTTLE or wait for the window to reset |
Testing
composer test
License
MIT