REST API Design

Was haben wir vor?

  • HTTP Standards
  • REST
  • Authentifizierung
  • API-Dokumentation

HTTP

Hypertext Transfer Protocol

Geschichte

HTTP/1.1

publiziert 1999

HTTP/1.1

modifiziert 2007

HTTP/2?

gibt es seit 2015

TCP

IP

TCP

HTTP

Zustandslos

Lebenszyklus

Request

Client

Response

Server

Nachrichten

Aufbau

Header

Body

Transport

http://my-app.net/api.php

DNS

API Server

213.73.89.123

HTTP Nachrichten

auf Port 80

Klartext

Kommunikation

$ telnet www.hs-bremen.de 80
Trying 194.94.24.5...
Connected to www.hs-bremen.de.
Escape character is '^]'.

GET / http/1.1
Host: www.hs-bremen.de

HTTP/1.1 200 OK
Date: Sat, 20 Feb 2016 17:02:00 GMT
Server: Apache
Accept-Ranges: bytes
X-Mod-Pagespeed: 1.1.23.1-2169
Vary: Accept-Encoding
Cache-Control: max-age=0, no-cache
Content-Length: 204
Content-Type: text/html

<html>...</html>

Connection closed by foreign host.

Request

GET / http/1.1
Host: www.hs-bremen.de<CR><LF>
<CR><LF>

Response

HTTP/1.1 200 OK
Date: Sat, 20 Feb 2016 17:02:00 GMT
Server: Apache
Accept-Ranges: bytes
X-Mod-Pagespeed: 1.1.23.1-2169
Vary: Accept-Encoding
Cache-Control: max-age=0, no-cache
Content-Length: 204
Content-Type: text/html<CR><LF>
<CR><LF>
<html>...</html><CR><LF>
<CR><LF>

Header

...die wichtigsten

Request

  • Accept
  • Authorization
  • Cache-Control
  • Cookie
  • Content-Type
  • If-Modified-Since
  • Referer
  • User-Agent

Response

  • Allow
  • Cache-Control
  • Content-Length
  • Content-Type
  • Expires
  • Last-Modified
  • Location

HTTP-Methoden

GET

POST

HEAD

PUT

PATCH

DELETE

TRACE

OPTIONS

CONNECT

Datenübertragung

HTTP-GET

HTTP-GET

GET /api.php?search=cats&breed=british%20shorthair HTTP/1.1
Host: my-app.net
...

Query-String

?<key>=<value>&<key>=<value>&...
  • search => cats
  • breed => british shorthair

HTTP-POST

HTTP-POST

POST /api.php/cats HTTP/1.1
Host: my-app.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 29

name=Zoe&breed=domestic%20cat
POST /api.php/cats HTTP/1.1
Host: my-app.net
Content-Type: application/json
Content-Length: 37

{"name":"Zoe","breed":"domestic cat"}

UPLOADS

multipart/form-data

multipart/form-data?

 <form action="http://my-app.net/api.php/cats" 
       enctype="multipart/form-data" 
       method="post">
   <label for="name">Name der Katze?</label>
   <input type="text" id="name" name="name">
   <label for="pictures">Fotos der Katze?</label>
   <input type="file" id="pictures" name="pictures" multiple>
   <button type="submit">Speichern</button> 
   <button type="reset">Abbrechen</button>
 </form>

multipart/form-data

 <form action="http://my-app.net/api.php/cats" enctype="multipart/form-data" method="post">
   <label for="name">Name der Katze?</label>
   <input type="text" id="name" name="name">
   <label for="vita">Lebenlauf der Katze?</label>
   <input type="file" id="vita" name="vita">
   <button type="submit">Speichern</button> 
   <button type="reset">Abbrechen</button>
 </form>
POST /api.php/cats HTTP/1.1
Host: my-app.net
Content-Type: multipart/form-data; boundary=AaB03x

--AaB03x
Content-Disposition: form-data; name="name"

Larry
--AaB03x
Content-Disposition: form-data; name="vita"; filename="file1.txt"
Content-Type: text/plain

... contents of file1.txt ...
--AaB03x--

"name" Feld

"vita" Feld

multipart/form-data

 <form action="http://my-app.net/api.php/cats" enctype="multipart/form-data" method="post">
   <!-- wie eben -->
   <input type="file" id="pictures" name="pictures" multiple>
   <!-- wie eben -->
 </form>
POST /api.php/cats HTTP/1.1
Host: my-app.net
Content-Type: multipart/form-data; boundary=AaB03x

