OAuth 2.0 en OpenID Connect worden vaak samen gebruikt, maar lossen verschillende problemen op: toegang verlenen tot een dienst en vaststellen wie er inlogt. Je leest hoe de rollen, tokens en beveiligingskeuzes werken, en waar het in de praktijk mis kan gaan.
OAuth 2.0 geeft toegang, maar bewijst niet wie iemand is
OAuth 2.0 is een protocol waarmee een gebruiker een applicatie toestemming kan geven om bepaalde gegevens of functies van een andere dienst te gebruiken. Denk aan een agenda-app die afspraken mag lezen, of een webshop die voorraad ophaalt uit een extern systeem. De applicatie krijgt daarvoor een toegangsbewijs: een access token. Dat token vertegenwoordigt toestemming voor een afgebakende handeling, niet automatisch de identiteit van de persoon die toestemming gaf.
Dat onderscheid is belangrijk. Een applicatie kan een access token ontvangen en daarmee een API aanroepen, maar uit de aanwezigheid van dat token volgt niet zonder meer dat de gebruiker is ingelogd. Ook de tekst of structuur van een token vertelt niet vanzelf wat het betekent. De ontvangende API moet weten welke uitgever het token heeft gemaakt, voor welk doel het is bedoeld en welke rechten eraan gekoppeld zijn.
OpenID Connect, vaak afgekort tot OIDC, bouwt voort op OAuth 2.0 en voegt juist een gestandaardiseerde manier toe om gebruikersidentiteit vast te stellen. Het levert onder meer een ID-token met claims over de authenticatie. De praktische vuistregel is daarom: gebruik OAuth 2.0 voor gedelegeerde toegang tot API’s en OIDC voor aanmelden en identiteitsinformatie. Een knop met de tekst Inloggen met een externe aanbieder gebruikt doorgaans OIDC, ook al loopt de onderliggende autorisatie uit technisch oogpunt via OAuth 2.0.
Wie OAuth alleen als inlogprotocol behandelt, loopt het risico een access token als bewijs van identiteit op te slaan. Dat kan leiden tot verkeerde accountkoppelingen of vertrouwen in gegevens die voor een ander doel zijn uitgegeven. Behandel autorisatie en authenticatie dus als afzonderlijke vragen, ook wanneer één integratie beide afhandelt.
De rollen in OAuth 2.0 en OpenID Connect
Een OAuth-integratie bestaat uit rollen die vaak over meerdere organisaties en systemen verdeeld zijn. De resource owner is meestal de gebruiker die toestemming geeft. De client is de applicatie die toegang wil, bijvoorbeeld een mobiele app of een webshop. De authorization server controleert de gebruiker en geeft tokens uit. De resource server is de API die de beschermde gegevens aanbiedt. Eén leverancier kan de authorization server en resource server allebei beheren, maar dat maakt hun functies niet hetzelfde.
Bij aanmelden met OIDC heet de applicatie ook wel de relying party: zij vertrouwt op de identiteitsverklaring van een OpenID Provider. De provider authenticeert de gebruiker en levert een ID-token. De applicatie controleert dat token en gebruikt de daarin toegestane claims om een lokale sessie te maken. De API achter de applicatie kan daarnaast een access token nodig hebben; dat token heeft een ander doel en kan door een andere component worden gecontroleerd.
Maak bij het ontwerp expliciet welke componenten welke rol spelen. Een veelvoorkomende fout is dat een browserapplicatie het access token rechtstreeks naar een willekeurige backend stuurt, terwijl die backend niet de bedoelde resource server is. Een andere fout is een API die tokens accepteert omdat ze door een bekende aanbieder zijn uitgegeven, zonder te controleren of ze voor die API bedoeld zijn.
Beschrijf daarom per token de uitgever, ontvanger, toegestane scopes en validator. Als een website zowel gebruikers laat inloggen als een externe voorraad-API aanroept, zijn dat twee aparte vertrouwensrelaties. De loginprovider verklaart iets over de gebruiker; de voorraad-API controleert of de website toegang tot voorraadgegevens heeft. Door die relaties te scheiden, wordt duidelijk waar toestemming wordt gegeven en waar die toestemming wordt afgedwongen.
Zo verloopt inloggen met de authorization code flow
Voor moderne websites en apps is de authorization code flow de gebruikelijke basis voor zowel OAuth 2.0 als OIDC. De gebruiker begint bij de client en wordt naar de authorization server gestuurd. In dat verzoek staan onder meer de client_id, de exacte redirect URI, de gevraagde rechten en een willekeurige state-waarde. Bij OIDC staat ook de scope openid in het verzoek. Na authenticatie en eventuele toestemming stuurt de provider de browser terug met een tijdelijke authorization code.
De client wisselt die code vervolgens in voor tokens. Bij een serverapplicatie gebeurt dat doorgaans vanaf de backend, waar clientgegevens veilig kunnen blijven. Een publieke client, zoals een mobiele app, kan geen geheim bewaren en gebruikt daarom PKCE. Daarbij wordt vooraf een willekeurige code verifier gemaakt en wordt een afgeleide code challenge meegestuurd. Bij het inwisselen moet de client de verifier bewijzen, zodat een onderschepte code niet zomaar door een andere partij kan worden gebruikt.
De state-waarde koppelt de terugkeer aan het oorspronkelijke verzoek en helpt ongewenste verzoeken te herkennen. In OIDC wordt daarnaast een nonce gebruikt om het ID-token aan de gestarte loginpoging te binden. Deze waarden zijn niet uitwisselbaar: controleer ze elk op de plek en voor het doel waarvoor ze zijn bedoeld.
Gebruik geen verouderde implicit flow waarbij tokens direct in de browser-URL worden teruggestuurd. URL’s kunnen in browsergeschiedenis, logs of verwijzende informatie terechtkomen. De authorization code flow beperkt die blootstelling en maakt een gecontroleerde tokenuitwisseling mogelijk. Een goede implementatie controleert na terugkeer ook foutmeldingen, verlopen codes en afwijkende redirect URI’s, in plaats van de gebruiker automatisch als ingelogd te beschouwen.
ID-tokens en access tokens hebben verschillende doelen
Een ID-token is een OIDC-verklaring voor de client over een authenticatie. Het bevat claims zoals de uitgever, de beoogde client, het tijdstip van uitgifte en een stabiele subject-identificatie. Een access token is bedoeld om een resource server toegang te geven tot een API. Het kan een JWT zijn, maar ook een ondoorzichtig tekenreekstoken dat de API via een apart controlepunt moet laten valideren. Ga er dus niet van uit dat elk access token leesbaar is of dezelfde claims bevat.
Een client die een ID-token ontvangt, moet onder meer de handtekening controleren met sleutels die bij de vertrouwde provider horen. Controleer ook issuer, audience, geldigheidsduur en, waar van toepassing, nonce. Een geldig ondertekend token is niet automatisch geldig voor iedere applicatie: de audience moet overeenkomen met de client waarvoor het token is uitgegeven. Gebruik bij sleutelrotatie de actuele sleutels uit de metadata van de provider en behandel onbekende sleutels niet als reden om de controle over te slaan.
Een API valideert access tokens volgens de afspraken van die API. Bij JWT’s controleert zij onder andere handtekening, issuer, audience, vervaltijd en relevante scopes of rollen. Bij opaque tokens kan introspection nodig zijn, waarbij de API bij de authorization server navraagt of het token nog actief is. De keuze heeft gevolgen voor beschikbaarheid en snelheid: lokale JWT-validatie is efficiënt, terwijl introspection actuele intrekking kan signaleren maar een extra afhankelijkheid introduceert.
Gebruik nooit een ID-token als bearer token voor een API alleen omdat het technisch een JWT is. Het is uitgegeven voor de client, niet noodzakelijk voor de API, en bevat informatie met een ander doel. Houd verwerking gescheiden: ID-token voor de logintransactie, access token voor de bedoelde resource, en lokale sessie voor het bijhouden van de ingelogde browser.
Scopes en toestemming beperken de toegang
Scopes beschrijven welke toegang een client aanvraagt, bijvoorbeeld het lezen van profielgegevens of het beheren van agenda-afspraken. De naam van een scope is geen beveiliging op zichzelf: de authorization server moet bepalen of de client die scope mag aanvragen en de resource server moet controleren of het token de benodigde rechten bevat. Een API die scopes niet afdwingt, maakt de toestemming feitelijk betekenisloos.
Vraag alleen de rechten aan die nodig zijn voor een concrete functie. Een integratie die alleen voorraadstanden leest, hoort geen schrijfrechten voor producten of klantgegevens te krijgen. Dat heet het principe van minimale bevoegdheden. Het beperkt schade als een token uitlekt en maakt een toestemmingsscherm begrijpelijker. Brede scopes lijken soms handig voor toekomstige functies, maar zorgen vaak voor onnodige toegang en kunnen gebruikers terughoudend maken om toestemming te geven.
Scopes zijn niet altijd hetzelfde als de interne autorisatie van een applicatie. Een scope kan aangeven dat een client een API-functie mag aanroepen, terwijl de API daarna nog moet controleren of de betreffende gebruiker toegang heeft tot een specifiek klantnummer of dossier. Vertrouw dus niet op een algemene scope als vervanging voor objectniveau-autorisatie. Controleer zowel wat de client mag doen als welke gegevens de gebruiker zelf mag zien.
Leg vast hoe scopes worden toegekend, ingetrokken en gewijzigd. Sommige providers vragen toestemming van de gebruiker, andere werken met vooraf ingestelde rechten tussen organisaties. In beide gevallen moet duidelijk zijn wie de toestemming beheert en wat er gebeurt als een scope later wordt ingetrokken. Test ook negatieve scenario’s: een token zonder vereiste scope hoort een duidelijke weigering op te leveren, niet een lege response of onbedoelde toegang.
Veilig inloggen met redirect URI, state en nonce
De redirect URI bepaalt waar de provider de browser na authenticatie naartoe stuurt. Registreer daarom per omgeving exacte callbackadressen, zoals een aparte URI voor productie en test. Losse wildcardpatronen zijn riskant: een aanvaller kan proberen een code of token naar een domein te laten sturen dat hij beheert. De client moet bovendien controleren dat de redirect URI in het autorisatieverzoek exact overeenkomt met een vooraf geregistreerde waarde. Gebruik HTTPS buiten lokale ontwikkeling.
State beschermt de koppeling tussen de loginstart en de terugkeer. Maak de waarde onvoorspelbaar, bewaar haar aan de serverkant of in een passende beveiligde browsersessie en accepteer haar maar één keer. Controleer de teruggekomen waarde voordat de authorization code wordt verwerkt. Zonder die controle kan een aanvaller een gebruiker naar een ongewenste logintransactie sturen, bijvoorbeeld met een account van de aanvaller, waarna gegevens in het verkeerde account belanden.
Bij OIDC vervult nonce een andere functie: de client vergelijkt de nonce uit het ID-token met de waarde die voor deze authenticatie is gemaakt. Zo wordt voorkomen dat een eerder verkregen token zonder meer in een nieuwe logintransactie wordt hergebruikt. Controleer nonce samen met de handtekening, audience, issuer en vervaltijd; één losse controle maakt het token niet betrouwbaar.
Let ook op code leakage en logging. Schrijf authorization codes, access tokens en ID-tokens niet naar applicatielogs, analytics of foutmeldingen. Verwijder tijdelijke parameters na verwerking en voorkom dat scripts van derden op callbackpagina’s toegang krijgen tot gevoelige URL-gegevens. Als een provider een fout terugstuurt, toon dan een veilige melding en log alleen voldoende context om het probleem te onderzoeken, zonder geheimen of volledige tokens op te slaan.
Sessies, accountkoppeling en gebruikersidentiteit
Na een geslaagde OIDC-authenticatie maakt een website meestal een eigen sessie. Dat is iets anders dan het ID-token bewaren en bij elke paginalading opnieuw gebruiken. Een server-side sessie kan browsertoegang beperken tot een willekeurige sessiecookie met de attributen Secure, HttpOnly en een passende SameSite-instelling. Stel de sessie-ID opnieuw in na inloggen, zodat een eerder door een aanvaller vastgelegde sessie niet kan worden overgenomen. Beveilig ook wijzigingen van gevoelige accountinstellingen met passende controles.
Koppel een extern account niet uitsluitend op basis van e-mailadres. E-mailadressen kunnen wijzigen, opnieuw worden uitgegeven of door een provider niet als geverifieerd worden gemarkeerd. Een stabielere sleutel is de combinatie van issuer en subject: dezelfde subjectwaarde kan bij verschillende providers voorkomen, dus de issuer hoort erbij. Bewaar ook welke provider de identiteit heeft bevestigd en wanneer, zodat latere wijzigingen gecontroleerd kunnen worden.
Automatisch samenvoegen van accounts op basis van een overeenkomstig e-mailadres is een risicovolle keuze. Als een adres niet aantoonbaar is geverifieerd, kan iemand anders toegang krijgen tot bestaande gegevens. Veiliger is om een gebruiker eerst in te laten loggen op het bestaande account en daarna bewust een externe identiteit te laten koppelen. Bij een al gebruikte identiteit moet de applicatie een conflict afhandelen, niet stilzwijgend de accounts samenvoegen.
Denk na over wat er gebeurt als een gebruiker de toegang bij de provider intrekt of zijn externe account verwijdert. Een bestaande lokale sessie verdwijnt niet noodzakelijk automatisch. Stel daarom eigen regels in voor sessieduur, opnieuw authenticeren bij risicovolle handelingen en het ontkoppelen van accounts. Een geslaagde externe login is een momentopname van authenticatie, geen permanente garantie dat elk toekomstig verzoek veilig is.
Refresh tokens beheren en toegang intrekken
Access tokens hebben doorgaans een beperkte geldigheidsduur. Als een applicatie langdurige toegang nodig heeft, kan zij een refresh token gebruiken om nieuwe access tokens aan te vragen zonder de gebruiker telkens opnieuw te laten inloggen. Een refresh token is gevoeliger dan een kortlevend access token: wie het buitmaakt, kan mogelijk herhaaldelijk nieuwe toegang verkrijgen. Sla het daarom niet op in browser localStorage en neem het niet op in logs of foutmeldingen.
Bij een serverapplicatie hoort een refresh token versleuteld en alleen toegankelijk voor de component die het nodig heeft. Een publieke client kan geen geheim bewaren; volg daar de opslagmogelijkheden van het platform en de richtlijnen van de provider. Mobiele apps gebruiken bijvoorbeeld een beveiligde opslagvoorziening van het besturingssysteem. Een backend-for-frontend kan tokens buiten de browser houden en alleen een beveiligde sessiecookie aan de browser geven, maar voegt wel serverbeheer en sessie-infrastructuur toe.
Ondersteunt de provider rotatie van refresh tokens, gebruik die dan zorgvuldig. Bij rotatie wordt een token na gebruik vervangen; hergebruik van een oude token kan een aanwijzing zijn dat een kopie is buitgemaakt. De applicatie moet gelijktijdige verversingsverzoeken goed afhandelen, anders kunnen twee verzoeken elkaar ongeldig maken en gebruikers onverwacht uitloggen. Maak ook onderscheid tussen intrekken bij de provider en uitloggen in de lokale website: dat zijn afzonderlijke acties.
Kies de geldigheidsduur op basis van het risico en de gebruikerservaring. Langere toegang vermindert opnieuw inloggen, maar vergroot de periode waarin een gestolen token bruikbaar kan zijn. Voor gevoelige gegevens kan opnieuw authenticeren nodig zijn, ook als de lokale sessie nog geldig is. Houd rekening met verlopen of ingetrokken tokens, tijdelijke storingen bij de tokenserver en duidelijke herstelpaden, zodat een mislukte vernieuwing niet leidt tot een eindeloze redirectlus.
OAuth gebruiken voor koppelingen met externe API’s
Bij een koppeling met een externe dienst is de belangrijkste ontwerpvraag niet alleen hoe je een token ophaalt, maar welke partij namens wie handelt. Een webshop kan bijvoorbeeld voorraad ophalen met een token dat bij de webshop hoort, of namens een specifieke beheerder toegang krijgen na diens toestemming. Dat zijn verschillende autorisatiemodellen. Leg vast of toegang gebruikersgebonden of applicatiegebonden is, welke gegevens nodig zijn en wie de koppeling kan beheren of verbreken.
Voor server-naar-serververkeer zonder gebruikerslogin kan een client credentials-achtige flow passend zijn, als de provider die ondersteunt. De applicatie authenticeert zichzelf en krijgt beperkte toegang voor haar eigen identiteit. Gebruik die flow niet als vervanging voor gebruikerslogin: er is geen gebruiker die op dat moment authenticatie uitvoert. Wanneer een medewerker namens zichzelf toestemming geeft, is een interactieve authorization code flow vaak geschikter. Volg altijd de mogelijkheden en beveiligingseisen van de betreffende API.
Behandel providerfouten en netwerkproblemen als normale situaties. Een API kan een token afwijzen, een scope kan ontbreken of een dienst kan tijdelijk onbereikbaar zijn. Vernieuw een verlopen token volgens de protocolregels, maar blijf niet onbeperkt opnieuw proberen na een toegangsweigering. Gebruik begrensde retries met wachttijd voor tijdelijke fouten en maak onderscheid tussen een probleem dat automatisch kan herstellen en een koppeling die opnieuw toestemming vereist.
Beperk ook de gegevens die je na een API-aanroep bewaart. Een geldig token geeft geen reden om alle opgehaalde informatie permanent op te slaan. Bepaal bewaartermijnen, toegangsrechten en procedures voor verwijdering wanneer een koppeling wordt beëindigd. Test met afzonderlijke credentials voor ontwikkeling, test en productie, en controleer in monitoring alleen status, latency en foutcategorieën. Tokens en volledige autorisatieheaders horen nooit in observability-systemen terecht te komen.