How it works Identity sources Architecture Login flows Features Integration Pricing
Language
Talk to us
Identity & SSO on Authentik

One login.
Every internal app.

simpliSSO is a centralized identity portal built on Authentik and federated with your Microsoft 365 / Azure AD tenant. Staff sign in once with their existing company email — and reach every internal application: web apps over OIDC, legacy ERPs over SAML2, hardware over LDAP.

24
Services connected
12
Feature modules
10
Weeks to go-live
3
Protocols (OIDC · SAML2 · LDAP)
The login flow
Staff opens app
Browser · any tile
step 1
Authentik
Session check · policies
step 2
Azure AD
MS365 password · MFA
step 3
JWT issued · app granted
Claims · groups · roles
done
After step 2, every other app skips straight to the JWT issue — that's Single Sign-On. One login, all 24 services.
How Authentik works

The passport-control model

Azure AD is the government that issued the passport — it owns the identity. Authentik is the border officer that checks it and grants access to each specific room (application). Same passport, every door.

IdP

Identity Provider

The system that knows who you are. In simpliSSO, Authentik is the IdP, backed by Azure AD as the upstream source.

SP

Service Provider

Any app that trusts the IdP to verify users. ERPs, A/R aging, document control, every internal tool.

OIDC

OpenID Connect

Modern token-based login protocol. Used by all Tier 1 web apps. Tokens are JSON (JWT), signed by Authentik.

SAML2

SAML 2.0

Older XML-based SSO protocol. Required for legacy ERPs like Infor M3 — Authentik speaks both at the same time.

LDAP

LDAP outpost

A proxy Authentik deploys for systems that can't speak OIDC or SAML2 natively — printers, label hardware, legacy boxes.

Flow

Flow & Stage

A flow is a sequence of stages Authentik runs on login: enter password, verify OTP, link WhatsApp. Each step is configurable.

Policy

Policy

A Python rule that allows, denies, or modifies access. "Only Finance group can reach A/R Aging." Evaluated inside flows.

Mapping

Property Mapping

A Python expression that shapes the token. Used to embed ERP claims — M3 company, division, roles — directly into the JWT.

Identity sources

Works with whatever IdP you already have

Azure AD is shown in the architecture below — but Authentik federates with any standards-compliant identity source. Swap or stack providers without changing your apps.

OIDC Default

Microsoft 365 / Azure AD

The setup shown in this architecture. Staff use existing MS365 credentials and MFA flows from Conditional Access pass straight through.

OIDC

Google Workspace

Same pattern, swap the upstream. Staff sign in with their workspace.google.com account and Authentik issues the downstream tokens.

OIDC SAML 2.0

Okta

Common when consolidating off another SSO. Authentik can ride on top while you migrate apps one at a time — no big-bang cutover.

LDAP

On-prem LDAP / Active Directory

For air-gapped or strictly on-prem sites. Authentik binds to your existing AD or OpenLDAP directory — no cloud trip required.

OIDC

Generic OIDC

Any provider that publishes a .well-known/openid-configuration URL — Keycloak, Auth0, Cognito, GitLab, your own.

SAML 2.0

Generic SAML 2.0

Any IdP that publishes a SAML metadata XML — ADFS, Shibboleth, OneLogin, internal IdPs. Authentik consumes the metadata and federates.

Same pattern for all of them — Authentik handles the federation, your apps never see the upstream change. You can also stack multiple sources in one flow (e.g., MS365 for staff + LDAP for contractors).

Architecture

One IdP, every protocol, every tier

Azure AD is the source of truth. Authentik is the single SSO hub. Every internal app — modern, legacy, or hardware — connects through one of three protocols.

Authentik

IdP · Open-source

The single SSO gatekeeper. Self-hosted on your infrastructure, never stores passwords — it federates upstream to Azure AD and issues tokens downstream to every app.

  • OIDC, SAML2, and LDAP providers in one stack
  • Custom Python policies, stages, and mappings
  • Blueprint YAML — full config-as-code
  • Docker Compose deployment, 4 vCPU / 8 GB

