You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
18 KiB
349 lines
18 KiB
{% extends "base.html" %} {% block content %}
|
|
<h1>OAuth</h1>
|
|
|
|
<img src="static/img/oauth.png" style="float: right">
|
|
|
|
<p>Heb je wel eens gebruik gemaakt van een API van Twitter, Facebook of Google? Dan heb je waarschijnlijk te maken gehad
|
|
<strong>OAuth</strong>, de technologie die je toestemming verleent om gebruik te maken van die API's.</p>
|
|
|
|
<p>Er zijn veel misverstanden over OAuth waardoor het vaak verkeerd begrepen wordt en er veel frictie ontstaat ontstaat tussen
|
|
de API en de ontwikkelaar. Het is bijvoorbeeld niet ontworpen om enkel mee in te loggen, hoewel het daar wel veel voor
|
|
gebruikt wordt. Deze week gaan we kijken naar het probleem van authoriseren en hoe OAuth dat oplost.</p>
|
|
|
|
<p>We gaan deze week oefenen met OAuth 2.0. Er is ook versie 1.0 en 1.0a die heel anders werken (hoewel ze qua concept wel gelijk
|
|
zijn). Let altijd op met welke versie je te maken hebt, je kan een 2.0 client niet laten praten met een 1.0 server en vice
|
|
versa.
|
|
</p>
|
|
|
|
<p>We nemen als voorbeeld de Poespas bank. Een simpele website waarmee gebruikers kunnen inloggen met een gebruikersnaam en
|
|
wachtwoord. Als ze ingelogd zijn zien ze hun huidige saldo. De bank heeft een simpele API waarmee gebruikers hun saldo
|
|
kunnen checken.</p>
|
|
|
|
<h3>Simpele authenticatie</h3>
|
|
|
|
<p>Een simpele manier om de saldo API te implementeren is om rechtstreeks je gebruikersnaam en wachtwoord mee te geven in de
|
|
API request. De server checkt of de inloggegevens correct zijn en geeft de saldo informatie van die gebruiker terug. Uiteraard
|
|
moet dit alles over een beveiligde verbinding gebeuren, maar als dat zo is is het in theorie een veilige oplossing.</p>
|
|
|
|
<figure>
|
|
<img src="static/img/auth_basic.png">
|
|
</figure>
|
|
|
|
<img src="static/img/http_auth.png" style="float: right; width: 30%; margin-left: 10px">
|
|
|
|
<p>Veel echte API's werkte vroeger op deze manier (vaak op basis van
|
|
<a href="http://en.wikipedia.org/wiki/Basic_access_authentication" target="_blank">HTTP Authentication</a>). En zolang alles over een beveiligde verbinding tussen de gebruiker en de server loopt is alles
|
|
veilig. De problemen beginnen echter als een derde partij betrokken raakt bij onze API.</p>
|
|
|
|
<p>Een startup heeft de
|
|
<i>BudgetApp</i> ontwikkelt, een nieuwe app die laat zien hoeveel saldo je per maand op je rekening hebt staan. Ze maken gebruik
|
|
van de Poespas API om het saldo van de gebruiker op te halen. Maar om die API te kunnen gebruiken hebben ze de gebruikersnaam
|
|
en het wachtwoord van de gebruiker nodig. Op hun inlogscherm vragen ze daarom om de inloggegevens zodat de app goed kan
|
|
werken.
|
|
</p>
|
|
|
|
<figure>
|
|
<img src="static/img/auth_basic_app.png">
|
|
</figure>
|
|
|
|
<p>Het probleem met deze manier van authenticeren wordt nu snel duidelijk: de app krijgt de gebruikersnaam en wachtwoord van
|
|
de gebruiker in handen. Daarmee kunnen ze niet alleen het saldo ophalen met de API. Maar ook alle andere dingen die de
|
|
gebruiker kan zoals bijvoorbeeld inloggen, geld overschrijven en het wachtwoord veranderen.</p>
|
|
|
|
|
|
<p>Om deze situatie te voorkomen willen we een authorisatiesysteem waarbij de app wel een sleutel krijgt om de API te gebruiken,
|
|
maar niet het wachtwoord van de gebruiker zelf. Dat moet strict geheim blijven.</p>
|
|
|
|
<h3>Access tokens</h3>
|
|
|
|
<img src="static/img/access_token.png" class="center-block" style="float: right; width: 25%">
|
|
<p>OAuth lost dit probleem op door het concept van een
|
|
<b>access token</b>. Een lange string waarmee je aangeeft dat een bepaalde gebruiker jou toestemming heeft gegeven om de API
|
|
te mogen gebruiken. Deze token geef je bij elke aanroep van de API mee in een speciale 'Authorization' header, zodat de
|
|
server deze kan verifiëren.</p>
|
|
|
|
<p>De app heeft nu niet meer de gebruikersnaam en wachtwoord van de gebruiker nodig, de access token is voor de bank een veilige
|
|
manier om toegang tot de API te geven. En omdat een access token maar tijdelijk geldig is (vaak maar een uur) blijft de
|
|
schade relatief beperkt als een hacker hier toegang tot krijgt.</p>
|
|
|
|
<img src="static/img/oauth_facebook.png" class="center-block">
|
|
|
|
<p>Access tokens kan je aanvragen door de gebruiker te sturen naar een speciale authorisatie pagina. Je hebt ze vast wel eens
|
|
eerder gezien: de pagina's waar je moet inloggen en op 'Geef toestemming' moet klikken. Zodra je op die knop klikt wordt
|
|
je weer teruggestuurd naar de oorspronkelijke app of webapplicatie met de access token in de URL.</p>
|
|
|
|
<figure>
|
|
<img src="static/img/auth_access_token.png" class="center-block">
|
|
</figure>
|
|
|
|
<p>Je kan ook een speciale
|
|
<b>authorisatie code</b> terugkrijgen in plaats van een access token als je dat in een parameter aanzet. Deze kan je inwisselen
|
|
voor een access token en een refresh token, een speciale token waarmee je nieuwe access tokens kan maken. Handig als je
|
|
langer dan een uur van de API gebruik wil blijven maken. Met al die verschillende tokens gaan we oefenen in de opdrachten.</p>
|
|
|
|
<h3>Opdrachten</h3>
|
|
|
|
<p>Deze site heeft ook een API die te vinden is op:
|
|
<a href="/api/hello" target="_blank">/api/hello</a>. Die mag je uiteraard alleen gebruiken als je een access token hebt, als je gewoon op de link klikt krijg
|
|
je een lege 403 pagina (Forbidden). We gaan alle stappen doorlopen om te komen tot de almachtige access token waarmee we
|
|
de API kunnen aanroepen.</p>
|
|
|
|
<p>De eerste stap bij een OAuth provider is om een client aan te maken. Daarmee weet de server altijd wie het is die de API
|
|
aanroep doet. Voor deze opdracht kan je die aanmaken op
|
|
<a href="/o/applications/" target="_blank">deze pagina</a>. Maak je eigen applicatie met de volgende gegevens:</p>
|
|
|
|
<ul>
|
|
<li>
|
|
<b>Name</b>: mag je zelf kiezen</li>
|
|
<li>
|
|
<b>Client id</b>: mag je zelf kiezen, maar moet wel uniek zijn</li>
|
|
<li>
|
|
<b>Client secret</b>: mag je zelf kiezen</li>
|
|
<li>
|
|
<b>Client type</b>: Confidential</li>
|
|
<li>
|
|
<b>Authorization grant type</b>: Implicit</li>
|
|
<li>
|
|
<b>Redirect uris</b>: http://example.com/ (mag ook een eigen URL zijn)</li>
|
|
</ul>
|
|
|
|
<figure>
|
|
<img src="static/img/oauth_register.png" style="width: 50%">
|
|
</figure>
|
|
|
|
<p>Elke OAuth server heeft twee endpoints (lees: URL's): de authorisatie endpoint en de token endpoint. Hieronder zie je de
|
|
endpoints van een aantal bekende servers:</p>
|
|
|
|
<table border="1" cellpadding="4">
|
|
<tr>
|
|
<th>Service</th>
|
|
<th>Authorization endpoint</th>
|
|
<th>Token endpoint</th>
|
|
</tr>
|
|
<tr>
|
|
<td>Deze cursus</td>
|
|
<td>https://websec.paulwagener.nl/o/authorize/</td>
|
|
<td>https://websec.paulwagener.nl/o/token/</td>
|
|
</tr>
|
|
<tr>
|
|
<td>GitHub</td>
|
|
<td>https://github.com/login/oauth/authorize</td>
|
|
<td>https://github.com/login/oauth/access_token</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Google</td>
|
|
<td>https://accounts.google.com/o/oauth2/auth</td>
|
|
<td>https://accounts.google.com/o/oauth2/token</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Facebook</td>
|
|
<td>https://www.facebook.com/dialog/oauth</td>
|
|
<td>https://graph.facebook.com/oauth/access_token</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p>De authorisatie endpoint is de URL waar je de gebruiker heenstuurt om hem te laten inloggen op de website en om op 'Toestaan'
|
|
te laten klikken. Deze site redirect daarna weer terug naar je eigen site / app met een speciale authorisatie code. De
|
|
token endpoint is de OAuth API waar we al onze access tokens uit kunnen halen, maar dan hebben we wel eerst die authorisatie
|
|
code nodig.</p>
|
|
|
|
<p>Om te authoriseren hebben we de volgende URL parameters nodig: (
|
|
<a href="http://tools.ietf.org/html/rfc6749#section-4.1.1" target="_blank">documentatie</a>)</p>
|
|
|
|
<ul>
|
|
<li>
|
|
<b>response_type</b>: "token" of "code". Met "token" krijg je direct een access token in de redirect URL. Handig als je
|
|
de API maar 1x hoeft te gebruiken. Minder handig als je de API langer wilt blijven gebruiken, want access tokens blijven
|
|
vaak niet langer dan een uur geldig. Dan moet je de gebruiker elk uur opnieuw laten inloggen. Met "code" krijg je een
|
|
authorisatie code die je kan inwisselen voor een speciale refresh_token, daar kan je tot in de oneindigheid nieuwe access
|
|
tokens mee maken.
|
|
<i>Voor deze opdracht gebruiken we "token"</i>.</li>
|
|
<li>
|
|
<b>client_id</b>: Jouw gekozen client id</li>
|
|
<li>
|
|
<b>redirect_uri</b>: optioneel, en eigenlijk ook redundant. Want die geef je ook vaak op als je je registreert als client
|
|
bij de provider. Als de gebruiker op 'Toestaan' heeft geklikt wordt hij naar deze URL teruggestuurt met de authorisatie
|
|
code of access token</li>
|
|
<li>
|
|
<b>scope</b>: optioneel, kan je mee aangeven welke gedeeltes van de API je wilt kunnen gebruiken</li>
|
|
<li>
|
|
<b>state</b>: optioneel, een string die je kan meegeven die ook weer wordt teruggestuurd als de gebruiker wordt terug geredirect.
|
|
Kan handig zijn.</li>
|
|
</ul>
|
|
|
|
<p>Voor deze site is de authorisatie URL:
|
|
<br>
|
|
<span class="url">https://websec.paulwagener.nl/o/authorize/?client_id=(CLIENT_ID)&response_type=token</span>
|
|
</p>
|
|
|
|
<div class="question">
|
|
<span class="question-string">Authoriseer met je eigen client id. Welke access token heb je gekregen?</span>
|
|
</div>
|
|
|
|
<p class="hint">
|
|
<strong>Hint:</strong> Let je wel op dat er een slash staat achter
|
|
<code>authorize</code> in de URL?</p>
|
|
|
|
<div class="question">
|
|
<span class="question-string">Hoeveel seconden is deze token code geldig?</span>
|
|
</div>
|
|
|
|
<p>De access token kan je nu gebruiken om een request te doen naar
|
|
<a href="/api/hello" target="_blank">/api/hello</a>. De token geef je mee door de volgende HTTP header mee te sturen:</p>
|
|
|
|
<code class="pre">Authorization: Bearer nW8JkemabiKpxvD1Yen3FCcWM3k7vr</code>
|
|
|
|
<p>Uiteraard moet je je eigen access token dan gebruiken. Er zijn vele tools waarmee je HTTP requests mee kan maken met je eigen
|
|
headers en data. In de screenshots gebruiken we
|
|
<a href="https://chrome.google.com/webstore/detail/postman-rest-client-packa/fhbjgbiflinjbdggehcddcbncdddomop" target="_blank">Postman</a>.</p>
|
|
|
|
<img src="static/img/oauth_call_api.png" class="center-block screenshot" style="width: 80%; margin-top: 20px">
|
|
|
|
<div class="question">
|
|
<span class="question-string">Wat is de geheime code die gestuurd wordt als je de API aanroept?</span>
|
|
</div>
|
|
|
|
<p class="hint">
|
|
<strong>Hint:</strong> Let op dat je de Authorization data als
|
|
<i>header</i> meestuurt. Via de POST form-data of als URL parameter gaat niet werken!</p>
|
|
|
|
|
|
<h3>Web applicaties</h3>
|
|
|
|
<p>Wat doen we als de token verloopt en je nog steeds de API wil gebruiken? Je kan natuurlijk de gebruiker opnieuw naar de authorisatie
|
|
URL sturen voor een nieuwe access token. Maar als jouw web applicatie dagelijks de gebruiker meerdere keren per dag redirect
|
|
om op 'Geef toestemming' te klikken wordt die daar helemaal gek van.</p>
|
|
|
|
<p>OAuth heeft hier de zogenaamde refresh tokens voor verzonnen. Dit is een uitgebreidere manier om access tokens te krijgen
|
|
en is meer geschikt voor webapplicaties. We moeten wel helemaal terug naar het begin en alle stappen net iets anders doen:</p>
|
|
|
|
<ol>
|
|
<li>Ga naar
|
|
<a href="/o/applications/" target="_blank">/o/applications/</a> en verander jouw applicatie zodat deze nu als
|
|
<b>Authorization grant type</b> de waarde
|
|
<b>Authorization code</b> heeft.
|
|
<br>
|
|
<img src="static/img/oauth_set_grant_type.png" class="screenshot" style="width: 40%">
|
|
</li>
|
|
|
|
<li>Ga opnieuw naar dezelfde authorisatie URL van vorige keer, maar nu met
|
|
<b>response_type=code</b> in plaats van response_type=token</li>
|
|
|
|
<li>Je wordt nu teruggestuurd met een zogenaamde authorisatie code</li>
|
|
</ol>
|
|
|
|
<div class="question">
|
|
<span class="question-string">Welke authorisatie code heb je gekregen?</span>
|
|
</div>
|
|
|
|
<img src="static/img/oauth_authorization_code.png" style="width: 25%; float: right">
|
|
|
|
|
|
<p>Deze authorisatie code is nog geen access token op zich, maar kan een webapplicatie wel inwisselen voor een access token.
|
|
Dit doet het door een POST request te doen naar de token endpoint. We moeten daarvoor de volgende parameters meesturen:</p>
|
|
|
|
<ul>
|
|
<li>
|
|
<b>grant_type</b>: Moet de waarde "authorization_code" hebben.</li>
|
|
<li>
|
|
<b>code</b>: De authorisatie code die we hebben gekregen van de authorisatie endpoint.</li>
|
|
<li>
|
|
<b>redirect_uri</b>: Dezelfde URL die we eerder als redirect_uri hebben gebruikt.</li>
|
|
<li>
|
|
<b>client_id</b>: De client id die je hebt verzonnen</li>
|
|
<li>
|
|
<b>client_secret</b>: De client secret die je hebt verzonnen</li>
|
|
</ul>
|
|
|
|
<p>Doe er niet te lang over, authorisatie codes verlopen vaak al na enkele minuten. Als je het goed doet krijg je een response
|
|
met daarin de refresh_token (en een gratis access_token!). Als je een
|
|
<code>invalid_grant</code> error krijgt betekent dat dat jouw code verlopen is en dat je een nieuwe moet aanvragen. Als je een
|
|
<code>invalid_client</code> error krijgt moet je goed controleren of je redirect_uri, client_id en client_secret allemaal goed staan ingesteld. En
|
|
als je een
|
|
<code>access_denied</code> krijgt moet je nog een keer heel goed controleren of je redirect_uri echt
|
|
<i>exact</i> hetzelfde is als toen je je app hebt geregistreerd.</code>. Krijg je een andere error? Controleer dan nog een
|
|
keer extra goed of de URL eindigt op
|
|
<code>/token/</code> (die laatste slash is belangrijk!) en dat je alle data verstuurd als form data via POST.</p>
|
|
|
|
<img src="static/img/oauth_exchange_auth_code.png" class="screenshot" style="margin: 20px; width: 80%; margin-left: auto; margin-right: auto;">
|
|
|
|
<div class="question">
|
|
<span class="question-string">Copy paste de response die je hebt gekregen waar de refresh token in staat</span>
|
|
</div>
|
|
|
|
<img src="static/img/oauth_refresh_token.png" style="float: right; width: 25%">
|
|
<p>Gefeliciteerd, je hebt nu een refresh token! Een oneindige bron van nieuwe access tokens. Je vraagt je bijna af waarom OAuth
|
|
uberhaupt de moeite doet om een authorisatie code te hebben als die toch maar zo kort gebruikt wordt. De reden hiervoor
|
|
is dat dit mechanisme ervoor zorgt dat alleen de webapplicatie de refresh en access tokens kan ophalen, en niet de gebruiker.
|
|
Zonder de juiste client_secret kan de gebruiker namelijk helemaal niets met de authorisatie code.</p>
|
|
|
|
<p>We schakelen voor het gebruik van de refresh token even over naar een hele andere site</p>
|
|
|
|
<h3>Google</h3>
|
|
|
|
<p>Bij Google hebben we een OAuth client geregistreerd en de volgende gegevens gekregen:</p>
|
|
|
|
<code style="display: block;">
|
|
Client id: 507532281075-92u6kon3r2kpesqbadmt29cnlvtpjbkh.apps.googleusercontent.com<br>
|
|
Client secret: yTmMOl9GfCFl-F4QoESfi7HT<br>
|
|
Refresh token: 1/EpIRGrHcCV8wi54zGwJoKUklSTkndrJvj1mixqNrgkY"<br></code>
|
|
|
|
<p>Met deze gegevens kunnen we weer een POST doen naar de token endpoint (voor Google: https://accounts.google.com/o/oauth2/token)
|
|
om een access token te genereren. Dit keer hoeven we geen redirect_uri mee te sturen en moeten we de grant_type parameter
|
|
op 'refresh_token' zetten:</p>
|
|
|
|
<p class="hint">
|
|
<strong>Hint:</strong> Deze keer geen slash achteraan de token URL endpoint, goed opletten!</p>
|
|
|
|
<img src="static/img/oauth_google_refresh.png" class="screenshot" style="width: 70%; margin: 20px; display: block">
|
|
|
|
<p>Gebruik de access token die je krijgt om een API call te doen naar
|
|
<span class="url">https://www.googleapis.com/calendar/v3/calendars/websec.paulwagener@gmail.com/events</span>. Vergeet niet weer de juiste Authorization
|
|
header te gebruiken om je access token in te zetten! Als je het goed doet krijg je alle events uit onze Google Calender
|
|
te zien in JSON formaat.</p>
|
|
|
|
<img src="static/img/oauth_google_call_api.png" class="screenshot" style="width: 70%; margin: 20px; display: block">
|
|
|
|
<div class="question">
|
|
<span class="question-string">Wat is de code die in de Geheime afspraak staat? </span>
|
|
</div>
|
|
|
|
<p>Genoeg tutorial, tijd voor het echte werk! We gaan handmatig het hele OAuth proces doorlopen voor een Google API. Dat mag
|
|
weer de
|
|
<a href="https://developers.google.com/google-apps/calendar/v3/reference/events/list" target="_blank">Google Calendar API</a> zijn maar dan voor je eigen Google Calendar. Maar je mag ook een
|
|
<a href="https://developers.google.com/apis-explorer/" target="_blank">andere API</a> kiezen zoals
|
|
<a href="https://developers.google.com/gmail/api/" target="_blank">Gmail</a> of
|
|
<a href="https://developers.google.com/drive/v2/reference/" target="_blank">Google Drive</a>.</p>
|
|
|
|
<p>Details over Google OAuth 2.0 kan je vinden op
|
|
<a href="https://developers.google.com/identity/protocols/OAuth2" target="_blank">deze pagina</a>. Let op dat je API's eerst moet activeren in de
|
|
<a href="http://console.developers.google.com/" target="_blank">Developer Console</a>. Dat is ook de plek waar je je Client ID en Client secret krijgt, die maak je aan onder APIs &
|
|
auth > Credentials. Maak een nieuwe client aan voor een standaard web applicatie.</p>
|
|
|
|
<p>Gebruik de authorisatie URL zoals die
|
|
<a href="https://developers.google.com/identity/protocols/OAuth2WebServer#formingtheurl" target="_blank">hier</a> staat gedocumenteerd.</p>
|
|
|
|
<p class="hint">
|
|
<strong>Hint: </strong>Vergeet niet de juiste scope te gebruiken zodat de gebruiker ook echt toestemming kan geven voor een bepaalde
|
|
API. Bij Google zijn de scopes meestal URL's. Voor Google Calendar kan je de scope bijvoorbeeld
|
|
<a href="https://developers.google.com/google-apps/calendar/auth">hier</a> vinden</p>
|
|
|
|
|
|
<div class="question">
|
|
<span class="question-string">Beschrijf de HTTP requests die je hebt gemaakt om de token te krijgen EN de API te gebruiken (en ook de uitkomst van die
|
|
requests). Gevoelige data mag je met ***** censureren.</span>
|
|
</div>
|
|
|
|
<h3>Mini webapp</h3>
|
|
|
|
<p>Maak een kleine webapplicatie in PHP of JavaScript waarmee je de requests uit de vorige opdracht automatisch uitvoert (je
|
|
mag ook een OAuth framework gebruiken)</p>
|
|
|
|
<img src="static/img/webapp1.png" class="screenshot" style="width: 40%">
|
|
<img src="static/img/webapp2.png" class="screenshot" style="width: 40%">
|
|
|
|
<div class="question">
|
|
<span class="question-string">Plak de broncode van je webapplicatie in het tekstvak</span>
|
|
</div>
|
|
|
|
{% endblock %} |