Skip to main content

Autorisatie, permissions en server-side contextcontrole

5.1 Doel van dit hoofdstuk

Dit hoofdstuk beschrijft hoe OefenHub autorisatie technisch afdwingt na succesvolle authenticatie en interne accountinitialisatie. Vanaf Feature 11-5 is de primaire autorisatiebeslissing permission-based RBAC: rollen groeperen permissions, maar controles op routes, schermen, menu-items, frontpageblokken, services en commands vragen om expliciete permission-codes.

Autorisatie is in OefenHub geen frontendmechanisme. Zichtbare knoppen, menu-items, frontpageblokken, routeparameters, browserstate, querystringwaarden of eerder geladen schermcontexten zijn nooit voldoende om toegang te verlenen. Iedere raadpleeg-, wijzig-, export-, live-, beheer- en backgroundactie moet server-side opnieuw worden gecontroleerd tegen de actuele account-, permission-, relatie-, feature- en objectcontext.

Dit hoofdstuk vult hoofdstuk 04 aan. Hoofdstuk 04 beschrijft identiteit, authenticatie, provisioning en sessieopbouw. Dit hoofdstuk beschrijft wat er daarna gebeurt: welke permissions effectief zijn, welke applicatiecontext geldig is en of de gebruiker een specifieke actie op een specifiek object mag uitvoeren.

5.2 Scope en afbakening

OnderwerpIn dit hoofdstuk
Authenticatie bij Keycloak of externe identity providerNee, zie hoofdstuk 04
Interne accountstatus en provisioningAlleen als input voor autorisatie
Rollen als bundels voor permissionsJa
Permissions en RolePermissionsJa
Permissioncache en invalidatieJa
Declaratieve permission metadata en guardsJa
ASP.NET Core policiesAlleen optioneel implementatiedetail; niet het functionele autorisatiemodel
Object- en relatieautorisatieJa, als domein-specifieke tweede laag
Queryscoping en IDOR-preventieJa
Frontend-zichtbaarheid van actiesAlleen als afgeleide van server-side permissionchecks
Databaseconstraints en soft linksAlleen waar relevant voor autorisatie
Logging van geweigerde toegangJa, in samenhang met hoofdstuk 19
Beheerinterface voor rollen/permissiesNee; placeholder Feature 17 story 091

5.3 Belangrijke inputbronnen

5.4 Ontwerpprincipes

PrincipeRegel
Permission-firstRoutes, pages, components, menu-items, frontpageblokken en commands controleren tegen expliciete permission-codes.
Rollen zijn bundelsRollen geven rechten alleen via RolePermissions; directe rolchecks voor autorisatie zijn niet toegestaan.
Server-side autorisatieIedere actie wordt server-side gecontroleerd. UI-state is nooit autoriserend.
Least privilegeEen gebruiker krijgt alleen toegang tot de minimale actie en dataset die op dat moment nodig is.
Context is explicietAutorisatie gebruikt expliciete permission-, relatie-, object- en actiecontext.
Objecttoegang boven permission alleenEen permission geeft nooit automatisch toegang tot alle objecten van dat type.
Routes zijn onbetrouwbaarRouteparameters en querystrings worden beschouwd als gebruikersinput.
Queryscoping vóór uitlezenData wordt bij voorkeur al in de query beperkt tot de toegestane dataset.
Geen gedeeltelijke datalekkenBij weigering wordt geen naam, antwoorddata, payload, runinhoud of technisch detail gelekt.
Hercontrole per actieDetail, filter, export, live-start en mutatie herhalen autorisatie.
Modulegrenzen blijven intactAutorisatie gebruikt publieke modulecontracts, niet elkaars interne DbContexts of entities.
Loggen zonder inhoudslekGeweigerde toegang wordt herleidbaar gelogd zonder gevoelige payloads.

5.5 Eigenaarschap en projectafbakening

Autorisatie wordt verdeeld over meerdere technische onderdelen, maar de centrale rollen-, permission-, cache- en contextlogica hoort bij OefenHub.Authorization.