Microsoft 365 / Azure AD

Source of truth

Your existing Microsoft tenant remains the authoritative directory. Staff sign in with the same company email and password they already use for Office 365 — no new accounts to manage.

  • Federation via Azure Enterprise App registration
  • MFA pass-through from Conditional Access
  • SCIM push — instant provisioning & deprovisioning
  • UPN, groups, department, display name mapping

Custom Python layer

Yours · IP-owned

All custom logic runs inside Authentik as expression policies, property mappings, and Django stages. No external services. All Python code becomes your IP on final payment.

  • JWT claim shaping for ERP roles (M3, A/R)
  • Step-up MFA expression policy
  • WhatsApp & 3CX linking stages
  • FastAPI tile launcher for the app portal
Tier 1

Web apps · OIDC / SAML2

All modern internal apps over OIDC. Legacy ERPs over SAML2. Tokens carry custom claims so apps read roles directly — no separate permission database.

Infor M3 SCE Mobile RF Web A/R Aging Warehouse Report Document Control IT Services Wolf Approve Building Service FutureSkill AI Projects Report
Tier 2

Comms · Custom linking

Per-user account linking stages bind each staff member's WhatsApp number and 3CX extension to their Azure AD account on first login. Portal tiles deep-link to the right conversation.

WhatsApp Business 3CX IP Phone
Tier 3

Hardware · LDAP

Printers, label hardware, and legacy boxes that cannot speak OIDC or SAML2 authenticate against the LDAP outpost. Robot systems connect via portal deep link only.

Kyocera Print Print Label WCS Robot
System diagram

What talks to what

Staff hit the portal · Authentik checks session and federates to Azure AD · tokens flow downstream to every tier via the right protocol.

flowchart LR classDef staff fill:#1c1917,stroke:#a8a29e,color:#fef3c7 classDef portal fill:#1c1917,stroke:#a8a29e,color:#fef3c7 classDef ak fill:#082f49,stroke:#38bdf8,color:#e0f2fe classDef azure fill:#052e16,stroke:#22c55e,color:#dcfce7 classDef tier1 fill:#0c2030,stroke:#22d3ee,color:#cffafe classDef tier2 fill:#1e1b4b,stroke:#818cf8,color:#e0e7ff classDef tier3 fill:#3a1f0a,stroke:#f97316,color:#fed7aa STAFF["Staff"]:::staff PORTAL["App Portal"]:::portal subgraph AK["Authentik (IdP)"] direction TB FLOW["Login Flow"]:::ak OIDC["OIDC provider"]:::ak SAML["SAML2 provider"]:::ak LDAP["LDAP outpost"]:::ak MAP["Property mappings
(JWT claims)"]:::ak MFA["Step-up MFA policy"]:::ak end subgraph AZ["Microsoft 365"] AAD["Azure AD"]:::azure M365["Office 365"]:::azure end T1["Tier 1 — Web apps
OIDC + SAML2"]:::tier1 T2["Tier 2 — Comms
WhatsApp · 3CX"]:::tier2 T3["Tier 3 — Hardware
Kyocera · Print Label"]:::tier3 STAFF -->|browse| PORTAL PORTAL -->|redirect| FLOW FLOW <-->|federate| AAD FLOW --> MFA FLOW --> OIDC FLOW --> SAML OIDC --> MAP MAP -->|JWT + claims| T1 SAML -->|assertion| T1 LDAP -->|bind| T3 PORTAL -->|deep link| T2 AAD --- M365
Login flows

What staff actually experience

Three flows cover 99% of daily use: the first login of the day, returning to another app, and the additional MFA challenge on sensitive systems.

Scenario A — first login of the day. Staff opens the portal, signs in once via Azure AD, and the portal personalizes the app tile grid based on their group memberships.