--AaB03x
Content-Disposition: form-data; name="name"

Larry
--AaB03x
Content-Disposition: form-data; name="pictures"
Content-Type: multipart/mixed; boundary=BbC04y

--BbC04y
Content-Disposition: file; filename="img1.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary

... contents of img1.jpg ...
--BbC04y
Content-Disposition: file; filename="img2.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary

... contents of img2.jpg ...
--BbC04y--
--AaB03x--

"name" Feld

Header für "pictures" Feld

Datei 1 aus "pictures"

Datei 2 aus "pictures"

Response

Status Codes

1xx – Informationen

  • 100 - Continue
  • 101 - Switching Protocols
  • 102 - Processing

2xx – Erfolgreiche Operation

  • 200 - OK
  • 201 - Created
  • 202 - Accepted
  • 204 - No Content

3xx – Umleitung

  • 301 - Moved Permanently
  • 302 - Found
  • 303 - See Other
  • 304 - Not Modified
PUT /api.php/cats/1 HTTP/1.1
Host: my-app.net

... body ...

HTTP/1.1 302 FOUND
Date: Sat, 20 Feb 2016 23:02:00 GMT
Location: /api.php/cats

4xx – Client-Fehler

  • 400 - Bad Request
  • 401 - Unauthorized
  • 403 - Forbidden
  • 404 - Not Found
  • 405 - Method Not Allowed

5xx – Server-Fehler

  • 500 - Internal Server Error
  • 501 - Not Implemented
  • 502 - Bad Gateway
  • 503 - Service Unavailable

Ressourcen

In Ressourcen denken

Dokument

v.d. Leyens Doktor-arbeit

Star Wars

Film

Einkauf

Personal-

einstellungs-

prozess

Ressourcen identifizeren

über URI

Eine Ressource kann mehrere URI haben

  • http://restbucks.com/order/1234
  • http://restbucks.com/order/1234.json
     
  • http://www.google.com
  • http://google.com

URI Schemata

<schema>:<schema-spezifische-struktur>
  • http
  • ftp
  • mailto
  • urn

Verschiedene URI Formen

URI

Uniform Resource Identifier

Zur allgemeinen und unspezifischen Ressourcen Identifizierung.

IRI

International Resource Identifier

Eine Erweiterung der URI, welche internationale Zeichen erlaubt.

URN

Uniform Resource Name

Abstrakteste Form eine Resource zu identifizieren. Die URN ist unabhängig von technischen Gegebenheiten und definiert ausschließlich Namensräume für Ressourcen, z.B. urn:isbn:123456789

URL

Uniform Resource Locator

Eine besondere Art von URI, die vermittelt, wie man mit einer Ressource interagiert.

Zum Beispiel vermittelt http://google.com eine Web-Ressource mit der man über HTTP kommunizieren kann.

Adresse

Verbreitetes Synonym für Ressource URI innerhalb des Internets.

Sprich: HTTP URI

Darstellungsformen

Informelle Darstellung

Trennung von Information und Anzeige

http://restbucks.com/menu

Menu

Latte: 5 €
Espresso: 4 €
Cookie: 1 €

<html>
<body>
<h1>Menu</h1>
<ul>
<li>Latte: 5€</li>
<li>Espresso: 4€</li>
<li>Cookie: 1€</li>
</ul>
</body>
**Menu**

Latte: 5€
Espresso: 4€
Cookie: 1€

HTML

Text

Computer - Computer Interaktion

http://restbucks.com/order/123

Order

ID: 123
Customer-ID: 5
Coffee: Latte
Payment: 5€
Date: 2016-02-05

<order id="123">
<customer>
http://restbucks/customer/5
</customer>
<coffee>Latte</coffee>
<payment>5€</payment>
<date>2016-02-05</date>
</order>

Darstellungsformate und URIs

  • http://restbucks/menu.xml
  • http://restbucks/menu.json
  • http://restbucks/menu.html

Menu

Ressource

http://restbucks.com/menu

XML Darstellung

JSON Darstellung

Text Darstellung

HTTP Accept Header

Kommunikation

Perfektes Zusammenspiel

  • Ressourcen
  • (HTTP) Verben
  • Accept Header
  • (HTTP) Response Codes

= ?

REST

Representation State Transfer

Roy Fielding

Hypermedia

State Machine

  • Von Zustand zu Zustand
  • REST
    • Folgezustände und Übergänge nicht im Voraus bekannt

hypermedia as the engine of application state

Quelle: REST in Practice, Seite: 14