ProjectVerantwoordelijkheid
OefenHub.WebRoute guards, permission metadata, UI-compositie, zichtbare acties en doorgeven van gebruikersacties aan modulecontracts. Geen autorisatiebron.
OefenHub.IdentityInterne accountstatus, account lifecycle, provisioningstatus en accountidentiteit als input voor autorisatie.
OefenHub.AuthorizationRollen, UserRoles, Permissions, RolePermissions, permissioncache, permissionchecks, cache-invalidatie en autorisatiecontracts.
DomeinmodulesObjectspecifieke toegang binnen het eigen domein via publieke contracts.
OefenHub.SchedulingAutorisatie van jobuitvoering en technische jobcontext, niet de businessregels van domeinacties.
OefenHub.InfrastructureTechnische integraties en middlewareondersteuning waar nodig.

OefenHub.Authorization gebruikt schema authorization. Verwijzingen naar gebruikers uit identity worden als cross-module soft links behandeld, tenzij dit via een expliciet Technisch Ontwerp-besluit anders wordt vastgelegd.

5.6 Projectstructuur van OefenHub.Authorization

OefenHub.Authorization/
Contracts/
IOefenHubPermissionService.cs
IOefenHubPermissionCache.cs
IOefenHubPermissionCacheInvalidator.cs
IOefenHubAuthorizationDecisionService.cs
IPermissionMetadataRegistry.cs
Models/
Enums/

Data/
AuthorizationDbContext.cs
Entities/
Role.cs
UserRole.cs
Permission.cs
RolePermission.cs
Configurations/
Migrations/

Models/
Commands/
Queries/
ReadModels/
Enums/

Services/
Interfaces/

Guards/
Metadata/
Events/
Extensions/
MapDoel
ContractsPublieke autorisatie-ingangen voor Web en andere modules.
DataAutorisatie-eigen DbContext, entities, EF-configuraties en migrations.
GuardsPermission guards voor route/page/component-integratie waar deze niet in Web thuishoren.
MetadataDeclaratieve permissionmetadata en startup-/testvalidatie.
ServicesInterne implementatie van permission lookup, cache, invalidatie en beslislogica.
Models/ReadModelsModule-eigen readmodels voor autorisatiebeheer en contextoverzichten.
ExtensionsDependency-injectionregistratie zoals AddOefenHubAuthorization.

Implementatieclasses, entities en DbContext zijn standaard internal, tenzij zij expliciet onderdeel zijn van het publieke contract. Andere modules gebruiken alleen types uit Contracts.

5.7 Autorisatielagen

Autorisatie wordt niet als één enkele controle uitgevoerd. Iedere actie doorloopt meerdere lagen.

LaagVraagVoorbeeld
AuthenticatielaagIs er een geldige geauthenticeerde gebruiker?Providerlogin is geslaagd.
AccountlaagBestaat het interne account en is het actief?Users.IsActive = true.
PermissionlaagHeeft de gebruiker de vereiste permission?student-results.read.assigned.
FeaturelaagIs de functie beschikbaar?Vrienden/delen staat aan voor leerling.
ObjectlaagMag deze gebruiker dit object benaderen?Docent mag alleen eigen niveaucontext zien.
ActielaagMag deze actie op dit object?Bekijken wel, wijzigen niet.
DatasetlaagWelke records mogen worden getoond?Alleen gekoppelde kinderen of eigen leerlingen.
WorkflowlaagMag deze samengestelde actie in deze toestand?Ticket kan niet rechtstreeks vanuit New worden gesloten.

Een actie is pas toegestaan wanneer alle relevante lagen slagen.

5.8 Rollen, permissions en samengestelde UI

Een gebruiker kan meerdere rollen hebben, behalve de rol Leerling. De effectieve permissions worden server-side bepaald als distinct union van alle actieve permissions uit alle actieve rollen.