sequenceDiagram actor Staff participant Portal as App Portal participant Authentik as Authentik participant AzureAD as Azure AD participant App as Target App Staff->>Portal: Open portal URL Portal->>Authentik: No session → /authorize Authentik->>AzureAD: Redirect → MS365 login AzureAD-->>Staff: Show MS365 login Staff->>AzureAD: Email + password (+ MFA) AzureAD->>Authentik: Return auth code Authentik->>AzureAD: Exchange code for tokens AzureAD-->>Authentik: ID token + profile Note over Authentik: Run policies
(group check, step-up MFA?) Authentik-->>Staff: Set session cookie Authentik-->>Portal: User profile + tile list Portal-->>Staff: Personalized app portal Staff->>App: Click tile App->>Authentik: /authorize (session found) Authentik->>App: OIDC token + claims App-->>Staff: Grant access ✓

Scenario B — returning user. Same staff member, same browser, different app. The session cookie carries them straight through — zero prompts, zero passwords re-entered.

sequenceDiagram actor Staff participant App as Target App participant Authentik as Authentik Staff->>App: Open app URL directly App->>Authentik: No app session → /authorize Note over Authentik: Session cookie found
No login needed Authentik->>App: OIDC token + claims App-->>Staff: Grant access ✓ (zero prompts)

Scenario C — step-up MFA. When the same user opens a sensitive system (IT Services admin, finance apps), Authentik triggers an extra MFA challenge if the last verification was more than 30 minutes ago.

sequenceDiagram actor Staff participant App as Sensitive App participant Authentik as Authentik participant MFA as Authenticator Staff->>App: Open sensitive app App->>Authentik: /authorize Note over Authentik: Session found
Policy: SENSITIVE_APPS + last_mfa > 30 min Authentik-->>Staff: MFA challenge Staff->>MFA: Open authenticator MFA-->>Staff: One-time code Staff->>Authentik: Submit OTP Note over Authentik: OTP valid
Update last_mfa_time Authentik->>App: Token + elevated claims App-->>Staff: Grant access ✓

Scenario D — SCIM provisioning. IT creates or deactivates a staff account once in Azure AD. SCIM pushes the change to Authentik and every connected app reflects it instantly — no per-app setup, no manual cleanup on resignation.

sequenceDiagram participant Admin as IT Admin participant AzureAD as Azure AD participant Authentik as Authentik participant Apps as All 24 Apps Admin->>AzureAD: Create new staff account AzureAD->>Authentik: SCIM push → create user Note over Authentik: Apply group memberships
Set app access policies Authentik-->>Apps: Visible in all authorized apps Note over Admin: No manual per-app setup Admin->>AzureAD: Deactivate (resignation) AzureAD->>Authentik: SCIM push → deactivate Note over Authentik: Invalidate active sessions Authentik-->>Apps: Access revoked instantly
Feature modules

12 independently scoped modules

Each module is priced and delivered independently. Add or remove before signing with proportional price adjustment. Infrastructure (F-01, F-02) and Core SSO (F-03) are mandatory — the rest can be phased.

F-01 Infrastructure

MS365 / Azure AD federation

Azure as upstream IdP. All 24 services inherit MS365 login, MFA, and conditional access. Staff use existing company credentials.

All 24 services
F-02 Infrastructure

Authentik install & hardening

Self-hosted Docker Compose production stack: PostgreSQL, Redis, Nginx SSL, backups, admin portal, branding.

Platform
F-03 Core SSO

24-service application config

Register all 24 services as OIDC or SAML2 Applications. Group-based access policies. Blueprint YAML config-as-code.

All 24 services
F-05 Custom Python

JWT claims — ERP role mapping

Python property mappings embed company code, division, and M3 roles into the token. Apps read roles directly — no separate permission DB.

Infor M3 · Web A/R Aging
F-06 Custom Python

Step-up MFA policy

Expression policy enforces an extra MFA challenge when accessing designated sensitive systems. ~20 lines of Python.

IT Services · finance apps
F-07 Custom Python

