Beveilig cloudgebaseerde applicaties met OAuth 2.0 en OpenID Connect
Een complete gids voor het beveiligen van je cloudapplicaties met OAuth 2.0 en OpenID Connect en hoe je een geweldige gebruikerservaring biedt met authenticatie en autorisatie.
Introductie
Cloudgebaseerde applicaties zijn tegenwoordig de trend. Hoewel het type applicatie varieert (web, mobiel, desktop, enz.), hebben ze allemaal een cloudbackend die diensten zoals opslag, computing en databases levert. Meestal moeten deze applicaties gebruikers authentiseren en autoriseren om toegang te krijgen tot bepaalde bronnen.
Hoewel zelfgemaakte authenticatie- en autorisatiemechanismen mogelijk zijn, is beveiliging een van de grootste zorgen geworden bij het ontwikkelen van cloudapplicaties. Gelukkig heeft onze industrie beproefde standaarden zoals OAuth 2.0 en OpenID Connect om ons te helpen bij het implementeren van veilige authenticatie en autorisatie.
Dit bericht gaat uit van de volgende aannames:
- Je hebt een basisbegrip van applicatieontwikkeling (web, mobiel of enig ander type).
- Je hebt gehoord over de concepten van authenticatie en autorisatie.
- Je hebt gehoord over OAuth 2.0 en OpenID Connect.
Ja, "gehoord" is voldoende voor zowel 2 als 3. Dit bericht zal echte voorbeelden gebruiken om concepten uit te leggen en het proces te illustreren met diagrammen. Laten we beginnen!
OAuth 2.0 versus OpenID Connect
Als je vertrouwd bent met OAuth 2.0 en OpenID Connect, kun je ook verder lezen omdat we enkele praktijkvoorbeelden in deze sectie zullen behandelen; als je nieuw bent met deze standaarden, is het ook veilig om verder te gaan, want we zullen ze op een eenvoudige manier introduceren.
OAuth 2.0
OAuth 2.0 is een autorisatieframework dat een applicatie in staat stelt beperkte toegang te verkrijgen tot beschermde bronnen van een andere applicatie namens een gebruiker of de applicatie zelf. De meeste populaire diensten zoals Google, Facebook en GitHub gebruiken OAuth 2.0 voor sociale login (bijv. "Aanmelden met Google").
Bijvoorbeeld, je hebt een webapplicatie MyApp die toegang wil krijgen tot de Google Drive van de gebruiker. In plaats van de gebruiker te vragen hun Google Drive-inloggegevens te delen, kan MyApp OAuth 2.0 gebruiken om toegang tot Google Drive aan te vragen namens de gebruiker. Hier is een vereenvoudigde flow:
In deze flow ziet MyApp nooit de Google Drive-inloggegevens van de gebruiker. In plaats daarvan ontvangt het een toegangstoken van Google waarmee het toegang krijgt tot Google Drive namens de gebruiker.
In OAuth 2.0-termen is MyApp de client, is Google zowel de autorisatieserver als de bronserver voor eenvoudigheid. In de echte wereld hebben we vaak gescheiden autorisatie- en bronservers om een Single Sign-On (SSO) ervaring te bieden. Bijvoorbeeld, Google is de autorisatieserver en kan meerdere bronservers hebben zoals Google Drive, Gmail en YouTube.
Merk op dat de daadwerkelijke autorisatiestroom complexer is dan dit. OAuth 2.0 heeft verschillende toestaan verleentypes, domeinen en andere concepten waarmee je bekend moet zijn. Laten we dat even terzijde leggen en verder gaan naar OpenID Connect.
OpenID Connect (OIDC)
OAuth 2.0 is geweldig voor autorisatie, maar je merkt misschien dat het geen manier heeft om de gebruiker te identificeren (d.w.z. authenticatie). OpenID Connect is een identiteitslaag bovenop OAuth 2.0 die authenticatiefuncties toevoegt.
In het bovenstaande voorbeeld moet MyApp weten wie de gebruiker is voordat de autorisatiestroom wordt gestart. Let op dat er hier twee gebruikers betrokken zijn: de gebruiker van MyApp en de gebruiker van Google Drive. In dit geval moet MyApp de gebruiker van zijn eigen applicatie kennen.
Laten we een eenvoudig voorbeeld bekijken, ervan uitgaande dat gebruikers zich kunnen aanmelden bij MyApp met gebruikersnaam en wachtwoord:
Aangezien we de gebruiker van onze eigen applicatie authenticeren, is het meestal niet nodig om toestemming te vragen zoals Google deed in de OAuth 2.0-flow. Ondertussen hebben we iets nodig dat de gebruiker kan identificeren. OpenID Connect introduceert de concepten zoals ID-token en gebruikersinformatie-uiteinde om ons daarbij te helpen.
Je merkt misschien dat de identity provider (IdP) een nieuwe zelfstandige deelnemer in de flow is. Het is hetzelfde als de autorisatieserver in OAuth 2.0, maar voor meer duidelijkheid gebruiken we de term IdP om aan te geven dat het verantwoordelijk is voor gebruikersauthenticatie en identiteitsbeheer.
Wanneer je bedrijf groeit, kun je meerdere applicaties hebben die dezelfde gebruikersdatabase delen. Net als OAuth 2.0, stelt OpenID Connect je in staat om een enkele autorisatieserver te hebben die gebruikers voor meerdere applicaties kan authentiseren. Als de gebruiker al is aangemeld bij één applicatie, hoeft de gebruiker zijn inloggegevens niet opnieuw in te voeren wanneer een andere applicatie ze omleidt naar de IdP. De stroom kan automatisch worden uitgevoerd zonder gebruikersinteractie. Dit wordt Single Sign-On (SSO) genoemd.
Nogmaals, dit is een zeer vereenvoudigde flow en er zijn meer details onder de motorkap. Laten we nu verder gaan met de volgende sectie om informatieoverbelasting te voorkomen.
Applicatietypen
In de vorige sectie hebben we webapplicaties als voorbeelden gebruikt terwijl de wereld diverser is dan dat. Voor een identity provider maakt de exacte programmeertaal, framework of platform dat je gebruikt niet echt uit. In de praktijk is één opmerkelijk verschil of de applicatie een openbare client of een privé (vertrouwde) client is:
- Openbare client: Een client die zijn inloggegevens niet vertrouwelijk kan houden, wat betekent dat de resource-eigenaar (gebruiker) er toegang toe heeft. Bijvoorbeeld, een webapplicatie die in een browser draait (bijv. enkelvoudige pagina-applicatie).
- Privé client: Een client die in staat is zijn inloggegevens vertrouwelijk te houden zonder deze bloot te stellen aan (resource-eigenaren) gebruikers. Bijvoorbeeld, een webapplicatie die op een server draait (bijv. server-side webapplicatie) of een API-service.
Met deze in gedachten, laten we eens kijken hoe OAuth 2.0 en OpenID Connect in verschillende applicatietypen kunnen worden gebruikt.
"Applicatie" en "client" kunnen in de context van dit bericht door elkaar worden gebruikt.
Webapplicaties die op een server draaien
De applicatie draait op een server en levert HTML-pagina's aan gebruikers. Veel populaire webframeworks zoals Express.js, Django en Ruby on Rails vallen in deze categorie; en backend-voor-front-end (BFF) frameworks zoals Next.js en Nuxt.js zijn ook inbegrepen. Deze applicaties hebben de volgende kenmerken:
- Omdat een server alleen privétoegang toestaat (er is geen manier voor het publiek om de code of inloggegevens van de server te zien), wordt het beschouwd als een privé client.
- De algemene gebruikersauthenticatiestroom is dezelfde als die we hebben besproken in de sectie "OpenID Connect".
- De applicatie kan het door de identity provider (d.w.z. de OpenID Connect provider) uitgegeven ID-token gebruiken om de gebruiker te identificeren en gebruikersspecifieke inhoud weer te geven.
- Om de applicatie veilig te houden, gebruikt de applicatie meestal de autorisatiecode-flow voor gebruikersauthenticatie en om tokens te verkrijgen.
Ondertussen moet de applicatie mogelijk toegang hebben tot andere interne API-services in een microservicesarchitectuur; of het is een monolithische applicatie die toegangscontrole nodig heeft voor verschillende delen van de applicatie. We bespreken dit in de sectie "Bescherm je API".
Enkelvoudige pagina-applicaties (SPAs)
De applicatie draait in de browser van de gebruiker en communiceert met de server via API's. React, Angular en Vue.js zijn populaire frameworks voor het bouwen van SPAs. Deze applicaties hebben de volgende kenmerken:
- Aangezien de code van de applicatie zichtbaar is voor het publiek, wordt het beschouwd als een openbare client.
- De algemene gebruikersauthenticatiestroom is dezelfde als die we hebben besproken in de sectie "OpenID Connect".
- De applicatie kan het door de identity provider (d.w.z. de OpenID Connect provider) uitgegeven ID-token gebruiken om de gebruiker te identificeren en gebruikersspecifieke inhoud weer te geven.
- Om de applicatie veilig te houden, gebruikt de applicatie meestal de autorisatiecode-flow met PKCE (Proof Key for Code Exchange) voor gebruikersauthenticatie en om tokens te verkrijgen.
Gewoonlijk moeten SPAs toegang krijgen tot andere API-services voor gegevensophaling en -bijwerking. We bespreken dit in de sectie "Bescherm je API".
Mobiele applicaties
De applicatie draait op een mobiel apparaat (iOS, Android, enz.) en communiceert met de server via API's. In de meeste gevallen hebben deze applicaties dezelfde kenmerken als SPAs.
Machine-to-machine (M2M) applicaties
Machine-to-machine applicaties zijn clients die op een server (machine) draaien en communiceren met andere servers. Deze applicaties hebben de volgende kenmerken:
- Net als webapplicaties die op een server draaien, zijn M2M-applicaties privé clients.
- De applicatie hoeft mogelijk niet de gebruiker te identificeren; in plaats daarvan moet het zichzelf authentiseren om toegang te krijgen tot andere diensten.
- De applicatie kan het toegangstoken uitgegeven door de identity provider (d.w.z. de OAuth 2.0 provider) gebruiken om toegang te krijgen tot andere diensten.
- Om de applicatie veilig te houden, gebruikt de applicatie meestal de client-inloggegevensflow om toegangstokens te verkrijgen.
Bij toegang tot andere diensten moet de applicatie mogelijk het toegangstoken in de aanvraagheader meegeven. We bespreken dit in de sectie "Bescherm je API".
Applicaties die op IoT-apparaten draaien
De applicatie draait op een IoT-apparaat (bijv. slimme thuisapparaten, wearables, enz.) en communiceert met de server via API's. Deze applicaties hebben de volgende kenmerken:
- Afhankelijk van de capaciteit van het apparaat kan het een openbare of privé client zijn.
- De algemene authenticatiestroom kan afwijken van de stroom die we hebben besproken in de sectie "OpenID Connect" naargelang de capaciteit van het apparaat. Sommige apparaten hebben bijvoorbeeld misschien geen scherm voor gebruikers om hun inloggegevens in te voeren.
- Als het apparaat de gebruiker niet hoeft te identificeren, hoeft het mogelijk geen ID-tokens of gebruikersinformatie-uiteindes te gebruiken; in plaats daarvan kan het worden behandeld als een machine-to-machine (M2M) applicatie.
- Om de applicatie veilig te houden, kan de applicatie de autorisatiecodeflow met PKCE (Proof Key for Code Exchange) voor gebruikersauthenticatie en verkrijgen van tokens of de client-inloggegevensflow gebruiken om toegangstokens te verkrijgen.
Bij communicatie met de server moet het apparaat mogelijk het toegangstoken in de aanvraagheader meegeven. We bespreken dit in de sectie "Bescherm je API".
Bescherm je API
Met OpenID Connect is het mogelijk de gebruiker te identificeren en gebruikersspecifieke gegevens op te halen via ID-tokens of gebruikersinformatie-uiteindes. Dit proces wordt authenticatie genoemd. Maar je wilt waarschijnlijk niet al je middelen blootstellen aan alle geauthentiseerde gebruikers, bijvoorbeeld dat alleen beheerders toegang hebben tot de gebruikersbeheerspagina.
Dit is waar autorisatie om de hoek komt kijken. Vergeet niet dat OAuth 2.0 een autorisatieframework is en OpenID Connect een identiteitslaag bovenop OAuth 2.0; wat betekent dat je ook OAuth 2.0 kunt gebruiken wanneer je OpenID Connect al hebt geïmplementeerd.
Laten we het voorbeeld uit de sectie "OAuth 2.0" onthouden: MyApp wil toegang krijgen tot de Google Drive van de gebruiker. Het is niet praktisch om MyApp toegang te geven tot alle bestanden van de gebruiker in Google Drive. In plaats daarvan moet MyApp expliciet vermelden wat het wil benaderen (bijv. alleen-lezen toegang tot bestanden in een specifieke map). In OAuth 2.0-termen wordt dit een domein (scope) genoemd.
Je kunt de term "toestemming" zien die door elkaar wordt gebruikt met "domein" in de context van OAuth 2.0 omdat "domein" soms dubbelzinnig is voor niet-technische gebruikers.
Wanneer de gebruiker toegang verleent aan MyApp, geeft de autorisatieserver een toegangstoken uit met het aangevraagde domein. Het toegangstoken wordt vervolgens naar de bronserver (Google Drive) gestuurd om toegang te krijgen tot de bestanden van de gebruiker.
Uiteraard kunnen we Google Drive vervangen door onze eigen API-services. Bijvoorbeeld, MyApp moet toegang krijgen tot de OrderService om de bestelgeschiedenis van de gebruiker op te halen. Deze keer, aangezien gebruikersauthenticatie al is uitgevoerd door de identity provider en zowel MyApp als OrderService onder onze controle staan, kunnen we overslaan om de gebruiker om toegang te laten verlenen; MyApp kan direct het verzoek naar OrderService sturen met het door de identity provider uitgegeven toegangstoken.
Het toegangstoken kan een lees:order
domein bevatten om aan te geven dat de gebruiker zijn bestelgeschiedenis kan lezen.
Stel dat de gebruiker per ongeluk een URL van een beheerderspagina in de browser invoert. Aangezien de gebruiker geen beheerder is, is er geen admin
domein in het toegangstoken. OrderService zal het verzoek afwijzen en een foutmelding retourneren.
In dit geval kan OrderService een 403 Forbidden-statuscode retourneren om aan te geven dat de gebruiker geen toestemming heeft om toegang te krijgen tot de beheerderspagina.
Voor machine-to-machine (M2M) applicaties is er geen gebruiker betrokken bij het proces. Applicaties kunnen rechtstreeks toegangstokens aanvragen bij de identity provider en deze gebruiken om toegang te krijgen tot andere diensten. Hetzelfde concept geldt: het toegangstoken bevat de noodzakelijke domeinen om toegang te krijgen tot de bronnen.
Autorisatiedesign
We kunnen zien dat er twee belangrijke zaken zijn om te overwegen bij het ontwerpen van autorisatie om je API-diensten te beschermen:
- Domeinen: Definiëren wat de client kan benaderen. Domeinen kunnen fijnmazig zijn (bijv.
lees:order
,schrijf:order
) of meer algemeen (bijv.order
) afhankelijk van je vereisten. - Toegangscontrole: Definiëren wie specifieke domeinen kan hebben. Bijvoorbeeld, alleen beheerders kunnen het
admin
domein hebben.
Wat betreft toegangscontrole, een aantal populaire benaderingen zijn:
- Rolgebaseerde toegangscontrole (RBAC): Rollen aan gebruikers toewijzen en definieer welke rollen toegang tot welke bronnen kunnen krijgen. Bijvoorbeeld, een beheerdersrol kan toegang tot de beheerderspagina krijgen.
- Attribuutgebaseerde toegangscontrole (ABAC): Beleidsregels definiëren op basis van attributen (bijv. afdeling van de gebruiker, locatie, enz.) en toegangsbeslissingen nemen op basis van deze attributen. Bijvoorbeeld, een gebruiker van de afdeling "Engineering" kan toegang krijgen tot de engineeringspagina.
Het is vermeldenswaard dat voor beide benaderingen de standaardmanier voor het verifiëren van toegangscontrole is om de domeinen van het toegangstoken te controleren, in plaats van rollen of attributen. Rollen en attributen kunnen zeer dynamisch zijn en domeinen zijn statischer, waardoor het veel gemakkelijker te beheren is.
Voor gedetailleerde informatie over toegangscontrole kun je verwijzen naar RBAC en ABAC: De toegangscontrolemodellen die je moet kennen.
Toegangstokens
Hoewel we de term "toegangstoken" vaak hebben genoemd, hebben we nog niet besproken hoe je er een verkrijgt. In OAuth 2.0 wordt een toegangstoken uitgegeven door de autorisatieserver (identity provider) na een succesvolle autorisatiestroom.
Laten we eens nauwkeuriger kijken naar het Google Drive-voorbeeld en aannemen dat we de autorisatiecode-flow gebruiken:
Er zijn enkele belangrijke stappen in de flow voor het uitgeven van toegangstokens:
- Stap 2 (Doorverwijzen naar Google): MyApp verwijst de gebruiker naar Google met een autorisatieverzoek. Meestal bevat dit verzoek de volgende informatie:
- Welke client (MyApp) het verzoek initieert
- Welke domeinen MyApp aanvraagt
- Waar Google de gebruiker naartoe moet verwijzen nadat de autorisatie is voltooid
- Stap 5 (Vragen om toestemming om toegang te krijgen tot Google Drive): Google vraagt de gebruiker om toegang te verlenen aan MyApp. De gebruiker kan ervoor kiezen om toegang te verlenen of te weigeren. Deze stap wordt toestemming (consent) genoemd.
- Stap 7 (Doorverwijzen naar MyApp met autorisatiedata): Deze stap is nieuw geïntroduceerd in het diagram. In plaats van het toegangstoken direct te retourneren, geeft Google eenmalige autorisatiecode aan MyApp voor een veiligere uitwisseling. Deze code wordt gebruikt om het toegangstoken te verkrijgen.
- Stap 8 (Gebruik autorisatiecode om te ruilen voor toegangstoken): Dit is ook een nieuwe stap. MyApp verzendt de autorisatiecode naar Google om deze om te ruilen voor een toegangstoken. Als identity provider zal Google de context van het verzoek samenstellen en beslissen of het een toegangstoken uitgeeft:
- De client (MyApp) is wie hij beweert te zijn
- De gebruiker heeft toegang verleend aan de client
- De gebruiker is wie hij beweert te zijn
- De gebruiker heeft de benodigde domeinen
- De autorisatiecode is geldig en niet verlopen
Het bovenstaande voorbeeld gaat ervan uit dat de autorisatieserver (identity provider) en de bronserver hetzelfde zijn (Google). Als ze gescheiden zijn, het voorbeeld van MyApp en OrderService nemend, zal de stroom er als volgt uitzien:
In deze flow geeft de autorisatieserver (IdP) zowel een ID-token als een toegangstoken aan MyApp uit (stap 8). Het ID-token wordt gebruikt om de gebruiker te identificeren, en het toegangstoken wordt gebruikt om toegang te krijgen tot andere diensten zoals OrderService. Aangezien zowel MyApp als OrderService eerstelijnsservices zijn, vragen ze de gebruiker meestal niet om toegang te verlenen; in plaats daarvan vertrouwen ze op de toegangscontrole in de identity provider om te bepalen of de gebruiker toegang kan krijgen tot de bronnen (d.w.z. of het toegangstoken de noodzakelijke domeinen bevat).
Ten slotte, laten we eens kijken hoe het toegangstoken wordt gebruikt in machine-to-machine (M2M) applicaties. Aangezien er geen gebruiker betrokken is bij het proces en de applicatie vertrouwd is, kan het direct een toegangstoken aanvragen bij de identity provider:
Toegangscontrole kan nog steeds worden toegepast. Voor OrderService maakt het niet uit wie de gebruiker is of welke applicatie de gegevens opvraagt; het is alleen geïnteresseerd in het toegangstoken en de domeinen die het bevat.
Toegangstokens worden meestal gecodeerd als JSON Web Tokens (JWT). Om meer te leren over JWT, kun je verwijzen naar Wat is JSON Web Token (JWT)?.
Bronindicatoren
OAuth 2.0 introduceert het concept van domeinen voor toegangscontrole. Echter, je realiseert je misschien snel dat domeinen niet voldoende zijn:
- OpenID Connect definieert een set standaarddomeinen zoals
openid
,offline_access
enprofile
. Het kan verwarrend zijn om deze standaarddomeinen te mengen met je aangepaste domeinen. - Je kunt meerdere API-services hebben die dezelfde domeinnaam delen maar verschillende betekenissen hebben.
Een veel voorkomende oplossing is om achtervoegsels (of voorvoegsels) toe te voegen aan de domeinnamen om de bron (API-service) aan te geven. Bijvoorbeeld, lees:order
en lees:product
zijn duidelijker dan lees
en lees
. Een OAuth 2.0-extensie RFC 8707 introduceert een nieuwe parameter resource
om de bronserver aan te geven die de client wil benaderen.
In werkelijkheid worden API-services meestal gedefinieerd door URL's, dus het is natuurlijker om URL's te gebruiken als bronindicatoren. Bijvoorbeeld, de OrderService API kan worden weergegeven als:
Zoals je kunt zien, biedt de parameter het gemak om de daadwerkelijke bron-URL's in de autorisatieverzoeken en toegangstokens te gebruiken. Het is vermeldenswaard dat RFC 8707 mogelijk niet door alle identity providers is geïmplementeerd. Je moet de documentatie van je identity provider controleren om te zien of deze de resource
-parameter ondersteunt (Logto ondersteunt het).
Kort gezegd, kan de resource
-parameter worden gebruikt in autorisatieverzoeken en toegangstokens om de bron aan te geven die de client wil benaderen.
Er is geen beperking op de toegankelijkheid van de bronindicatoren, d.w.z. de bronindicator hoeft geen echte URL te zijn die verwijst naar een API-service. Daarom weerspiegelt de naam "bronindicator" zijn rol in het autorisatieproces adequaat. Je kunt virtuele URL's gebruiken om bronnen te vertegenwoordigen die je wilt beschermen. Bijvoorbeeld, je kunt een virtuele URL
https://api.example.com/admin
definiëren in je monolithische applicatie om de bron te vertegenwoordigen die alleen beheerders kunnen benaderen.
Breng alles samen
Op dit punt hebben we de basisprincipes van OAuth 2.0 en OpenID Connect behandeld, en hoe je ze kunt gebruiken in verschillende applicatietypen en scenario's. Hoewel we ze afzonderlijk hebben besproken, kun je ze combineren volgens je zakelijke vereisten. De algemene architectuur kan zo eenvoudig zijn als dit:
Of iets complexer:
Naarmate je applicatie groeit, zul je zien dat de identity provider (IdP) een cruciale rol speelt in de architectuur; maar het heeft geen directe relatie met je zakelijke doelen. Hoewel het een goed idee is om het over te dragen aan een betrouwbare leverancier, moeten we de identity provider verstandig kiezen. Een goede identity provider kan het proces aanzienlijk vereenvoudigen, de ontwikkelingsinspanning verminderen en je behoeden voor potentiële beveiligingsvalkuilen.
Slotopmerkingen
Voor moderne cloudapplicaties is de identity provider (of autorisatieserver) de centrale plek voor gebruikersauthenticatie, identiteitsbeheer en toegangscontrole. Hoewel we veel concepten in deze post hebben besproken, zijn er nog veel nuances om te overwegen bij het implementeren van zo'n systeem. Als je geïnteresseerd bent in meer leren, kun je onze blog doorbladeren voor meer diepgaande artikelen.