SituatieRegel
Alleen leerlingStudent-permissionbundel actief; leerlingrol exclusief.
Leerling gecombineerd met andere rollenNiet toegestaan; autorisatiecontext ongeldig.
Beheerder + docent + ouder/voogdAlle gekoppelde permissions zijn effectief; frontpage toont blokken in vaste volgorde Beheerder, Docent, Ouder/voogd.
Docent + ouder/voogdBeide blokken/menu's kunnen zichtbaar zijn; objectscope blijft gescheiden.
Niet-publieke rolAlleen via expliciete beheer-/adminflow toegekend of ingetrokken.
Geen permissionsBeperkte context of veilige blokkade volgens hoofdstuk 04.

Rolcontext is geen UI-keuze die blind vertrouwd wordt. Wanneer de frontend een route of blok gebruikt, wordt deze server-side herleid naar de permission- en domeincontexten van het interne account.

5.9 Permission metadata en guards

OefenHub gebruikt eigen declaratieve permissionmetadata als primaire koppeling tussen ingang en autorisatie.

Voorbeelden:

endpoints.MapPost("/teacher/students/{studentId}/results", ...)
.RequireAuthorization()
.RequirePermission("student-results.read.assigned");
<PermissionView Permission="frontpage.view.teacher">
<TeacherFrontPageBlock />
</PermissionView>
<OefenHubPermissionGuard Permission="practice-results.read.own">
...
</OefenHubPermissionGuard>

Regels:

  • RequireAuthorization() blijft baseline authenticatie afdwingen.
  • RequirePermission(...) of vergelijkbare metadata declareert de OefenHub-permission.
  • ASP.NET Core policies mogen onder water gebruikt worden, maar zijn niet de primaire functionele bron.
  • Iedere gedeclareerde permission-code moet via startup-/testvalidatie bestaan in Permissions.Code of seeddata.
  • Er komt geen grote centrale C#-permissiecatalogus als tweede waarheid.

5.10 Permission service en cache

Permissionchecks lopen via publieke contracts.

public interface IOefenHubPermissionService
{
Task<bool> HasPermissionAsync(
Guid userId,
string permission,
CancellationToken cancellationToken);

Task RequirePermissionAsync(
Guid userId,
string permission,
CancellationToken cancellationToken);
}

public interface IOefenHubPermissionCache
{
Task<IReadOnlySet<string>> GetPermissionsAsync(
Guid userId,
CancellationToken cancellationToken);
}

public interface IOefenHubPermissionCacheInvalidator
{
Task InvalidateUserAsync(Guid userId, CancellationToken cancellationToken);
Task InvalidateUsersInRoleAsync(Guid roleId, CancellationToken cancellationToken);
}

Cachegedrag:

  • Bij login probeert OefenHub de effectieve permissions vooraf op te halen en te cachen.
  • Iedere check kijkt eerst in IMemoryCache.
  • Bij cache miss worden alle actieve permissions uit alle actieve rollen opgehaald.
  • Cachewaarde is een distinct set van permission-codes per gebruiker.
  • Standaard TTL komt uit Authorization:PermissionCacheDurationMinutes en is functioneel 60 minuten.
  • IMemoryCache is voorlopig voldoende; bij multi-instance deployment moet IDistributedCache of gedeelde invalidatie worden toegevoegd.

Cache-invalidatie is verplicht bij:

  • UserRoles toevoegen/intrekken/deactiveren;
  • publieke rolkeuze of publieke rolwijziging;
  • account deactiveren, heractiveren of anonimiseren;
  • RolePermissions toevoegen/intrekken/deactiveren;
  • Permissions deactiveren;
  • expliciete systeem- of beheeractie om usercache te legen.

5.11 Resource-based authorization en domeinchecks

Permissions zijn onvoldoende om objecttoegang te bepalen. Daarom gebruikt OefenHub na de permissioncheck domein-specifieke objectchecks voor objecten zoals kinderen, leerlingen, niveaus, oefeningen, runs, berichten, tickets en live-sessies.

Conceptueel patroon:

await permissionService.RequirePermissionAsync(
currentUser.UserId,
"student-results.read.assigned",
cancellationToken);

var decision = await teacherStudentAccessService.AuthorizeResultReadAsync(
teacherUserId: currentUser.UserId,
studentUserId: requestedStudentId,
runId: requestedRunId,
cancellationToken);