App portal — tile launcher API

FastAPI endpoint renders only the apps each user is authorized to see. Tile icons + Thai/English labels + deep links.

App Portal
F-08 Custom Python

WhatsApp account linking

Custom Django stage links each staff member's WhatsApp number to their AD account on first login. Portal tile deep-links to the right conversation.

WhatsApp Business
F-09 Custom Python

3CX extension linking

Maps each staff member's 3CX extension to their AD account. Portal tile launches the web client pre-authenticated to the right extension.

3CX IP Phone
F-10 Custom Python

Infor M3 SAML2 workaround

Custom property mappings bridge Authentik's modern SAML2 output to M3's non-standard attribute names and NameID requirements.

Infor M3
F-11 UI

Thai language UI customization

CSS + JS injection localizes login, MFA prompts, error messages, and password reset into Thai. Includes branding on every auth screen.

All login screens
F-12 QA

Testing, QA & user acceptance

End-to-end testing across all 24 services. Edge cases: token expiry, MFA failures, session conflicts, SAML mismatches, LDAP connectivity. 2-hour UAT.

All 24 services
F-13 Handover

Documentation & knowledge transfer

Architecture diagrams, admin runbook, onboarding/offboarding guides, troubleshooting — Thai and English. 2-hour live handover session.

Platform

Hardware (Kyocera Print, Print Label, WCS Robot) is client-provided and excluded from scope. Portal deep links to hardware systems are included where technically possible.

Integration

Drop SSO into your existing apps

Every app needs three things: redirect to Authentik, handle the callback, validate the session. The pattern is identical across frameworks — only the library differs. Four env vars per app, no homegrown auth code.

OIDC discovery — one URL to rule them all

Every Authentik OIDC provider auto-publishes a discovery document. New apps only need a client_id and client_secret — URLs, signing algorithms, and supported scopes are fetched from this single endpoint.

GET https://sso.example.com/application/o/<app-slug>/.well-known/openid-configuration
Authentik returns issuer, all endpoints, JWKS URI, supported scopes (incl. custom ERP claims from F-05), and signing algorithms. Clients auto-configure.
# pip install authlib fastapi itsdangerous
from fastapi import FastAPI, Request, Depends, HTTPException
from fastapi.responses import RedirectResponse
from authlib.integrations.starlette_client import OAuth
from starlette.middleware.sessions import SessionMiddleware
import os

app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=os.environ["SESSION_SECRET"])

oauth = OAuth()
oauth.register(
    name="authentik",
    client_id=os.environ["OIDC_CLIENT_ID"],
    client_secret=os.environ["OIDC_CLIENT_SECRET"],
    server_metadata_url=os.environ["OIDC_ISSUER_URL"] + ".well-known/openid-configuration",
    client_kwargs={"scope": "openid email profile groups"},
)

@app.get("/login")
async def login(request: Request):
    return await oauth.authentik.authorize_redirect(
        request, request.url_for("auth_callback"))

@app.get("/auth/callback")
async def auth_callback(request: Request):
    token = await oauth.authentik.authorize_access_token(request)
    user = token["userinfo"]
    request.session["user"] = {
        "sub":    user["sub"],
        "email":  user["email"],
        "groups": user.get("groups", []),
        # Custom ERP claims from F-05
        "m3_roles":        user.get("m3_roles", []),
        "ar_access_level": user.get("ar_access_level", "none"),
    }
    return RedirectResponse(url="/")

async def require_auth(request: Request) -> dict:
    user = request.session.get("user")
    if not user:
        raise HTTPException(status_code=401)
    return user

@app.get("/dashboard")
async def dashboard(user: dict = Depends(require_auth)):
    return {"hello": user["email"], "groups": user["groups"]}
# pip install mozilla-django-oidc

# settings.py
INSTALLED_APPS += ["mozilla_django_oidc"]
AUTHENTICATION_BACKENDS = ["myapp.auth.AuthtikBackend"]