URI

im Detail

Ein guter URI

  • Präzise
  • Beschreibend
  • Intuitiv
http://restbucks.com/order/1234

URI Templates

http://restbucks.com/order/1234
http://restbucks.com/order/{order-id}
http://restbucks.com/order/{year}/{month}/{day}

URI Tunneling

URI Tunneling

GET? POST?

GET

ist idempotent

Idempotenz ist ein Begriff aus der Mathematik und Informatik. Man bezeichnet ein Element einer Menge, das mit sich selbst verknüpft wieder sich selbst ergibt, als idempotent.

URI Tunneling

POST?

Vertreter dieser Art

  • WSDL
  • JSON-RPC
  • XML-RPC

Webservices! REST-API?

CRUD

Create Read Update Delete

POST    GET    PUT     DELETE

PATCH

CRUD in Restbucks

  • Bestellungen werden erstellt, wenn ein Kunde einen Kauf tätigt.
  • Bestellungen werden häufig gelesen, besonders wenn ihr Status abgefragt wird.
  • Unter bestimmten Umständen kann eine Bestellung noch geändert werden.
  • Bestellungen, die noch ausstehend sind, können storniert werden.

Ressourcen

Lebenszyklus einer Bestellung

aufgegeben

bezahlt

abgeschlossen

ausgeliefert

aktualisiert

Order CRUD

Verb URI Aktion
GET /order Liefert die Liste aller Bestellungen
POST /order Neue Bestellung erstellen und bei Erfolg einen Location Header zur neuen Bestellung zurückgeben.
GET /order/{orderId} Gibt den Status der angegebenen Bestellung zurück.
PUT /order/{orderId} Aktualisiert die Bestellung.
DELETE /order/{orderId} Löscht die Bestellung.

Datenübertragung

JSON

Bestellung erstellen

{
  "location": "takeAway",
  "items": [
    {
      "name": "Latte",
      "quantity": 1,
      "milk": "whole",
      "size": "small"
    }
  ]
}
POST http://restbucks.com/order

Bestellung erstellen

POST /order HTTP/1.1
Host: restbucks.com
Content-Type: application/json
Content-Length: 157 

{ ... }

Kunde

Restbucks

POST /order

{...}

201 Created

Location /order/1234

400 Bad Request

500 Internal Error

Bestellung erfolgreich erstellt

HTTP/1.1 201 Created
Content-Length: 203
Content-Type: application/json
Date: Wed, 01 Apr 2016 21:45:03 GMT
Location: http://restbucks.com/order/1234

{
  "_href": "http://restbucks.com/order/1234",
  "location": "takeAway",
  "items": [
    {
      "name": "Latte",
      "quantity": 1,
      "milk": "whole",
      "size": "small"
    }
  ],
  "status": "placed"
}

Bestellung lesen

HTTP/1.1 200 OK
Content-Length: 203
Content-Type: application/json
Date: Wed, 01 Apr 2016 21:45:03 GMT

{
  "_href": "http://restbucks.com/order/1234",
  "location": "takeAway",
  "items": [
    {
      "name": "Latte",
      "quantity": 1,
      "milk": "whole",
      "size": "small"
    }
  ],
  "status": "served"
}
GET /order/1234 HTTP/1.1
Host: restbucks.com

Bestellung ändern

HTTP/1.1 200 OK
Content-Length: 203
Content-Type: application/json
Date: Wed, 01 Apr 2016 21:45:03 GMT

{ ... }
PUT /order/1234 HTTP/1.1
Host: restbucks.com

{
  "location": "takeAway",
  "items": [
    {
      "name": "Latte",
      "quantity": 2,
      "milk": "whole",
      "size": "large"
    }
  ]
}
HTTP/1.1 204 No Content
Date: Wed, 01 Apr 2016 21:45:03 GMT

Bestellung ändern (selektiv)

HTTP/1.1 200 OK
Content-Length: 203
Content-Type: application/json
Date: Wed, 01 Apr 2016 21:45:03 GMT

{ ... <Vollständiges DTO> ... }
PATCH /order/1234 HTTP/1.1
Host: restbucks.com

{
  "location": "inStore",
}
HTTP/1.1 204 No Content
Date: Wed, 01 Apr 2016 21:45:03 GMT

Bestellung löschen

DELETE /order/1234 HTTP/1.1
Host: restbucks.com
HTTP/1.1 204 No Content
Date: Wed, 01 Apr 2016 21:45:03 GMT

Bestellung löschen?