if (!decision.Allowed)
{
return AccessDeniedResult.From(decision);
}

De domeinservice mag voor objectdata alleen publieke contracts gebruiken van de eigenaarmodule. Een autorisatieservice mag bijvoorbeeld niet rechtstreeks PracticeDbContext of RelationshipsDbContext gebruiken wanneer hij buiten die module leeft.

VoorbeeldToegestane technische route
Controleren of een kind actief gekoppeld is aan ouderRelationships contract.
Controleren of een run afgerond is en bij kind hoortPractice contract.
Controleren of docent niveau heeft geautoriseerdCatalog/Authorization of eigenaarcontract volgens implementatiekeuze.
Controleren of ticket van huidige gebruiker isSupport contract.

5.12 Queryscoping en IDOR-preventie

IDOR-risico's ontstaan wanneer een gebruiker een technisch object-id in een route of request kan aanpassen. Daarom gelden de volgende regels.

RegelToelichting
Object-id uit route is alleen inputHet id bewijst geen toegang.
Query's worden gescopedQuery bevat direct gebruiker-, permission-, relatie- of contextfilter.
Geen eerst ophalen en later verbergenBij voorkeur wordt ongeautoriseerde data nooit uit de database geladen.
Geen technische ids tonen waar niet nodigGebruikers zien functionele context, niet GUID's als herkenningsmiddel.
Export en detail hercontrolerenEen eerder getoond overzicht geeft geen blijvende toegang.
Oude bookmarks zijn veiligOude routes tonen geen gedeeltelijke data wanneer toegang is vervallen.

5.13 Autorisatie per domein

DomeinPrimaire autorisatiebron
IdentityEigen accountcontext, accountstatus en ondersteunde beheerpermissions.
AuthorizationRollen, UserRoles, Permissions, RolePermissions, permissioncache en invalidatie.
RelationshipsPermissions voor ingang plus actieve relatie, relatietype, uitnodigingsstatus en actorcontext.
CatalogPermissions voor ingang plus niveau-eigendom, collaboratorstatus, docentcontext en actieve oefeningstatus.
PracticePermissions voor ingang plus leerlingcontext, run-eigenaarschap, niveaucontext, relatie- of docentcontext.
CommunicationPermissions voor ingang plus mailboxparticipant, systeemberichtontvanger, threaddeelnemer, templatebeheer.
SupportPermissions voor ingang plus ticketmelder, beheerdercontext, assignment/behandelcontext.
LiveMonitoringPermissions voor ingang plus actieve docent- of ouder-/voogdrelatie, actieve run en livebeschikbaarheid.
AdminBeheerpermissions, expliciete supportcontext en beheeractie.
ReportingGeautoriseerde historische brondata, exportactie en viewercontext.
SchedulingJobtype, technische jobcontext en domeincontract voor uitvoering.

5.14 Domeinspecifieke autorisatie

Docent-, ouder-/voogd-, leerling-, beheer-, communicatie-, support-, live- en schedulingautorisatie blijven domeinspecifiek. De permissioncheck is de ingang; de domeincheck bepaalt de concrete scope.

Voorbeelden:

FlowPermissionDomeincheck
Leerlingresultaat lezen door leerlingpractice-results.read.ownRun is eigen afgeronde run.
Leerlingresultaat lezen door docentstudent-results.read.assignedDocent-leerlingrelatie + niveauautorisatie + run valt binnen scope.
Leerlingresultaat lezen door ouder/voogdstudent-results.read.childrenActieve GuardianStudent-relatie + run hoort bij kind.
Niveau wijzigen door eigenaarteacher-levels.update.ownActor is eigenaar van niveau.
Niveau wijzigen door collaboratorteacher-levels.update.collaboratingActor is actieve collaborator met bewerkrecht.
Live meekijken door docentlive-monitoring.start.assignedActieve run + docent-leerlingrelatie + niveauautorisatie.
Live meekijken door ouder/voogdlive-monitoring.start.childrenActieve run + actieve GuardianStudent-relatie.
Ticket als beheerder oplossentickets.resolve.allActieve behandelcontext en statusovergang toegestaan.