OIDC_RP_CLIENT_ID     = os.environ["OIDC_CLIENT_ID"]
OIDC_RP_CLIENT_SECRET = os.environ["OIDC_CLIENT_SECRET"]
OIDC_RP_SIGN_ALGO     = "RS256"
OIDC_OP_JWKS_ENDPOINT          = ISSUER + "jwks/"
OIDC_OP_AUTHORIZATION_ENDPOINT = ISSUER + "authorize/"
OIDC_OP_TOKEN_ENDPOINT         = ISSUER + "token/"
OIDC_OP_USER_ENDPOINT          = ISSUER + "userinfo/"
OIDC_RP_SCOPES = "openid email profile groups"
LOGIN_URL          = "/oidc/authenticate/"
LOGIN_REDIRECT_URL = "/"

# urls.py
urlpatterns = [path("oidc/", include("mozilla_django_oidc.urls")), ...]

# auth.py — sync AD groups + custom ERP claims to local user
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
from django.contrib.auth.models import Group

class AuthtikBackend(OIDCAuthenticationBackend):
    def update_user(self, user, claims):
        user.groups.clear()
        for name in claims.get("groups", []):
            group, _ = Group.objects.get_or_create(name=name)
            user.groups.add(group)
        profile = user.profile
        profile.m3_roles        = claims.get("m3_roles", [])
        profile.ar_access_level = claims.get("ar_access_level", "none")
        profile.save()
        return super().update_user(user, claims)

# views.py
from django.contrib.auth.decorators import login_required

@login_required
def dashboard(request):
    return render(request, "dashboard.html", {"user": request.user})
// npm install openid-client express-session
const { Issuer, generators } = require("openid-client");
const session = require("express-session");

let client;
async function initOIDC() {
  const issuer = await Issuer.discover(process.env.OIDC_ISSUER_URL);
  client = new issuer.Client({
    client_id:     process.env.OIDC_CLIENT_ID,
    client_secret: process.env.OIDC_CLIENT_SECRET,
    redirect_uris: ["https://yourapp.example.com/auth/callback"],
    response_types: ["code"],
  });
}

function requireAuth(req, res, next) {
  if (!req.session.user) return res.redirect("/login");
  next();
}

app.get("/login", (req, res) => {
  req.session.state = generators.state();
  req.session.nonce = generators.nonce();
  res.redirect(client.authorizationUrl({
    scope: "openid email profile groups",
    state: req.session.state,
    nonce: req.session.nonce,
  }));
});

app.get("/auth/callback", async (req, res) => {
  const params = client.callbackParams(req);
  const tokenSet = await client.callback(
    "https://yourapp.example.com/auth/callback", params,
    { state: req.session.state, nonce: req.session.nonce }
  );
  const claims = tokenSet.claims();
  req.session.user = {
    sub:           claims.sub,
    email:         claims.email,
    groups:        claims.groups        || [],
    // Custom ERP claims from F-05
    m3Roles:       claims.m3_roles      || [],
    arAccessLevel: claims.ar_access_level || "none",
  };
  res.redirect("/");
});

app.get("/dashboard", requireAuth, (req, res) => res.json(req.session.user));
// composer require league/oauth2-client
<?php
use League\OAuth2\Client\Provider\GenericProvider;
session_start();

$issuer   = getenv("OIDC_ISSUER_URL");
$provider = new GenericProvider([
    "clientId"                => getenv("OIDC_CLIENT_ID"),
    "clientSecret"            => getenv("OIDC_CLIENT_SECRET"),
    "redirectUri"             => "https://yourapp.example.com/callback.php",
    "urlAuthorize"            => $issuer . "authorize/",
    "urlAccessToken"          => $issuer . "token/",
    "urlResourceOwnerDetails" => $issuer . "userinfo/",
    "scopes"                  => "openid email profile groups",
]);

// login.php — start the dance
if (!isset($_GET["code"])) {
    $_SESSION["oauth2state"] = $provider->getState();
    header("Location: " . $provider->getAuthorizationUrl());
    exit;
}

