OAuth voor beginners | SURF Communities
OAuth: wat is het en hoe zet ik het in?
Steeds vaker krijgt SURFnet de vraag van een instelling hoe je op een veilige manier een backend-datastore kan ontsluiten voor de buitenwereld. Zolang het gaat om één vertrouwde partij die toegang moet krijgen tot een dataset is dit niet zo'n probleem: wissel een gebruikersnaam en wachtwoord uit, en geef die partij met deze credentials over een beveiligde https-verbinding toegang tot de betreffende data.
In de praktijk komen echter ook ingewikkeldere situaties voor. Een typisch voorbeeld hiervan is een gebruiker die zijn Flickr-foto's wil laten afdrukken. Idealiter zou hij dan de fotoafdruk website namens hemzelf toegang willen geven tot zijn Flickr-account, maar dan wel op zo'n manier dat hij die toestemming wel weer kan intrekken en de toegang kan beperken tot een bepaalde folder. Een ander voorbeeld is een mobiele app die persoonlijke roosters van een student wil ophalen uit de roostersystemen van een universiteit; maar dan wel graag zonder die app ook toegang te geven tot de email, tentamencijfers, en vakinschrijvingen van die student.
In zulke complexe situaties, waarbij je al je gebruikers toegang wilt geven tot hun eigen data, met clients die ze zelf schrijven, of die door derden beschikbaar worden gemaakt, ligt het toegangsbeheer dus een stuk lastiger. Er zijn dan meerdere partijen betrokken (de gebruiker, de dataleverancier en de gebruikte applicatie) en feitelijk zou je willen dat de gebruiker een applicatie (een website, een mobiele app, etc.) op een veilige manier namens hemzelf toegang kan geven tot een datastore.
OAuth-protocol
Precies om deze use cases op te lossen is het OAuth-protocol bedacht. Dit protocol, waarvan inmiddels versie 2 vastligt, is een open standaard die binnen de IETF wordt ontwikkeld, en waar grote (cloud)partijen als Microsoft, Google, en Amazon zich aan hebben gecommitteerd. De volledige specificatie is te vinden in RFC 6749. Hieronder zal ik, zonder op de details in te gaan, uitleggen hoe het protocol in grote lijnen in elkaar steekt.
OAuth 2 ondersteunt verschillende profielen. Deze verschillende profielen bedienen verschillende use cases, maar het meest gebruikte profiel, dat van toepassing is op bovenstaande use cases, en waar ik me in deze blog op focus, is het zogenaamde "authorisation code" profiel.
Daarnaast zijn er nog de zogenaamde "implicit grant"-, "client credentials grant"-, en "password credentials grant"-profielen. Dit zijn eenvoudigere, minder gebruikte manieren om te autoriseren, maar die niettemin in sommige specifieke situaties nodig zijn. Een goede uitleg over het onderscheid tussen de verschillende profielen ("grant types") is te vinden in deze blogpost.
Flow van 'authorisation code' profiel
In de context van SURFconext (of gefedereerde identiteiten in het algemeen) ziet de flow van het "authorisation code"-profiel er in grote lijnen als volgt uit:
Onderaan zie je gebruikers die via een app (bijvoorbeeld op een mobiel device) toegang willen tot een datastore of API van hun instelling. In OAuth-termen heet zo'n datastore of API een "Resource Server" (RS). Deze resource server levert (gefilterde) resources uit de backendsystemen van de instelling.
Daarnaast zie je een zogenaamde "Authorization Server" (AS) die alle OAuth-berichten afhandelt. Deze is in dit geval gekoppeld aan een identiteitsfederatie (zoals SURFconext) om de inkomende gebruikers te kunnen authenticeren.
Wat gebeurt er nu als een gebruiker data wil opvragen uit de resource server? Zijn client vraagt dan eerst de Authorization Server om een "authorization code" (1). Voordat de Authorization Server die kan uitgeven, moet hij eerst weten wie de gebruiker is voor wie de code moet worden uitgegeven. Om dat vast te stellen moet de gebruiker zich eerst authenticeren bij SURFconext (2). Na succesvolle authenticatie vraagt de Authorization Server de gebruiker dan om toestemming om de betreffende app toegang te geven tot de gevraagde gegevens. Slechts als de gebruiker deze toestemming verleent, geeft de AS de client een authorization code terug (3). De client kan de authorization code bij de Authorization Server inwisselen voor een "access token", waarmee hij voortaan (zonder al deze stappen opnieuw te doorlopen) toegang krijgt tot de daadwerkelijke data-interface.
Merk op dat het hiermee mogelijk wordt om eenmalig een account te "koppelen" binnen een mobiele app, waarna die mobiele app voortaan in de achtergrond toegang krijgt tot de gevraagde data, zonder dat de gebruiker telkens opnieuw moet inloggen.
Als de client nu daadwerkelijk data wil gaan ophalen, spreekt hij de API van de resource server aan, en geeft daarbij het ontvangen access token mee (4). De resource server controleert vervolgens bij de Authorization Server of het access token geldig is (5), en de Authorization server vertelt hem bovendien bij welke gebruiker dit token hoort, en in welke context (bijv. voor welke interface) het is afgegeven. Op basis daarvan kan de Resource Server beslissen tot welke data de client toegang krijgt in de backend (7), en of hij de gevraagde data terug wil geven aan de client (8).
Kant-en-klare Authorization Server
Zoals je ziet is het vrij veel werk om dit alles netjes en veilig te implementeren, vooral als je je realiseert dat ik hierboven slechts een van de beschikbare flows beschrijf, en er allerlei cornercases zijn waar je rekening mee moet houden. Als je OAuth wilt gebruiken, is het daarom verstandig om een kant-en-klare implementatie te hergebruiken, in plaats van het zelf bouwen van een Authorization Server. Daarover, en specifiek over de door SURFnet ontwikkelde Authorization Server, vertel ik in een volgende blog meer.