5.15 Zichtbare acties in Web

Web mag zichtbare acties afleiden uit server-side readmodels en permissionchecks, maar zichtbaarheid is nooit de autorisatie zelf.

UI-situatieTechnische regel
Knop verborgenGebruiksvriendelijkheid, geen beveiligingsmaatregel.
Knop zichtbaarCommand autoriseert opnieuw.
Frontpageblok zichtbaarRoute/detailactie autoriseert opnieuw.
Menu-item zichtbaarPage/endpoint autoriseert opnieuw.
Link uit oud berichtVervolgactie autoriseert opnieuw.
Route rechtstreeks geopendPage composition vraagt geautoriseerd readmodel op.
Browser terug/vooruitOude clientstate mag toegang niet herstellen.
Filterwaarden gewijzigdServer valideert filters tegen toegestane dataset.

5.16 Veilige afwijzing

Bij ontbrekende toegang wordt veilig geweigerd. De respons mag geen gevoelige gegevens of technische details lekken.

SituatieResponsrichting
Niet ingelogdRedirect naar login of beperkte publieke context.
Account niet actiefVeilige accountfout of contactroute.
Geen vereiste permissionGenerieke toegang-geweigerdafhandeling.
Geen objecttoegangGenerieke toegang-geweigerdafhandeling.
Object bestaat niet of niet toegankelijkGeen onderscheid lekken tenzij functioneel veilig.
Oude route/bookmarkGeen gedeeltelijke data; terug naar veilige context.
Export niet toegestaanGeen bestand genereren, generieke fout.
Live niet beschikbaarGeen live-data, veilige beschikbaarheidsmelding.

5.17 Logging van autorisatiebeslissingen

Niet iedere succesvolle permissioncheck hoeft als losse logregel te worden opgeslagen. Geweigerde, verdachte of beheerrelevante beslissingen moeten wel herleidbaar zijn.

Minimale velden bij geweigerde toegang:

VeldDoel
CorrelationIdKoppeling met request, job of workflow.
UserIdInterne gebruiker als soft reference of geanonimiseerde waarde.
PermissionGevraagde permission-code.
ActionGevraagde actie wanneer afwijkend van permission.
ResourceTypeType object, bijvoorbeeld ExerciseRun of Ticket.
ResourceIdHashIndien nodig gehasht of beperkt, niet altijd ruwe identifier.
DecisionCodeGestandaardiseerde reden, zonder inhoudslek.
UtcTimestampHerleidbaarheid.

Verboden in autorisatielogs:

  • wachtwoorden, tokens of providerclaims met gevoelige inhoud;
  • antwoorden of vraagpayloads;
  • volledige privéberichtinhoud;
  • ticketbeschrijvingen of interne discussie-inhoud;
  • kindnamen of runinhoud wanneer toegang ontbreekt;
  • volledige PDF- of exportinhoud.

5.18 Publieke contracts

Voorbeelden van publieke autorisatiecontracts:

public interface IOefenHubPermissionService
{
Task<bool> HasPermissionAsync(Guid userId, string permission, CancellationToken cancellationToken);
Task RequirePermissionAsync(Guid userId, string permission, CancellationToken cancellationToken);
}

public interface IOefenHubPermissionCache
{
Task<IReadOnlySet<string>> GetPermissionsAsync(Guid userId, CancellationToken cancellationToken);
}

public interface IOefenHubPermissionCacheInvalidator
{
Task InvalidateUserAsync(Guid userId, CancellationToken cancellationToken);
Task InvalidateUsersInRoleAsync(Guid roleId, CancellationToken cancellationToken);
}

public interface IOefenHubAuthorizationDecisionService
{
Task<OefenHubAccessDecision> AuthorizeAsync(
OefenHubAuthorizationRequest request,
CancellationToken cancellationToken);
}