DELETE /order/1234 HTTP/1.1
Host: restbucks.com
HTTP/1.1 405 Method Not Allowed
Allow: GET
Date: Wed, 01 Apr 2016 21:45:03 GMT

State Übergänge

{
  "location": "takeAway",
  "items": [
    {
      "name": "Latte",
      "quantity": 1,
      "milk": "whole",
      "size": "small"
    }
  ],
  "status": "served",
  "_href": "http://restbucks.com/order/1234",
  "_actions": [
    {
      "action": "pay",
      "href": "http://restbucks.com/order/1234/pay",
      "method": "POST"
    }
  ]
}

Security

HTTP Security Grundlagen

HTTP Authentication und Authorization

Beispiel:

Restbucks Payment nur für Kunden!

HTTP Basic Auth

Rechnung abrufen

GET /payment/1234 HTTP/1.1
Host: restbucks.com
401 Unauthorized
WWW-Authenticate: Basic realm="payments@restbucks.com"

# WWW-Authenticate: <Auth-Typ> <Auth-Optionen>

Request

Response

Rechnung abrufen

GET /payment/1234 HTTP/1.1
Host: restbucks.com
Authorization: Basic Zm9vOmJhcg==

Request mit Basic Auth

  • Realm spielt für den Server keine Rolle
  • Basic Auth
    • base64_encode("<login>:<passwort>");
    • http://<login>:<password>@my-api.com/

HTTP Digest Auth

Rechnung abrufen

GET /payment/1234 HTTP/1.1
Host: restbucks.com
401 Unauthorized
WWW-Authenticate: Digest realm="payments@restbucks.com",
      qop="auth",
      nonce="1e8c46a7d793433490cb8303f18a86e5",
      opaque="ff1eccda9ef442b3b38cabb2435d5967"

Request

Response

Digest

401 Unauthorized
WWW-Authenticate: Digest realm="payments@restbucks.com",
      qop="auth",
      nonce="1e8c46a7d793433490cb8303f18a86e5",
      opaque="ff1eccda9ef442b3b38cabb2435d5967"
  • qop
    • Quality of Protection
      • auth
        • Hash basiert auf URI, Login, Passwort & HTTP-Method
      • auth-int
        • wie auth, inkl. Body

Digest

401 Unauthorized
WWW-Authenticate: Digest realm="payments@restbucks.com",
      qop="auth",
      nonce="1e8c46a7d793433490cb8303f18a86e5",
      opaque="ff1eccda9ef442b3b38cabb2435d5967"
  • nonce
    • Zufälliger String
    • Bestimmte Zeit gültig (Session od. Anzahl)
    • Servergeneriert
    • "Salz" des Servers

Digest

401 Unauthorized
WWW-Authenticate: Digest realm="payments@restbucks.com",
      qop="auth",
      nonce="1e8c46a7d793433490cb8303f18a86e5",
      opaque="ff1eccda9ef442b3b38cabb2435d5967"
  • opaque
    • Zufälliger String
    • Muss unverändert in allen Requests zurückgegeben werden

Rechnung abrufen

GET /payment/1234 HTTP/1.1
Host: restbucks.com
Authorization: Digest username="beancounter",
    realm="payments@restbucks.com",
    nonce="1e8c46a7d793433490cb8303f18a86e5",
    uri="/payment/1234"
    qop="auth",
    nc=00000001,
    cnonce="cf45f0087f33bce12332aef430945dff",
    response="ff14aa3457acd60aa2091232a98756ff",
    opaque="ff1eccda9ef442b3b38cabb2435d5967"

Request mit Digest Auth

Rechnung abrufen

GET /payment/1234 HTTP/1.1
Host: restbucks.com
Authorization: Digest username="beancounter",
    realm="payments@restbucks.com",
    nonce="1e8c46a7d793433490cb8303f18a86e5",
    uri="/payment/1234"
    qop="auth",
    nc=00000001,
    cnonce="cf45f0087f33bce12332aef430945dff",
    response="ff14aa3457acd60aa2091232a98756ff",
    opaque="ff1eccda9ef442b3b38cabb2435d5967"

Request mit Digest Auth

  • nc (hexadezimal)
    • Wie oft wurde der nonce benutzt?
  • cnonce
    • nonce generiert vom Client
  • response
    • Response Hash

Rechnung abrufen

