designing a beautiful rest json api

110
Beautiful REST+JSON APIs Les Hazlewood @lhazlewood Apache Shiro Project Chair Expert Group Member, JEE Application Security CTO, Stormpath, stormpath.com

Upload: 0x07de

Post on 13-Aug-2015

39 views

Category:

Internet


0 download

TRANSCRIPT

Page 1: Designing a beautiful REST json api

Beautiful REST+JSON APIs

Les  Hazlewood  @lhazlewood  Apache  Shiro  Project  Chair  

Expert Group Member, JEE Application Security  CTO,  Stormpath,  stormpath.com  

Page 2: Designing a beautiful REST json api

@lhazlewood  

.com •  User Management API for Developers

•  Password security

•  Authentication and Authorization

•  LDAP & Active Directory Cloud Sync

•  Instant-on, scalable, and highly available

•  Free for developers

Page 3: Designing a beautiful REST json api

@lhazlewood  

Outline •  APIs, REST & JSON •  REST Fundamentals •  Design

Base  URL  Versioning  Resource  Format  Return  Values  Content  NegoEaEon  References  (Linking)  PaginaEon  Query  Parameters  AssociaEons  

Errors  IDs  Method  Overloading  Resource  Expansion  ParEal  Responses  Caching  &  Etags  Security  MulE  Tenancy  Maintenance  Batch  OperaEons  

Page 4: Designing a beautiful REST json api

@lhazlewood  

APIs

•  Applications •  Developers •  Pragmatism over Ideology •  Adoption •  Scale

Page 5: Designing a beautiful REST json api

@lhazlewood  

Why REST?

•  Scalability •  Generality •  Independence •  Latency (Caching) •  Security •  Encapsulation

Page 6: Designing a beautiful REST json api

@lhazlewood  

Why JSON?

•  Ubiquity •  Simplicity •  Readability •  Scalability •  Flexibility

Page 7: Designing a beautiful REST json api

@lhazlewood  

HATEOAS

•  Hypermedia

•  As

•  The

•  Engine

•  Of

•  Application

•  State

Page 8: Designing a beautiful REST json api

@lhazlewood  

REST Is Easy

Page 9: Designing a beautiful REST json api

@lhazlewood  

REST Is *&@#$! Hard

(for providers)

Page 10: Designing a beautiful REST json api

@lhazlewood  

REST can be easy

(if you follow some guidelines)

Page 11: Designing a beautiful REST json api

Example Domain: Stormpath

•  Applications •  Directories •  Accounts •  Groups •  Associations •  Workflows

Page 12: Designing a beautiful REST json api

@lhazlewood  

Fundamentals

Page 13: Designing a beautiful REST json api

@lhazlewood  

Resources

Nouns, not Verbs Coarse Grained, not Fine Grained Architectural style for use-case scalability

Page 14: Designing a beautiful REST json api

@lhazlewood  

What If? /getAccount /createDirectory /updateGroup /verifyAccountEmailAddress

Page 15: Designing a beautiful REST json api

@lhazlewood  

What If? /getAccount /getAllAccounts /searchAccounts /createDirectory /createLdapDirectory /updateGroup /updateGroupName /findGroupsByDirectory /searchGroupsByName /verifyAccountEmailAddress /verifyAccountEmailAddressByToken … Smells like bad RPC. DON’T DO THIS.

Page 16: Designing a beautiful REST json api

@lhazlewood  

Keep It Simple

Page 17: Designing a beautiful REST json api

@lhazlewood  

The Answer

Fundamentally two types of resources: Collection Resource Instance Resource

Page 18: Designing a beautiful REST json api

@lhazlewood  

Collection Resource

/applications

Page 19: Designing a beautiful REST json api

@lhazlewood  

Instance Resource

/applications/a1b2c3

Page 20: Designing a beautiful REST json api

@lhazlewood  

Behavior •  GET •  PUT •  POST •  DELETE •  HEAD

Page 21: Designing a beautiful REST json api

@lhazlewood  

Behavior POST, GET, PUT, DELETE

≠  1:1  

Create,  Read,  Update,  Delete  

Page 22: Designing a beautiful REST json api

@lhazlewood  

Behavior As  you  would  expect:   GET  =  Read  DELETE  =  Delete  HEAD  =  Headers,  no  Body  

Page 23: Designing a beautiful REST json api

@lhazlewood  

Behavior Not  so  obvious:    PUT  and  POST  can  both  be  used  for  Create  and  Update  