// callback.php — exchange code for token
if ($_GET["state"] !== $_SESSION["oauth2state"]) exit("CSRF");

$token = $provider->getAccessToken("authorization_code", ["code" => $_GET["code"]]);
$user  = $provider->getResourceOwner($token)->toArray();

$_SESSION["user"] = [
    "sub"             => $user["sub"],
    "email"           => $user["email"],
    "groups"          => $user["groups"]          ?? [],
    // Custom ERP claims from F-05
    "m3_roles"        => $user["m3_roles"]        ?? [],
    "ar_access_level" => $user["ar_access_level"] ?? "none",
];
header("Location: /index.php");
Four env vars per app

That's the whole contract between your app and simpliSSO. Vendor provides the client_id / client_secret pair after Authentik setup; you generate a random session secret; the issuer URL stays constant across all apps.

# .env — never commit to source control
OIDC_CLIENT_ID     = "your-app-client-id"
OIDC_CLIENT_SECRET = "your-app-client-secret"
SESSION_SECRET     = "random-256-bit-string"
OIDC_ISSUER_URL    = "https://sso.example.com/application/o/<app-slug>/"
Engagement scope

Scope the full 24-service stack

12 independently scoped modules. Add or remove any before signing for a proportional adjustment. Mandatory baseline is Infrastructure + Core SSO (F-01 · F-02 · F-03). Pricing is quoted per deployment — talk to us for a fixed-price proposal.

Scope by category
Infrastructure
F-01 · F-02
Mandatory
Core SSO
F-03
Mandatory
Custom Python
F-05 · F-06 · F-07 · F-08 · F-09 · F-10
Phaseable
UI
F-11
Phaseable
QA
F-12
Included
Handover
F-13
Included
Total · 12 modules · 24 services
Quoted per deployment
Payment milestones

Payment is tied to verified deliverables, not calendar dates.

Contract signing
30%
Upon signed agreement
MS365 federation live
40%
Azure AD + Authentik connected, first 5 apps live
Go-live & handover
30%
All 24 services live, UAT signed off
Delivery

10 weeks from kickoff to go-live

Phased rollout — federation and core SSO first, then custom integrations, with testing and handover compressed at the end.

Week 1–2 F-01 · F-02

Infrastructure

Authentik install, MS365 federation, SSL termination, admin portal configuration.

Week 3–4 F-03

Core SSO

All 24 apps configured as OIDC or SAML2 Applications in Authentik. Group-based access policies.

Week 5 F-05 · F-06 · F-10

ERP & security

Custom JWT claims, step-up MFA policy, Infor M3 SAML2 workaround.

Week 6 F-11

UI

Thai language CSS, company branding on all auth screens, mobile responsive verification.

Week 7–8 F-07 · F-08 · F-09

Custom integrations

App portal tile API, WhatsApp account linking, 3CX extension linking.

Week 9–10 F-12 · F-13

Testing & handover

End-to-end QA, user acceptance session, documentation, go-live.

Optional retainer

Monthly support — month-to-month, no lock-in

Optional after go-live. 30-day written notice to cancel. Pick the tier that matches your operational tempo.

Basic
Next business day
Response SLA · scoped per deployment
  • Bug fixes
  • User provisioning support
  • Minor config changes
Recommended
Standard
4 business hours
Response SLA · scoped per deployment
  • Everything in Basic
  • Minor feature additions
  • Monthly health check
Premium
2 business hours
Response SLA · scoped per deployment
  • Everything in Standard
  • New integrations (up to 2 / month)
  • Priority escalation
Get in touch

Ready to consolidate your logins?

Tell us how many apps, which protocols, and whether you're on MS365 or another upstream IdP. We'll scope a fixed-price proposal in a single call.

LINE
ID: iiitum1984
Call / WhatsApp
(+66) 83001 0222
Chat on LINE WhatsApp us Email us