GET /payment/1234 HTTP/1.1
Host: restbucks.com
Authorization: Digest username="beancounter",
    realm="payments@restbucks.com",
    nonce="1e8c46a7d793433490cb8303f18a86e5",
    uri="/payment/1234"
    qop="auth",
    nc=00000001,
    cnonce="cf45f0087f33bce12332aef430945dff",
    response="ff14aa3457acd60aa2091232a98756ff",
    opaque="ff1eccda9ef442b3b38cabb2435d5967"

Request mit Digest Auth

  • response Berechnung:
    1. MD5(username, realm & passwort)
    2. MD5(method & uri)
    3. MD5(Step1, nonce, nc, cnonce, qop & Step2)

Digest Man-in-the-middle Attack

HTTPS!

Api-Keys

simpel aber mit Vorsicht zu genießen

1 : 1

GET /order/1234?apikey=<some-pretty-long-cryptic-api-key> HTTP/1.1
Host: restbucks.com

OpenID

OpenID Workflow

OpenID Workflow

  1. Initiierung
    • Ein Kunde übermittelt eine OpenID
  2. Ermittlung
    • Ermittlung des OpeID Providers
  3. Schlüssel Austausch
    • Provider und API tauschen einen Token aus
  4. Redirect zum Provider
    • Der Kunde wird zum Provider umgeleitet
  5. Authentifizierung
    • Der Kunde authentifiziert sich beim Provider
  6. Redirect zur API
    • Bei Erfolg wird der Kunde zur API zurückgeleitet
  7. Anmeldung
    • Über den, vom Provider verifizierten, Token

OpenID Anmerkungen

  • Erfordert Benutzer Interaktion
    • oft mit "echtem" Login-Formular
  • Workflow kann abweichen
    • z.B. bei Two-Factor Authentication
  • Vertrauen
    • Verschiedene Provider

Au­then­ti­fi­zie­rung

VS.

Autorisation

  • Authentifizierung
    • Wer bist du?
    • z.B. OpenID
  • Autorisation?
    • Was darfst du?
    • z.B. OAuth

OAuth

OpenID vs. OAuth

Szenario:

Bestellung auf Twitter posten!

Drei Spieler

  • Der User
  • Der Konsument
  • Der Service Provider

Kommunikation

im OAuth (v1.x) Land

1. Absicht bekunden

  • Ole (User):
    Hey, Restbucks, ich würde meine Bestellungen gerne bei Twitter posten!
  • Restbucks (Konsument):
    Super. Ich frag mal nach Erlaubnis.

2. Erlaubnis einholen

  • Restbucks (Konsument):
    Ich habe hier einen User, der gerne seine Bestellungen twittern möchte. Dafür müsste ich, an seiner Stelle, in seinen Stream schreiben dürfen. Bekomme ich dafür einen Token?
  • Twitter (Service Provider):
    Klar, hier habe ich einen Token und ein Passwort für dich.

3. Der User muss bestätigen

  • Restbucks:
    Ole, ich habe für dich angefragt, du musst Twitter nur eben bestätigen, dass das okay ist.
  • Ole:
    Okay.

4. Der User bestätigt

  • Ole:
    Twitter, ich möchte den Request Token bestätigen, den ich von Restbucks erhalten habe.
  • Twitter:
    Okay, nur um sicher zu gehen, du möchtest Restbucks autorisieren, in deinem Namen zu tweeten?
  • Ole:
    Jepp!
  • Twitter:
    Okay, du kannst dann Restbucks sagen, dass sie den Token benutzen dürfen.

5. Der Konsument bekommt Zugriff

  • Restbucks:
    Twitter, kann ich diesen Request-Token gegen einen Access-Token tauschen?
  • Twitter:
    Klar, hier ist dein Access-Token und das Passwort dazu.

6. Der Konsument agiert im Namen des Users

  • Restbucks:
    Twitter, Ole hat bestellt und will das twittern. Hier sind Consumer-Key & Passwort, als auch Access-Token & Passwort.
  • Twitter:
    Danke und erledigt!

OAuth Anmerkungen

  • Der Konsument ist Vertreter des Users
  • Konsument und Service Provider können der selbe Dienst sein
  • OAuth 2.0 ist neuer und führt u.a. Token Gültigkeit ein
    • Neue Requests mit dem gleichen Token verlängern die Gültigkeit
    • Workflow kann sich zwischen Web, Mobile und Desktop unterscheiden
HTTP/1.1 410 Gone
Date: Fri, 14 Oct 2016 08:00:00 GMT

Literatur

REST API Design

By Ole Rößner

REST API Design

REST API Design | Ein team neusta Freitagsfrühstück

  • 991