Page 24: Designing a beautiful REST json api

@lhazlewood  

PUT for Create IdenEfier  is  known  by  the  client:    PUT /applications/clientSpecifiedId

{

}

Page 25: Designing a beautiful REST json api

@lhazlewood  

PUT for Update Full  Replacement    PUT /applications/existingId

{

“name”: “Best App Ever”,

“description”: “Awesomeness”

}

Page 26: Designing a beautiful REST json api

@lhazlewood  

PUT

Idempotent

Page 27: Designing a beautiful REST json api

@lhazlewood  

POST as Create On  a  parent  resource    POST /applications { “name”: “Best App Ever” } Response: 201 Created Location: https://api.stormpath.com/applications/a1b2c3

Page 28: Designing a beautiful REST json api

@lhazlewood  

POST as Update On  instance  resource    POST /applications/a1b2c3 { “name”: “Best App Ever. Srsly.” } Response: 200 OK

Page 29: Designing a beautiful REST json api

@lhazlewood  

POST

NOT  Idempotent

Page 30: Designing a beautiful REST json api

@lhazlewood  

Media Types

•  Format  SpecificaEon  +  Parsing  Rules  •  Request:  Accept  header  •  Response:  Content-Type  header  

•  application/json •  application/foo+json •  application/foo+json;application •  …

Page 31: Designing a beautiful REST json api

@lhazlewood  

Design Time!

Page 32: Designing a beautiful REST json api

@lhazlewood  

Base URL

Page 33: Designing a beautiful REST json api

@lhazlewood  

http(s)://foo.io vs

http://www.foo.com/dev/service/api/rest

Page 34: Designing a beautiful REST json api

@lhazlewood  

http(s)://foo.io

Rest Client vs

Browser

Page 35: Designing a beautiful REST json api

@lhazlewood  

Versioning

Page 36: Designing a beautiful REST json api

@lhazlewood  

URL https://api.stormpath.com/v1 vs. Media-Type application/foo+json;application&v=2 application/foo2+json;application

Page 37: Designing a beautiful REST json api

@lhazlewood  

Resource Format

Page 38: Designing a beautiful REST json api

@lhazlewood  

Media Type

Content-Type: application/json When time allows: application/foo+json

application/foo2+json;bar=baz

Page 39: Designing a beautiful REST json api

@lhazlewood  

Media Type Don’t go overboard! Media Type != Schema! Most only need 2 or 3 custom media types: •  instance resource •  collection resource

application/foo+json application/foo2+json;bar=baz …

Page 40: Designing a beautiful REST json api

@lhazlewood  

camelCase ‘JS’ in ‘JSON’ = JavaScript myArray.forEach Not  myArray.for_each account.givenName Not  account.given_name Underscores for property/function names are unconventional for JS. Stay consistent.

Page 41: Designing a beautiful REST json api

@lhazlewood  

Date/Time/Timestamp There’s already a standard. Use it: ISO 8601 Example: { …, “createdAt”: “2013-07-10T18:02:24.343Z”, ... } Use UTC!

Page 42: Designing a beautiful REST json api

@lhazlewood  

createdAt / updatedAt

Page 43: Designing a beautiful REST json api

@lhazlewood  

createdAt / updatedAt

Most people will want this at some point { …, “createdAt”: “2013-07-10T18:02:24.343Z”, “updatedAt”: “2014-09-29T07:02:48.761Z” } Use UTC!

Page 44: Designing a beautiful REST json api

@lhazlewood  

Response Body

Page 45: Designing a beautiful REST json api

@lhazlewood  

GET obvious

What about POST?

Return the representation in the response when feasible.

Add override (?_body=false) for control

Page 46: Designing a beautiful REST json api

@lhazlewood  

Content Negotiation

Page 47: Designing a beautiful REST json api

@lhazlewood  

Header

•  Accept header •  Header values comma delimited •  q param determines precedence, defaults

to 1, then conventionally by list order

GET /applications/a1b2c3

Accept: application/json, text/plain;q=0.8

Page 48: Designing a beautiful REST json api

@lhazlewood  

Resource Extension

/applications/a1b2c3.json /applications/a1b2c3.csv

ConvenEonally  overrides  Accept  header  

Page 49: Designing a beautiful REST json api

@lhazlewood  

HREF

•  Distributed Hypermedia is paramount!

•  Every accessible Resource has a canonical unique URL

•  Replaces IDs (IDs exist, but are opaque). •  Critical for linking, as we’ll soon see

