Projekt · Entra ID

End-to-End IAM: Från HR till Entra ID med Elytra och Ascendra

By Andreas Krisby 2026-07-24 4 min read

Bakgrund

end-to-end-iam är ett sidoprojekt där jag utforskar hur ett modernt IAM-landskap kan byggas. Projektet kretsar kring ett fiktivt företag, Legatus Corp, och består av två separata system.

  • Elytra: ett HR-system som är source of truth för anställningsdata.
  • Ascendra: en IGA-plattform (Identity Governance & Administration) som hanterar identiteter, roller, provisionering, SSO-anslutningar och audit.

Jag satte riktningen för projektet: att systemen skulle vara helt separata, att Convex skulle användas som plattform, och att IGA-systemet skulle kopplas mot en riktig Entra ID-tenant. Själva app-registreringen, permissions och client secret i Entra ID satte jag upp själv. Implementationen i kod byggdes med AI-assisterad utveckling.

Det är samma problem som alla organisationer med fler än en handfull anställda behöver lösa. När någon börjar ska rätt konton skapas automatiskt. När någon byter roll ska behörigheter uppdateras. När någon slutar ska åtkomsten försvinna snabbt och säkert. Det är precis det Joiner-Mover-Leaver-flödet (JML) det här projektet modellerar.

Till en början var kopplingen mot identitetsleverantören helt simulerad. “Test connection” skrev bara en rad i audit-loggen oavsett vad som faktiskt var konfigurerat. Ingen Entra ID, inga Graph-anrop och ingen riktig provisionering. Jag ville se hur lösningen fungerade mot en riktig katalog.

Det jag byggde

Arkitektur

Elytra och Ascendra är medvetet helt separata system.

  • Separata Convex-deployments
  • Ingen delad databas
  • Ingen direkt kommunikation mellan systemen

Den enda kopplingen är en append-only outbox. Elytra publicerar:

  • employee.created
  • employee.updated
  • employee.activated
  • employee.terminated

Ascendra pollar händelserna med en cursor och bygger upp sin egen identitetsmodell. Det här liknar hur många större organisationer separerar HR från sin IGA-plattform.

Elytra – Onboard employee, det första steget i JML-flödet

Provisionering till Entra ID

Det som saknades var nästa steg. Jag ville inte bara skapa identiteter inne i Ascendra utan faktiskt skapa, uppdatera och inaktivera riktiga användare i Entra ID.

Första tanken var SCIM. Det visade sig vara fel väg. När Entra ID är målkatalog finns ingen inkommande SCIM-endpoint att provisionera mot. SCIM används i stället av Entra när den själv provisionerar användare vidare till andra SaaS-tjänster.

Den korrekta lösningen blev därför Microsoft Graph API med application permissions och Client Credentials-flödet. Appregistreringen använder:

  • Single Tenant
  • Ingen Redirect URI
  • User.ReadWrite.All
  • Organization.Read.All
  • Application Permissions
  • Admin Consent
  • Client Secret

Det jag testade

  • Verifiering mot en riktig Entra-tenant via Microsoft Graph
  • Automatisk onboarding
  • Uppdatering av användare
  • Offboarding
  • Retry av misslyckade provisioneringar
  • Manuell Resync
  • Periodisk reconciliation mellan HR och Entra

Resultat

Provisioneringen är nu verklig hela vägen. När en HR-händelse skapas i Elytra materialiseras den i Ascendra som sedan skapar, uppdaterar eller inaktiverar ett riktigt konto i Entra ID.

Utöver den händelsestyrda provisioneringen finns även:

  • Reconciliation var 15:e minut
  • Retry av misslyckade Graph-anrop
  • SLA-varningar om onboarding eller offboarding tar för lång tid

Auth0 och den generiska SCIM-integrationen är fortfarande simulerade, eftersom de inte behövdes för just den här delen av projektet.

Entra ID – Granskningslogg som visar att Ascendra skapat och uppdaterat ett riktigt användarkonto

Utmaningar

Det intressanta började först när integrationen blev verklig.

Resync återaktiverade en uppsagd användare

Resync är en knapp i Ascendra som manuellt tvingar fram en ny synk mot Entra ID, till exempel om en tidigare synk misslyckats. Den läser inte något nytt från HR-systemet, utan tar det Ascendra redan vet om identiteten och skickar det till Entra igen via Graph.

Den allvarligaste buggen: koden bakom Resync skickade alltid accountEnabled = true till Graph, oavsett om personen fortfarande var anställd. Konkret hände det här: en anställd sades upp, kontot inaktiverades korrekt i Entra. Jag klickade Resync igen på samma person, bara för att testa, och kontot återaktiverades i Entra, trots att HR-systemet fortfarande visade personen som uppsagd.

Lösningen blev att alltid utgå från HR-systemets aktuella anställningsstatus när accountEnabled sätts, oavsett vilken funktion som gör Graph-anropet. Det var ett fel som hade kunnat ge en tidigare anställd åtkomst igen, bara genom en enda extra knapptryckning.

UPN och e-post är inte samma sak

Min tenant hade endast sin standarddomän verifierad. Lösningen blev att använda tenantens verifierade domän för userPrincipalName, och behålla användarens riktiga e-postadress i mail. Det är också hur många företag arbetar i praktiken.

Övriga problem

  • Test Connection gav ingen feedback i gränssnittet
  • SSO-sidan var tom eftersom tabellen aldrig fylldes med testdata
  • Misslyckade Entra-synkar försvann ur livscykelvyn i stället för att visas som fel

Reflektion

Det här projektet lärde mig framför allt en sak. Så länge en integration är simulerad är det lätt att tro att allt fungerar. Först när den kopplas mot ett riktigt system dyker de verkliga problemen upp. Det var också då projektet började kännas som ett riktigt IAM-system i stället för en demo.

Teknisk anteckning

AI användes för att bygga stora delar av kodimplementationen. Mitt fokus låg på riktningen och IAM-koncepten bakom lösningen: att separera HR och IGA, koppla ett IGA-system mot en riktig Entra ID-tenant, sätta upp app-registrering, permissions och secret i Entra ID, samt att testa flödet tillräckligt hårt för att hitta buggar som Resync-problemet ovan.