This guide explains UR SSO for leaders, stakeholders, and developers building UR apps (Umutungo, AMS, Library, …). It covers what SSO is, what data apps receive, and a step-by-step process to connect a new application.
This repository is the public home for UR SSO documentation and diagrams. App teams use it without needing access to the Inuma codebase.
Platform / IdP operations (issuer keys, cookie domain) are handled by the Inuma SSO administrators — contact them if you need production issuer details.
Quick developer reference: DEVELOPER.md.
- Executive Summary
- What Is Single Sign-On?
- Benefits & Business Value
- What the Staff Experience Looks Like
- How It Works
- What Data Apps Can Receive
- The Login Flow
- How to Connect Your App to UR SSO
- Security & Logout
- Rollout Roadmap
- FAQ
- Glossary
The UR Single Sign-On (SSO) Portal lets every staff member use one account — their existing Inuma account — to log in to all University of Rwanda digital systems. Instead of remembering a different username and password for each system, staff sign in once and are automatically recognized everywhere.
We already hold accurate staff information and login credentials inside Inuma. SSO turns Inuma into the central identity system ("the front door") for the University, so new and existing applications can rely on it for secure login.
In one sentence: One UR account, one login, every UR system — secure, consistent, and centrally managed.
This is built on OpenID Connect (OIDC) — the same open standard behind "Sign in with Google" — so it is proven, secure, and future-proof.
Today, each system tends to have its own separate login. Staff juggle multiple passwords; IT manages multiple user lists; when someone leaves, their access has to be removed from many places.
Single Sign-On solves this. There is one trusted login service (Inuma). Every other system asks Inuma: "is this person who they say they are?" The staff member logs in once, and every connected system trusts that answer.
Think of it like a staff ID badge for the digital world: you show it once at the main entrance, and it opens every door you are authorized to use — instead of a separate key for every room.
For staff
- One account and one password to remember (or passwordless email-code login).
- Log in once, move between systems without logging in again.
- A familiar, branded University of Rwanda Single Sign-On experience.
For the University / management
- Single source of truth for identity — staff data lives in one place.
- Faster onboarding: a new app can be connected quickly via the admin portal.
- Instant offboarding: disable one Inuma account and access to every connected system is revoked for new logins.
- Consistency: shared identity across UR systems.
For security & compliance
- Central control and an audit trail of who signed into what and when.
- Fewer passwords stored across systems = smaller attack surface.
- Standards-based technology (OpenID Connect / OAuth 2.0).
For cost & scale
- Reuses staff data and credentials already in Inuma — no separate identity product to license.
- Ready to connect third-party tools later (e.g. Moodle) because it follows an open standard.
- A staff member opens any UR system (for example, AMS or a Library app).
- If they are not logged in, they are taken to the familiar UR Single Sign-On page (Inuma login), which shows something like "Sign in to continue to Library".
- They log in with their Inuma email + password (or a one-time email code).
- They are sent straight back into the app — already signed in.
- When they open another UR system in the same browser session, they are not asked to log in again (silent SSO).
- Optionally, a "My Apps" page in Inuma lists connected systems they can open with one click.
Staff do not need to visit Inuma first. They can start from any connected app and click Sign in with UR SSO.
Inuma is the Identity Provider (IdP). Each other UR application is a client. They speak OpenID Connect (OIDC) using Authorization Code + PKCE — the recommended pattern for web and single-page apps.
Key ideas
- There is no second password store — login reuses Inuma credentials and session.
- Apps receive signed tokens, never the password.
- Admins decide which apps may use SSO and which staff fields each app may receive.
- SSO answers "who is this person?" Each app still decides "what can they do here?" with its own roles.
When an admin onboards an app, they grant scopes. The app only receives the claims allowed by those scopes.
| Scope | What the app can receive |
|---|---|
openid (required) |
Stable user id (sub) |
profile |
Name, position, function, staff level, unit type, unit, department, school, campus, organisation, institution, employee id, picture |
email |
Email (+ verified) |
phone |
Phone number |
ur_roles |
Coarse role only (e.g. staff / hod) — not Inuma’s fine-grained permissions |
ur_acting |
Whether they are acting, and who/where they are acting for (name, position, unit) |
ur_hr |
Gender, date of birth, degree, qualification, employment date/group (opt-in) |
offline_access |
Refresh tokens (not profile data) |
Defaults for new apps: openid profile email ur_roles offline_access
(ur_acting and ur_hr are opt-in.)
Never sent via SSO: password, bank details, RSSB, or other highly sensitive fields.
{
"sub": "68e4e5ca9b137a806e0d5b5a",
"name": "Jane Doe",
"email": "jane@ur.ac.rw",
"email_verified": true,
"position": "Asset Manager",
"function": "Academic Staff",
"staff_level": "L5",
"unit_type": "DEPARTMENT",
"unit": "Computer Science",
"department": "Computer Science",
"school": "College of Science",
"campus": "Nyarugenge",
"institution": "University of Rwanda",
"employee_id": 12345,
"role": "staff",
"is_acting": true,
"acting_for": [
{
"id": "68e4e5ca9b137a806e0d5b5b",
"name": "Jean Official",
"position": "Dean",
"unit_type": "SCHOOL",
"unit": "School of ICT"
}
]
}Once the Inuma session exists in that browser, opening another UR app usually needs no second password.
SSO answers "Who are you?" — applications answer "What can you do?"
This section is for anyone building a UR application (Umutungo, AMS, Library, HR tools, …). Think of it like connecting "Login with Google" — except the login provider is Inuma / UR SSO.
After the admin gives you a Client ID, paste the ready-made prompt from
AGENT_PROMPT.md into Cursor / Claude Code / Copilot Chat.
Fill in issuer, Client ID, redirect URI, and scopes — the agent can wire the
integration into your app.
Email or message the Inuma / UR SSO administrator and ask them to onboard your app. Send them this information:
| What to send | Example (Umutungo) | Why |
|---|---|---|
| App name | Umutungo |
Shown on login / My Apps |
| App home URL | https://umutungo.ur.ac.rw |
Launcher link |
| Login callback URL(s) | https://umutungo.ur.ac.rw/callback |
Where Inuma returns after login — must match exactly |
| Logout return URL(s) | https://umutungo.ur.ac.rw |
Where users land after SSO logout |
| App type | Browser SPA (React) or server app | Public + PKCE vs Confidential + secret |
| Scopes you need | Usually openid profile email ur_roles offline_access |
What staff fields you receive |
| Local/dev callback (optional) | http://localhost:5173/callback |
So you can test on your machine |
Ask for acting/HR data only if you truly need it (ur_acting, ur_hr).
The admin registers the app in Inuma → UR SSO → Onboarding Apps.
The admin will send you back:
| You receive | Browser SPA (most UR apps) | Server app |
|---|---|---|
| Client ID | Yes (e.g. ur_83d8…) |
Yes |
| Client secret | No — never put a secret in frontend code | Yes — store only on the server |
| Issuer URL | https://inuma.ur.ac.rw/oidc |
Same |
| Allowed scopes | The list they approved | Same |
| Registered redirect URI | Exactly what you asked for | Same |
Keep the Client ID (and secret, if any) out of public git repos when possible — use environment variables.
For a typical React / Vite UR app:
npm install oidc-client-ts react-oidc-contextOther stacks:
| Stack | Suggested library |
|---|---|
| React SPA | oidc-client-ts + react-oidc-context |
| Vue / plain JS SPA | oidc-client-ts |
| Node / Next server | openid-client (confidential client) |
A full copy-paste React sample lives in
examples/oidc-client-react.
Create (or update) .env in your app:
# Inuma OIDC issuer (do not add a trailing slash after /oidc unless told to)
VITE_SSO_ISSUER=https://inuma.ur.ac.rw/oidc
# From the SSO admin
VITE_SSO_CLIENT_ID=ur_xxxxxxxxxxxxxxxxLocal development example:
VITE_SSO_ISSUER=http://localhost:3000/oidc
VITE_SSO_CLIENT_ID=ur_xxxxxxxxxxxxxxxxImportant: the redirect URI your code builds (see next step) must be
exactly one of the URIs the admin registered (including http vs https,
port, and path).
Example config (React):
// src/oidcConfig.ts
import { WebStorageStateStore } from 'oidc-client-ts';
import type { AuthProviderProps } from 'react-oidc-context';
export const oidcConfig: AuthProviderProps = {
authority: import.meta.env.VITE_SSO_ISSUER,
client_id: import.meta.env.VITE_SSO_CLIENT_ID,
redirect_uri: `${window.location.origin}/callback`,
post_logout_redirect_uri: window.location.origin,
response_type: 'code',
scope: 'openid profile email ur_roles offline_access',
userStore: new WebStorageStateStore({ store: window.localStorage }),
automaticSilentRenew: true,
onSigninCallback: () => {
window.history.replaceState({}, document.title, window.location.pathname);
},
};Wrap your app with the auth provider (once, at the root):
// src/main.tsx
import { AuthProvider } from 'react-oidc-context';
import { oidcConfig } from './oidcConfig';
<AuthProvider {...oidcConfig}>
<App />
</AuthProvider>;Use the same scopes the admin granted. Do not request scopes that were not approved.
On your login screen (Umutungo login, AMS login, …), add a button that starts the redirect:
import { useAuth } from 'react-oidc-context';
function LoginPage() {
const auth = useAuth();
return (
<button type="button" onClick={() => void auth.signinRedirect()}>
Sign in with UR SSO
</button>
);
}What happens when the user clicks it:
- Browser goes to Inuma SSO.
- User signs in with their existing Inuma account (if not already signed in).
- Inuma sends them back to your
/callbackwith a one-time code. - The library exchanges the code for tokens.
- Your app is signed in — you never see their password.
Make sure your router serves the app at /callback (same origin as
redirect_uri). With react-oidc-context, mounting <App /> (inside
AuthProvider) on both / and /callback is enough — the library detects
?code= and finishes login.
After success, read the staff profile:
const auth = useAuth();
const profile = auth.user?.profile;
// Examples of claims (if scopes were granted):
// profile.sub, profile.name, profile.email, profile.position,
// profile.unit_type, profile.unit, profile.role, profile.acting_forCall your own APIs with:
Authorization: Bearer <access_token>Your API should validate the token against Inuma’s JWKS
({issuer}/jwks).
SSO tells you who the person is. Your app still decides what they can do.
Typical pattern for Umutungo / AMS / any UR product:
- After SSO login, read
emailorsub. - Find or create the user in your database.
- Apply your roles (
umutungo_admin,asset_manager, …) from your DB. - Optionally use Inuma claims (
unit,campus,acting_for, …) as context.
Your roles do not need to match Inuma roles. Inuma permissions are not sent over SSO.
| Check | Expected |
|---|---|
| Click Sign in with UR SSO | Redirects to Inuma login (or silent SSO if already logged in) |
| After login | Returns to your app signed in; profile has name/email |
| Wrong redirect URI | Error redirect_uri mismatch → fix URI with admin |
| Second UR app in same browser | Often no password again (silent SSO) |
| Sign out | Ends session via Inuma when using signoutRedirect() |
- Ask the admin to register the production redirect URI(s).
- Put production
VITE_SSO_ISSUER+VITE_SSO_CLIENT_IDon the server. - Optionally ask them to enable Show in launcher so the app appears on Inuma My Apps.
Inuma admins manage apps under UR SSO:
| Page | Path | Purpose |
|---|---|---|
| Overview | /admin/sso |
Counts and shortcuts |
| Onboarding Apps | /admin/sso/apps |
Create/edit apps, scopes, logo, activate/deactivate |
| SSO Logs | /admin/sso/logs |
Audit of SSO events |
| Client type | Use when |
|---|---|
| Public (PKCE) | Browser SPAs — no secret |
| Confidential | Server apps — secret shown once |
- Contact SSO admin with app name, URLs, type, and scopes
- Receive Client ID (+ secret only if confidential)
- Optional: paste
AGENT_PROMPT.mdinto Cursor / Claude Code -
npm install oidc-client-ts react-oidc-context(or equivalent) - Set
VITE_SSO_ISSUERandVITE_SSO_CLIENT_IDin.env - Configure OIDC (
authority,client_id,/callback) - Add Sign in with UR SSO button
- Handle
/callbackand read profile claims - Map
sub/ email → your app’s users and roles - Test login, mismatch errors, and logout
- Switch env to production issuer + Client ID
Security highlights
- PKCE required for every client.
- Exact redirect URI matching (no wildcards).
- Short-lived access tokens; optional refresh tokens.
- Rate limiting and audit logging of SSO events.
- Passwords never leave Inuma; apps only see signed tokens and allowed claims.
- Deactivating an Inuma account blocks new SSO logins for that person.
Logout
- An app can send the user to Inuma to end the central session.
- Apps can also register for back-channel logout so sessions end everywhere when the user signs out.
- Stand up the Inuma SSO provider.
- Harden production (stable signing keys, shared cookie domain, HTTPS).
- Pilot with one application and real staff.
- Onboard remaining UR systems via the admin portal.
- Enable full Single Logout where needed.
No. They use their existing Inuma account.
Yes — same idea, university-hosted. Inuma is the identity provider for UR apps. Staff click Sign in with UR SSO, authenticate at Inuma if needed, and return with tokens. The app never sees the password.
No. They can open any connected app and use Sign in with UR SSO. If they are already signed into Inuma in that browser, the next app often gets silent SSO. My Apps is optional convenience, not a requirement.
Follow §8 How to Connect Your App to UR SSO: contact the admin → get Client ID → install library → set env → add login button → handle callback → map users/roles in your app.
- Browser / SPA → Public + PKCE (no secret in the frontend).
- Server backend → Confidential (secret stored only on the server).
Only the claims for scopes the admin granted (see §6). Never the password. Inuma permissions are not shared over SSO.
Least privilege and privacy. Sensitive fields (bank, RSSB, password) stay out of
OIDC. Extra fields use opt-in scopes (ur_hr, ur_acting) or a separate secure
API when truly needed. Admins still choose what each app may request.
Expected. SSO proves identity; the app owns authorization. Match the person by
email/sub, then use the app’s own roles.
Yes. Deactivate (or delete) the client under Onboarding Apps. Inactive apps cannot complete new SSO logins.
Never. Apps only receive signed tokens and allowed identity claims.
Yes — Public client + PKCE, no stored secret in the app.
If it supports standard OpenID Connect, it can be onboarded the same way.
Deactivate their Inuma account. New logins to connected systems that rely on Inuma SSO are blocked. Enable back-channel logout so open app sessions can end too.
Login to connected apps depends on the identity provider, so Inuma should be run for high availability as the university front door.
- SSO (Single Sign-On): log in once, access many systems.
- IdP (Identity Provider): the trusted login service — here, Inuma.
- Client / Relying Party: an app that relies on the IdP for login.
- OIDC (OpenID Connect): the open identity standard used (on OAuth 2.0).
- PKCE: security extension that protects the login exchange.
- Scope: bucket that controls which claims an app may receive.
- Claim: a piece of identity info (name, email, unit, role, …).
- Access token / ID token: prove API access and who the user is.
- Public client: browser/native app using PKCE with no client secret.
- Confidential client: server app that authenticates with a client secret.
- Trusted client: first-party UR app that can skip the consent screen.
Technical reference: OIDC_SSO_README.md ·
Client example: examples/oidc-client-react