Page 50: Designing a beautiful REST json api

@lhazlewood  

Instance w/ HREF (v1)

GET /accounts/x7y8z9 200 OK

{

“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,

“givenName”: “Tony”,

“surname”: “Stark”,

...

}

Page 51: Designing a beautiful REST json api

@lhazlewood  

Resource References aka ‘Linking’

(v1)

Page 52: Designing a beautiful REST json api

@lhazlewood  

•  Hypermedia is paramount. •  Linking is fundamental to scalability.

•  Tricky in JSON •  XML has it (XLink), JSON doesn’t •  How do we do it?

Page 53: Designing a beautiful REST json api

@lhazlewood  

Instance Reference (v1)

GET /accounts/x7y8z9 200 OK

{

“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,

“givenName”: “Tony”,

“surname”: “Stark”,

…,

“directory”: ???? }

Page 54: Designing a beautiful REST json api

@lhazlewood  

Instance Reference (v1)

GET /accounts/x7y8z9 200 OK

{

“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”,

“givenName”: “Tony”,

“surname”: “Stark”,

…,

“directory”: { “href”: “https://api.stormpath.com/v1/directories/g4h5i6”

}

}

Page 55: Designing a beautiful REST json api

@lhazlewood  

Collection Reference (v1)

GET /accounts/x7y8z9 200 OK {

“href”: “https://api.stormpath.com/v1/accounts/x7y8z9”, “givenName”: “Tony”,

“surname”: “Stark”, …,

“groups”: { “href”: “https://api.stormpath.com/v1/accounts/x7y8z9/groups”

} }

Page 56: Designing a beautiful REST json api

@lhazlewood  

Linking v2 (recommended)

Page 57: Designing a beautiful REST json api

@lhazlewood  

Instance HREF (v2)

