Parichay (sb-iam) SSO
sb-iam is the SDS-deployed Laravel-Passport instance that backs all setu apps' authentication. We refer to it as "Parichay" externally — same OAuth2 + PKCE protocol. Sanchalan supports both the web (session) flow and the mobile (token) flow. Wired in code; flipping AUTH_MODE=sso in the env activates it.
What's shipped
SsoControllerwith five endpoints: webredirect+callback, mobilemobileRedirect+mobileExchange, publicmodeprobe.- User auto-link by email match against
attendance_employee_registerat SSO callback time. - Sanctum personal-access-tokens minted on mobile exchange.
- Allowlist for
mobile_redirect_uris(CSV in env) — exact-match required.
Web SSO
sequenceDiagram
participant U as Browser
participant Be as backend
participant IAM as sb-iam (Parichay)
U->>Be: GET /admin (no session)
Be-->>U: 302 /admin/login
U->>Be: GET /admin/login (sso_enabled=true)
Be-->>U: 302 /api/auth/sso/redirect
Be->>Be: store state + code_verifier in session
Be-->>U: 302 sb-iam /oauth/authorize?…
U->>IAM: login (Parichay UI)
IAM-->>U: 302 /api/auth/sso/callback?code=…&state=…
U->>Be: GET /api/auth/sso/callback?code=…&state=…
Be->>Be: pull state + verifier from session, verify state matches
Be->>IAM: POST /oauth/token (client_id, code, code_verifier)
IAM-->>Be: {access_token}
Be->>IAM: GET /api/auth/user (Bearer)
IAM-->>Be: {email, name, role, parichay_id}
Be->>Be: User::firstOrCreate, link to employee_register by email
Be->>Be: Auth::guard('web')->login(user)
Be-->>U: 302 / (lands on /admin)
Mobile SSO
sequenceDiagram
participant App as Sanchalan app
participant Be as backend
participant IAM as sb-iam
App->>App: PKCE generate (verifier, challenge, state)
App->>Be: POST /api/auth/sso/mobile/redirect
{code_challenge, state, redirect_uri} Be->>Be: validate redirect_uri allowlist Be-->>App: {authorization_url} App->>IAM: open authorization_url in flutter_web_auth_2 IAM-->>App: redirect sanchalan-attendance://auth/callback?code=…&state=… App->>Be: POST /api/auth/sso/mobile/exchange
{code, code_verifier, redirect_uri} Be->>IAM: POST /oauth/token (server-to-server) IAM-->>Be: {access_token} Be->>IAM: GET /api/auth/user IAM-->>Be: {email, name, role} Be->>Be: User::firstOrCreate + employee_id link Be->>Be: createToken('mobile', [scopes]) — Sanctum PAT Be-->>App: {access_token, user} App->>App: TokenStore.save (flutter_secure_storage)
{code_challenge, state, redirect_uri} Be->>Be: validate redirect_uri allowlist Be-->>App: {authorization_url} App->>IAM: open authorization_url in flutter_web_auth_2 IAM-->>App: redirect sanchalan-attendance://auth/callback?code=…&state=… App->>Be: POST /api/auth/sso/mobile/exchange
{code, code_verifier, redirect_uri} Be->>IAM: POST /oauth/token (server-to-server) IAM-->>Be: {access_token} Be->>IAM: GET /api/auth/user IAM-->>Be: {email, name, role} Be->>Be: User::firstOrCreate + employee_id link Be->>Be: createToken('mobile', [scopes]) — Sanctum PAT Be-->>App: {access_token, user} App->>App: TokenStore.save (flutter_secure_storage)
Operator setup
- In sb-iam admin, create two OAuth clients:
- Web (confidential) — redirect
https://attendance.rajyasabha.digital/api/auth/sso/callback - Mobile (public PKCE) — redirect
sanchalan-attendance://auth/callback
- Web (confidential) — redirect
- Populate Sanchalan
.env:AUTH_MODE=sso SBIAM_PUBLIC_URL=https://ft.rajyasabha.digital/iam SBIAM_BASE_URL=http://sb-iam-app:80 # docker-internal SBIAM_CLIENT_ID=... # web SBIAM_REDIRECT_URI=https://attendance.rajyasabha.digital/api/auth/sso/callback SBIAM_USERINFO_PATH=/api/auth/user SBIAM_SCOPES=openid profile attendance.punch attendance.leave SBIAM_MOBILE_CLIENT_ID=... # mobile PKCE SBIAM_MOBILE_REDIRECT_URIS=sanchalan-attendance://auth/callback - Recreate the app container.
- Verify
GET /api/auth/mode→{"data":{"auth_mode":"sso","sso_enabled":true,"redirect_url":"…/api/auth/sso/redirect"}}. - Visit
/admin— should bounce through sb-iam and land back as the SSO'd user.
What sb-iam returns
The userinfo endpoint contract is:
GET /api/auth/user (Bearer access_token)
=>
{
"user": {
"id": "uuid", // Parichay subject id
"email": "you@rajyasabha.digital",
"name": "Kushal Pathak",
"role": "admin", // Sanchalan reads this for RBAC
"parichay_id": "…" // optional; mirrors id
}
}
SsoController's mapUser():
- Looks up existing User by
parichay_idfirst (handles email change). - Falls back to email lookup.
- If no User exists, creates one with a random password (never used).
- Auto-links to
attendance_employee_registerby email match. - Sets the
rolefrom the IDP claim (orconfig/sso.email_roleoverride).
RBAC handoff
The role string from sb-iam is the contract. config/permissions.php maps roles to permission keys; route middleware uses keys, not roles directly. Common mappings:
| sb-iam role | Permissions granted |
|---|---|
admin | everything (admin-attendance, admin-reports, leave-approver, can_enroll_face, can_manage_*) |
hr_admin | admin-attendance, admin-reports, can_enroll_face, can_manage_holidays |
attendance_admin | admin-attendance, can_manage_geo_fences |
reports_viewer | admin-reports only |
reporting_officer | leave-approver only |
punch_user | can_punch, can_apply_leave, can_view_own_attendance |
Smoke test path (when SSO is enabled)
# 1) Confirm endpoint visibility
curl /api/auth/mode
=> {"data":{"auth_mode":"sso","sso_enabled":true,"redirect_url":"…"}}
# 2) Hit the redirect — should 302 to sb-iam
curl -I /api/auth/sso/redirect
=> HTTP/1.1 302 Found
Location: https://ft.rajyasabha.digital/iam/oauth/authorize?response_type=code&…
# 3) After SSO complete, browser session is set; subsequent /admin requests succeed.
# 4) Mobile flow:
curl -X POST -H "Content-Type: application/json" -d '{
"code_challenge":"…",
"state":"…",
"redirect_uri":"sanchalan-attendance://auth/callback"
}' /api/auth/sso/mobile/redirect
=> {"authorization_url":"https://…/oauth/authorize?…"}
Failure modes + audit
sso_state_mismatch— CSRF protection. Logged + redirect to/login?sso_error=….sso_token_exchange_failed— sb-iam rejected the code. Audit'd asauth.sso.token_exchange_failed.sso_local_user_inactive— User exists locally butactive=false. Operator must reactivate or delete.- All successful SSO logins emit
auth.loginwithvia: ssoorvia: sso_mobilein the audit chain.