De exacte interfacevorm mag tijdens implementatie wijzigen, maar de contractuele bedoeling blijft:

  • permission server-side bepalen;
  • actie en resource expliciet meegeven wanneer domeincontext nodig is;
  • geen module-interne entities lekken;
  • beslisreden technisch vastleggen zonder gevoelige inhoud;
  • Web en domeinmodules alleen publieke contracts laten gebruiken;
  • cache-invalidatie beschikbaar stellen voor rol-/permissionmutaties.

5.19 Tests

Autorisatie krijgt eigen testdekking op meerdere niveaus.

TesttypeVoorbeelden
Unit testsPermissionlookup, cache hit/miss, invalidatie, combinatierollen, student-exclusiviteit.
Module integration testsAuthorizationDbContext, Permissions, RolePermissions, UserRoles, seeddata.
Cross-module integration testsGuardianresultaat, docentgeschiedenis, tickettoegang, live-start.
Web/componenttestsKnoppen/blokken zichtbaar/verborgen op basis van permissions.
Security testsIDOR-pogingen, oude bookmarks, route-id manipulatie, onbekende permissionmetadata.
Architecture testsWeb geen DbContext, modules alleen Contracts, geen interne entities over grens, geen directe rolchecks voor autorisatie.
Regression testsIntrekken rol/relatie/autorisatie blokkeert vervolgacties direct na cache-invalidatie.

Minimale scenario's:

ScenarioVerwachting
Login met meerdere rollenEén gecachete distinct permissionset.
Permissioncache verlopenVolgende check laadt opnieuw uit DB.
UserRole ingetrokkenCache voor gebruiker wordt geleegd en toegang vervalt.
RolePermission later gewijzigdCache van gebruikers in die rol wordt geleegd.
Endpoint declareert onbekende permissionStartup-/testvalidatie faalt.
Ouder verliest relatie en opent oude runlinkGeen resultaatdata.
Docent opent run buiten eigen niveauautorisatieToegang geweigerd.
Beheerder probeert live mee te kijkenNiet toegestaan als reguliere livefunctie.
PDF-export via oude linkOpnieuw autoriseren, anders geen bestand.
SignalR reconnect na ingetrokken toegangLivegroep verlaten of geen updates.

5.20 Implementatiechecklist

Bij iedere nieuwe route, command, query, export of job moet minimaal worden gecontroleerd:

  • welke permission vereist is;
  • of de permission in het RBAC-permissieregister en seeddata bestaat;
  • welk object of welke dataset wordt benaderd;
  • welke module eigenaar is van de domeincheck;
  • welk publiek contract de controle uitvoert;
  • of routeparameters opnieuw worden gevalideerd;
  • of queryscoping vóór uitlezen gebeurt;
  • of oude clientstate/bookmarks veilig falen;
  • of geweigerde toegang geen inhoud lekt;
  • of logging een correlation-id bevat;
  • of cache-invalidatie nodig is bij mutatie;
  • of tests bestaan voor toegestane én geweigerde toegang;
  • of Software Requirements Specification, Functioneel Ontwerp, permissionregister en database-informatie aangepast moeten worden bij nieuwe functionele regels.

5.21 Implementatieverificaties

PuntToelichting
PermissionmetadataExacte helpernamen zoals RequirePermission, RequiresPermission, PermissionView en OefenHubPermissionGuard vastleggen bij implementatie.
AuthorizationDbContext-inhoudPermissions en RolePermissions toevoegen, inclusief constraints, seeddata en migraties.
ClaimsmappingProviderclaims blijven geen bron voor permissions; interne Users.Id is ingang voor permissionlookup.
Resource-id loggingBepalen wanneer ruwe ids, hashes of beperkte referenties gelogd worden.
Featuretoggle-integratieExacte technische koppeling tussen featuretoggles en permissionbeslissingen bepalen.
SignalR authorizationHub- en group-autorisatie opnieuw testen met permissions.
TickerQ technische actorRepresentatie van scheduler/system actor vastleggen.
Access denied storageBepalen of bepaalde securityevents alleen in technische logs of ook persistent worden opgeslagen.
Rollen-/permissionbeheer UIBuiten scope; placeholder Feature 17 story 091.