GET /accounts/x7y8z9 200 OK { “meta”: { “href”: “https://api.stormpath.com/v1/accounts/x7y8z9”, “mediaType”: “application/ion+json”, ... }, “givenName”: “Tony”,

“surname”: “Stark”, …

}

Page 58: Designing a beautiful REST json api

@lhazlewood  

Instance Reference (v2) GET /accounts/x7y8z9 200 OK { “meta”: { ... }, “givenName”: “Tony”, “surname”: “Stark”, …, “directory”: { “meta”: { “href”: “https://api.stormpath.com/v1/directories/g4h5i6” “mediaType”: “application/ion+json” } } }

Page 59: Designing a beautiful REST json api

@lhazlewood  

Collection Reference (v2) GET /accounts/x7y8z9 200 OK { “meta”: { ... }, “givenName”: “Tony”, “surname”: “Stark”, …, “groups”: { “meta”: { “href”: “https://api.stormpath.com/v1/accounts/x7y8z9/groups”, “mediaType”: “application/ion+json”, “rel”: [“collection”] } } }

Page 60: Designing a beautiful REST json api

@lhazlewood  

Reference Expansion

(aka Entity Expansion, Link Expansion)

Page 61: Designing a beautiful REST json api

@lhazlewood  

Account and its Directory?

Page 62: Designing a beautiful REST json api

@lhazlewood  

GET /accounts/x7y8z9?expand=directory

200 OK

{

“meta”: {...},

“givenName”: “Tony”,

“surname”: “Stark”,

…,

“directory”: {

“meta”: { ... },

“name”: “Avengers”, “description”: “Hollywood’s hope for more $”, “createdAt”: “2012-07-01T14:22:18.029Z”, … }

}

Page 63: Designing a beautiful REST json api

@lhazlewood  

Partial Representations

Page 64: Designing a beautiful REST json api

@lhazlewood  

GET /accounts/x7y8z9?fields=givenName,surname,directory(name)

Page 65: Designing a beautiful REST json api

@lhazlewood  

Collections!

Page 66: Designing a beautiful REST json api

@lhazlewood  

Collections

•  A first class resource ‘citizen’ •  Own href / metadata •  Own properties •  Different from all other collections

Page 67: Designing a beautiful REST json api

@lhazlewood  

GET /accounts/x7y8z9/groups

200 OK { “meta”: { ... }, “offset”: 0, “limit”: 25, “size”: 289, “first”: { “meta”:{“href”: “…/accounts/x7y8z9/groups?offset=0”}}, “previous”: null, “next”: { “meta”:{“href”: “…/accounts/x7y8z9/groups?offset=25”}}, “last”: { “meta”:{“href”: “…”}}, “items”: [ { “meta”: { “href”: “…”, ...} }, … ] }

Page 68: Designing a beautiful REST json api

@lhazlewood  

Pagination

Page 69: Designing a beautiful REST json api

@lhazlewood  

Collection Resource supports query params: •  Offset •  Limit …/applications?offset=50&limit=25

Page 70: Designing a beautiful REST json api

@lhazlewood  

GET /accounts/x7y8z9/groups 200 OK { “meta”: { ... }, “offset”: 0, “limit”: 25, “first”: { “meta”:{“href”: “…/accounts/x7y8z9/groups?offset=0”}}, “previous”: null, “next”: { “meta”:{“href”: “…/accounts/x7y8z9/groups?offset=25”}}, “last”: { “meta”:{“href”: “…”}}, “items”: [ { “meta”: { “href”: “…”, ...} }, { “meta”: { “href”: “…”, ...} }, … ] }

Page 71: Designing a beautiful REST json api

@lhazlewood  

Sorting

Page 72: Designing a beautiful REST json api

@lhazlewood  

GET .../accounts? orderBy=surname,givenName%20desc

Page 73: Designing a beautiful REST json api

@lhazlewood  

Search

Page 74: Designing a beautiful REST json api

@lhazlewood  

“Find all accounts with a ‘company.com’ email address that can login to a particular

application”

Page 75: Designing a beautiful REST json api

@lhazlewood  

GET /applications/x7y8z9/accounts? email=*company.com 200 OK { “meta”: { ... }, “offset”: 0, “limit”: 25, “first”: { “meta”:{“href”: “/applications/x7y8z9/accounts?email=*company.com&offset=0”}}, “previous”: null, “next”: { “meta”:{“href”: “/applications/x7y8z9/accounts?email=*company.com&offset=25”}}, “last”: { “meta”:{“href”: “…”}}, “items”: [ { “meta”: { “href”: “…”, ...} }, { “meta”: { “href”: “…”, ...} }, … ] }

Page 76: Designing a beautiful REST json api

@lhazlewood  

Search cont’d

•  Filter search .../accounts?q=some+value

•  Attribute Search .../accounts?surname=Joe&email=*company.com

Page 77: Designing a beautiful REST json api

@lhazlewood  

Search cont’d •  Starts with

?email=joe*

•  Ends with

?email=*company.com

•  Contains ?email=*foo*

Page 78: Designing a beautiful REST json api

@lhazlewood  

Search cont’d

•  Range queries

“all accounts created between September 1st and the 15th” .../accounts?createdAt=[2014-09-01,2014-09-15]

Page 79: Designing a beautiful REST json api

@lhazlewood  

Many To Many

Page 80: Designing a beautiful REST json api

@lhazlewood  

Group to Account

•  A group can have many accounts •  An account can be in many groups •  Each mapping is a resource:

GroupMembership

Page 81: Designing a beautiful REST json api

@lhazlewood  

GET /groupMemberships/23lk3j2j3

200 OK

{

“meta”:{“href”: “…/groupMemberships/23lk3j2j3”},

“account”: { “meta”:{“href”: “…”}

},

“group”: {

“meta”{“href”: “…”}

}, …

}

Page 82: Designing a beautiful REST json api

@lhazlewood  

GET /accounts/x7y8z9 200 OK

{

“meta”:{“href”: “…/accounts/x7y8z9”},

“givenName”: “Tony”,

“surname”: “Stark”,

…,

“groups”: { “meta”:{“href”: “…/accounts/x7y8z9/groups”}

},

“groupMemberships”: {

“meta”:{“href”: “…/groupMemberships?accountId=x7y8z9”}

}

}

Page 83: Designing a beautiful REST json api

@lhazlewood  

Async or Long-Lived Operations

Page 84: Designing a beautiful REST json api

@lhazlewood  

POST /emails { “from”: [email protected],

“subject”: “Hi!”

“body”: “...”

}

Page 85: Designing a beautiful REST json api

@lhazlewood  

204 Accepted Location: /emails/23Sd932sSl { “status”: “queued”, ...

}

Page 86: Designing a beautiful REST json api

@lhazlewood  

GET /emails/23Sd932sSl Expires: 2014-09-29T18:00:00.000Z { “status”: “sent”, ...

}

Page 87: Designing a beautiful REST json api

@lhazlewood  

Batch Operations

Page 88: Designing a beautiful REST json api

@lhazlewood  

•  Each batch reflects a resource

•  Batches are likely to be a collection

•  Batches are likely to have a status

•  Batch deletes easier than create/update

Page 89: Designing a beautiful REST json api

@lhazlewood  

Batch Delete

“Delete  all  company.com  accounts”    DELETE /accounts?

email=*@company.com

Page 90: Designing a beautiful REST json api

@lhazlewood  

Batch Create / Update

Already  have  a  CollecEon  concept.    Use  it.

Page 91: Designing a beautiful REST json api

@lhazlewood  

Batch Create or Update POST  /accounts    {          “items”: [ { ... account 1 ... }, { ... account 2 ... }, ... ] }

Page 92: Designing a beautiful REST json api

@lhazlewood  

Batch Operations: The ‘Catch’

Caching  is  bypassed  enErely  L

Page 93: Designing a beautiful REST json api

@lhazlewood  

204 Accepted Location: /batches/a1b2c3 { “status”: “processing”, //overall status “size”: “n”, “limit”: 25, ..., “items”: { { response 1 (w/ individual status) ...}, { response 2 (w/ individual status) ...}, ... } }

Page 94: Designing a beautiful REST json api

@lhazlewood  

Errors

Page 95: Designing a beautiful REST json api

@lhazlewood  

•  As descriptive as possible •  As much information as possible •  Developers are your customers

Page 96: Designing a beautiful REST json api

@lhazlewood  

POST /directories 409 Conflict { “status”: 409, “code”: 40924, “property”: “name”, “message”: “A Directory named ‘Avengers’ already exists.”, “developerMessage”: “A directory named ‘Avengers’ already exists. If you have a stale local cache, please expire it now.”, “moreInfo”: “https://www.stormpath.com/docs/api/errors/40924” }

Page 97: Designing a beautiful REST json api

@lhazlewood  

Security

Page 98: Designing a beautiful REST json api

@lhazlewood  

Avoid sessions when possible Authenticate every request if necessary Stateless

Authorize based on resource content, NOT URL!

Use Existing Protocol:

Oauth 1.0a, Oauth2, Basic over SSL only Custom Authentication Scheme:

Only if you provide client code / SDK Only if you really, really know what you’re doing

Use API Keys instead of Username/Passwords

Page 99: Designing a beautiful REST json api

@lhazlewood  

401 vs 403

•  401 “Unauthorized” really means Unauthenticated “You need valid credentials for me to respond to this request”

•  403 “Forbidden” really means Unauthorized “Sorry, you’re not allowed!”

Page 100: Designing a beautiful REST json api

@lhazlewood  

HTTP Authentication Schemes

•  Server  response  to  issue  challenge:    

WWW-Authenticate: <scheme name> realm=“Application Name” •  Client  request  to  submit  credenEals:    

Authorization: <scheme name> <data>

Page 101: Designing a beautiful REST json api

@lhazlewood  

API Keys

•  Entropy •  Password Reset •  Independence •  Scope •  Speed •  Limited Exposure •  Traceability

Page 102: Designing a beautiful REST json api

@lhazlewood  

IDs

Page 103: Designing a beautiful REST json api

@lhazlewood  

•  IDs should be opaque •  Should be globally unique •  Avoid sequential numbers (contention,

fusking) •  Good candidates: UUIDs, ‘Url64’

Page 104: Designing a beautiful REST json api

@lhazlewood  

HTTP Method Overrides

Page 105: Designing a beautiful REST json api

@lhazlewood  

POST /accounts/x7y8z9?_method=DELETE

Page 106: Designing a beautiful REST json api

@lhazlewood  

Caching & Concurrency Control

Page 107: Designing a beautiful REST json api

@lhazlewood  

Server  (iniEal  response):   ETag: "686897696a7c876b7e”

Client  (later  request):

If-None-Match: "686897696a7c876b7e” Server  (later  response):  

 304 Not Modified

Page 108: Designing a beautiful REST json api

@lhazlewood  

Maintenance

Page 109: Designing a beautiful REST json api

@lhazlewood  

Use HTTP Redirects Create abstraction layer / endpoints when migrating Use well defined custom Media Types

Page 110: Designing a beautiful REST json api

@lhazlewood  

.com •  Free for developers •  Eliminate months of development •  Automatic security best practices •  Single Sign On for your apps •  API Authentication & Key Management •  Token Authentication for SPAs / Mobile •  Authorization

Libraries  and  integraEons:    h`ps://docs.stormpath.com