Protect Your API With OAuth2

A presentation at PHP Yorkshire in April 2017 in York, UK by Rob Allen

Slide 1

Slide 1

Protect Your API with OAuth 2   Slides: http://akrab.at/2nin0yI     Rob Allen ~ April 2017 ~ @akrabat

Slide 2

Slide 2

Authentication Know who is logging into your API • Rate limiting • Revoke application access if its a problem • Allow users to revoke 3rd party applications Rob Allen ~ @akrabat

Slide 3

Slide 3

How? Authorization header: GET /books/1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l   base64_encode ( "Aladdin:OpenSesame" )     

  QWxhZGRpbjpPcGVuU2VzYW1l Rob Allen ~ @akrabat

Slide 4

Slide 4

Problems • All clients have to know user's credentials • Credentials are passed in every request Rob Allen ~ @akrabat

Slide 5

Slide 5

OAuth2 The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service oauth.net Rob Allen ~ @akrabat

Slide 6

Slide 6

Roles • The user (Resource Owner) • The (third-party) application (Client) • The API (Resource Server) • The Authorisation server Rob Allen ~ @akrabat

Slide 7

Slide 7

Grant types Grant type Use case Authorization code 3rd party web or native Password 1st party Client credentials application (no user) Implicit 3rd party JS app Rob Allen ~ @akrabat

Slide 8

Slide 8

Tokens OAuth uses a bearer token GET /books/1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {some-string-here} Rob Allen ~ @akrabat

Slide 9

Slide 9

Password grant (for 1st party apps) Rob Allen ~ @akrabat

Slide 10

Slide 10

Password grant Rob Allen ~ @akrabat

Slide 11

Slide 11

Password flow Rob Allen ~ @akrabat

Slide 12

Slide 12

Password flow Rob Allen ~ @akrabat

Slide 13

Slide 13

Password flow Rob Allen ~ @akrabat

Slide 14

Slide 14

Password flow Rob Allen ~ @akrabat

Slide 15

Slide 15

Implementing in PHP Rob Allen ~ @akrabat

Slide 16

Slide 16

OAuth 2.0 Server PHP by Brent Shaffer $ composer require bshaffer/oauth2-server-php   • Implements Authorise and Token endpoints • Mulitple storage backends: PDO, Redis, Mongo, Cassandra, DynamoDB, etc Rob Allen ~ @akrabat

Slide 17

Slide 17

Steps to implement For the client and user credentials grants: 1. Set up the database tables 2. Register the OAuth2 Server 3. Implement the Authorise endpoint Rob Allen ~ @akrabat

Slide 18

Slide 18

Database tables • CREATE   TABLE   oauth_clients   ... • CREATE   TABLE   oauth_access_tokens   ... • CREATE   TABLE   oauth_authorization_codes   ... • CREATE   TABLE   oauth_refresh_tokens   ... • CREATE   TABLE   oauth_users   ... • CREATE   TABLE   oauth_scopes   ... • CREATE   TABLE   oauth_jwt   ...   (SQL is in the Cookbook in the docs) Rob Allen ~ @akrabat

Slide 19

Slide 19

Create a Server 1  use   MyAuth\PdoStorage ; 2  use   OAuth2\GrantType\UserCredentials ; 3  4  $container [ 'OAuth2Server' ]  

  function   ( $c )   { 5       $pdo  

  $c

get ( 'db' ); 6       $storage  

  new   PdoStorage ( $pdo ); 7  8       $server  

  new   \OAuth2\Server ( $storage ); Rob Allen ~ @akrabat

Slide 20

Slide 20

Add the grant  1  use   MyAuth\PdoStorage ;  2  use   OAuth2\GrantType\UserCredentials ;  3   4  $container [ 'OAuth2Server' ]  

  function   ( $c )   {  5       $pdo  

  $c

get ( 'db' );  6       $storage  

  new   PdoStorage ( $pdo );  7   8       $server  

  new   \OAuth2\Server ( $storage );  9  10       /* Add the password grant type */ 11       $userCreds  

  new   UserCredentials ( $storage ); 12       $server

addGrantType ( $userCreds ); 13  14       return   $server ; 15  }; Rob Allen ~ @akrabat

Slide 21

Slide 21

Aside: use Bcrypt namespace   MyAuth ; class   PdoStorage   extends   \OAuth2\Storage\Pdo {    protected   function   checkPassword ( $user ,   $pwd )    {      return   password_verify ( $pwd ,   $user [ 'password' ]);    } } Rob Allen ~ @akrabat

Slide 22

Slide 22

Credentials We need a client: 1  INSERT   INTO   oauth_clients 2     ( client_id ,   client_secret ,   redirect_uri ) 3  VALUES 4     ( "mywebsite" ,   "$2y$10$mzP0fR...BHu" ,   null ); Rob Allen ~ @akrabat

Slide 23

Slide 23

Credentials We need a client: 1  INSERT   INTO   oauth_clients 2     ( client_id ,   client_secret ,   redirect_uri ) 3  VALUES 4     ( "mywebsite" ,   "$2y$10$mzP0fR...BHu" ,   null ); & a user: 1  INSERT   INTO   oauth_users 2     ( username ,   password ,   first_name ,   last_name ) 3  VALUES 4     ( "rob" ,   "$2y$10$Qq1CsK...LV6" ,   "Rob" ,   "Allen" ); Rob Allen ~ @akrabat

Slide 24

Slide 24

Token endpoint  1  $app

post (  2     '/token' ,  3     function   ( $request ,   $response )   {  4       $server  

  $this

get ( 'OAuth2Server' );  5       $req  

  \OAuth2\Request :: createFromGlobals ();  6   7       $server

handleTokenRequest ( $req )

send ();  8       exit ;  9     } 10  ); Rob Allen ~ @akrabat

Slide 25

Slide 25

How does this work?  1  $ curl -i -X POST http://localhost:8888/token 
 2    -H "Accept: application/json" 
 3    -H "Content-Type: application/json" 
 4    -d $'{  5        "grant_type": "password"  6        "client_id": "mywebsite",  7        "client_secret": "abcdef",  8        "username": "rob",  9        "password": "123456" 10    }' Rob Allen ~ @akrabat

Slide 26

Slide 26

Response  1  HTTP/1.1 200 OK  2  Host: localhost:8888  3  Content-Type: application/json  4   5  {  6    "access_token": "65077f90e3baae8aa863",  7    "expires_in": 3600,  8    "token_type": "Bearer",  9    "scope": null, 10    "refresh_token": "be071d2c6193d32a353d" 11  } Rob Allen ~ @akrabat

Slide 27

Slide 27

Protecting your API endpoints Rob Allen ~ @akrabat

Slide 28

Slide 28

Is the token valid? 1  /* test for valid Auth header */ 2  $req  

  \OAuth2\Request :: createFromGlobals (); 3  if   ( ! $server

verifyResourceRequest ( $req ))   { 4     /* not valid / 5  } 6  7  / get information */ 8  $token  

  $server

getAccessTokenData ( $req ); 9  $username  

  $token [ 'user_id' ]; Rob Allen ~ @akrabat

Slide 29

Slide 29

Unauthorised API call 1  $ curl -i -H "Accept: application/json" 
2    http://localhost:8888/authors 3  4  HTTP/1.1 401 Unauthorized 5  Host: localhost:8888 6  Connection: close 7  X-Powered-By: PHP/7.0.15 8  WWW-Authenticate: Bearer realm="Service" 9  Content-Type: application/json Rob Allen ~ @akrabat

Slide 30

Slide 30

Authorised API call  1  $ curl -i -H "Accept: application/json" 
 2    -H "Authorization: Bearer 65077f90e3baae8aa863" 
 3    http://localhost:8888/authors  4   5  HTTP/1.1 200 OK  6  Host: localhost:8888  7  Connection: close  8  X-Powered-By: PHP/7.0.15  9  Content-type: application/hal+json 10  11  { 12      "count": 6, 13      "_links": { 14  ... Rob Allen ~ @akrabat

Slide 31

Slide 31

Authorisation Code (for 3rd party apps) Rob Allen ~ @akrabat

Slide 32

Slide 32

Authorisation code Rob Allen ~ @akrabat

Slide 33

Slide 33

Authorisation code flow Rob Allen ~ @akrabat

Slide 34

Slide 34

Authorisation code flow Rob Allen ~ @akrabat

Slide 35

Slide 35

Authorisation code flow Rob Allen ~ @akrabat

Slide 36

Slide 36

Authorisation code flow Rob Allen ~ @akrabat

Slide 37

Slide 37

Authorisation code flow Rob Allen ~ @akrabat

Slide 38

Slide 38

Authorisation code flow Rob Allen ~ @akrabat

Slide 39

Slide 39

Authorisation code flow Rob Allen ~ @akrabat

Slide 40

Slide 40

Authorisation code flow Rob Allen ~ @akrabat

Slide 41

Slide 41

Authorisation code flow Rob Allen ~ @akrabat

Slide 42

Slide 42

Implementing in PHP Rob Allen ~ @akrabat

Slide 43

Slide 43

Required pieces 1. A website that talks to the Authorisation server 2. A new endpoint in the Authorisation server to provide auth codes Rob Allen ~ @akrabat

Slide 44

Slide 44

Process 1. 3rd party app sends user to our website: 2. User logs in to our website and authorises app 3. Our website gets code from our API 4. Our website redirects user back to app (or displays a code) Rob Allen ~ @akrabat

Slide 45

Slide 45

Add the grant  1  $container [ 'OAuth2Server' ]  

  function   ( $c )   {  2      // ...  3       $server  

  new   \OAuth2\Server ( $storage );  4   5       /* Add the password grant type */  6       $userCreds  

  new   UserCredentials ( $storage );  7       $server

addGrantType ( $userCreds );  8   9       return   $server ; 10  }; Rob Allen ~ @akrabat

Slide 46

Slide 46

Add the grant  1  $container [ 'OAuth2Server' ]  

  function   ( $c )   {  2      // ...  3       $server  

  new   \OAuth2\Server ( $storage );  4   5       /* Add the password grant type */  6       $userCreds  

  new   UserCredentials ( $storage );  7       $server

addGrantType ( $userCreds );  8   9       /* Add authorisation code grant type */ 10       $authCode  

  new   AuthorizationCode ( $storage ); 11       $server

addGrantType ( $authCode ); 12  13       return   $server ; 14  }; Rob Allen ~ @akrabat

Slide 47

Slide 47

Authorise Rob Allen ~ @akrabat

Slide 48

Slide 48

Remember this? Rob Allen ~ @akrabat

Slide 49

Slide 49

It's more like this… Rob Allen ~ @akrabat

Slide 50

Slide 50

Website sends to API Pressing Yes does this:  1  $data [ 'code' ]  

  'token' ;  2  $data [ 'client_id' ]  

  $_GET [ 'client_id' ];  3  $data [ 'redirect_uri' ]  

  $_GET [ 'redirect_uri' ];  4  $data [ 'state' ]  

  $_GET [ 'state' ];  5   6  $apiResponse  

  $guzzle

post ( '/authorise' ,   [  7     'json'  

  $data ,  8     'headers'  

  [  9       'Authorization'  

  'Bearer ' . $webAccessToken , 10     ] 11  ]); Rob Allen ~ @akrabat

Slide 51

Slide 51

API handles authorisation API's /authorise endpoint: 1  if   ( ! $server

validateAuthorizeRequest ( $req ,   $res ))   { 2       $srvResponse

send ();   exit ; 3  } 4  5  $server

handleAuthorizeRequest ( $req ,   $res ,   true ); 6  $srvResponse

send ();   exit ; Rob Allen ~ @akrabat

Slide 52

Slide 52

Website handles response  1  if   ( $apiResponse

getStatusCode ()   !=   302 )   {  2     throw   new   Exception ( "Failed to get code" );  3  }  4  $loc  

  $apiResponse

getHeaderL ( 'Location' );  5   6  if   ( $this

isValidUrl ( $loc ))   {  7     /* location is valid - redirect */  8     return   $response

withRedirect ( $loc );  9  } 10  11  /* invalid url - display the code to user */ 12  parse_str ( $parts [ 'query' ],   $queryParams ); 13  return   $renderer

renderPage ( $queryParams [ 'code' ]); Rob Allen ~ @akrabat

Slide 53

Slide 53

Authorised Rob Allen ~ @akrabat

Slide 54

Slide 54

Get token from code 1  $ curl -X "POST" http://localhost:8888/token 
2    -H "Accept: application/json" 
3    -H "Content-Type: application/json" 
4    -d $'{ 5     "grant_type": "authorization_code", 6     "client_id": "testclient", 7     "client_secret": "abcdef", 8     "code": "aee25eb86b2be8ca572d9f4031c57a3c5c52137c", 9    }' Rob Allen ~ @akrabat

Slide 55

Slide 55

Response  1  HTTP/1.1 200 OK  2  Host: localhost:8888  3  Connection: close  4  Content-Type: application/json  5   6  {  7    "access_token": "df7fcb455efb9a2c9544",  8    "expires_in": 3600,  9    "token_type": "Bearer", 10    "scope": null, 11    "refresh_token": "bb87ffbef191bdda55b1" 12  } Rob Allen ~ @akrabat

Slide 56

Slide 56

JWT bearer tokens Rob Allen ~ @akrabat

Slide 57

Slide 57

JWT • Cryptographically signed block of data • Potentially faster • A JWT consists of • Header • Payload • Signature Also: JWT is pronounced "jot" Rob Allen ~ @akrabat

Slide 58

Slide 58

Payload  1  {  2     "id" :   "394a71988caa6cc30601e43f5b6569d52cd7f" ,  3     "jti" :   "394a71988caa6cc30601e43f5b6569d52cd7f" ,  4     "iss" :   "{issuer_id}" ,  5     "aud" :   "{client_id}" ,  6     "sub" :   "{user_id}" ,  7     "exp" :   1483711650 ,  8     "iat" :   1483708050 ,  9     "token_type" :   "bearer" , 10     "scope" :   "read write delete" 11  } Rob Allen ~ @akrabat

Slide 59

Slide 59

Implementation 1. Update token creation to create JWT tokens 2. Update validation to check for JWT tokens Rob Allen ~ @akrabat

Slide 60

Slide 60

Previously 1  $container [ 'OAuth2Server' ]  

  function   ( $c )   { 2     $pdo  

  $c

get ( 'db' ); 3     $storage  

  new   PdoStorage ( $pdo ); 4  5     $server  

  new   \OAuth2\Server ( $storage ); 6  7    // ... add grants ... Rob Allen ~ @akrabat

Slide 61

Slide 61

Enable JWT 1  $container [ 'OAuth2Server' ]  

  function   ( $c )   { 2     $pdo  

  $c

get ( 'db' ); 3     $storage  

  new   PdoStorage ( $pdo ); 4  5     $server  

  new   \OAuth2\Server ( $storage ,   [ 6       'use_jwt_access_tokens'  

  true , 7     ]); 8  9    // ... add grants ... Rob Allen ~ @akrabat

Slide 62

Slide 62

Get a token  1  $ curl -i -X POST http://localhost:8888/token 
 2    -H "Accept: application/json" 
 3    -H "Content-Type: application/json" 
 4    -d $'{  5        "grant_type": "password"  6        "client_id": "mywebsite",  7        "client_secret": "abcdef",  8        "username": "rob",  9        "password": "123456" 10    }' Rob Allen ~ @akrabat

Slide 63

Slide 63

Response  1  HTTP/1.1 200 OK  2  Host: localhost:8888  3  Connection: close  4  Content-Type: application/json  5   6  {  7    "access_token": "eyJ0eXAiOi...BLUWlojjm24HmNbOMg",  8    "expires_in": 3600,  9    "token_type": "Bearer", 10    "scope": null, 11    "refresh_token": "be071d2c6193d32a353d" 12  } Rob Allen ~ @akrabat

Slide 64

Slide 64

Validation Use an in-memory OAuth2 Server: 1  $storage  

  new   OAuth2\Storage\Memory ([ 2     'keys'  

  [ 3       'public_key'  

  $publicKey , 4     ] 5  ]); 6  7  $server  

  new   OAuth2\Server ( $storage ,   [ 8       'use_jwt_access_tokens'  

  true , 9  ]); Rob Allen ~ @akrabat

Slide 65

Slide 65

Validation The validation code doesn't change 1  /* test for valid Auth header */ 2  $req  

  \OAuth2\Request :: createFromGlobals (); 3  if   ( ! $server

verifyResourceRequest ( $req ))   { 4     /* not valid / 5  } 6  7  / get information */ 8  $token  

  $server

getAccessTokenData ( $req ); 9  $username  

  $token [ 'user_id' ]; Rob Allen ~ @akrabat

Slide 66

Slide 66

Refresh tokens Rob Allen ~ @akrabat

Slide 67

Slide 67

Refresh tokens • Access tokens expire quickly • Use the refresh token to get a new access token • Guard refresh tokens! 1  $ curl -i -X POST http://localhost:8888/token 
2    -H "Accept: application/json" 
3    -H "Content-Type: application/json" 
4    -d $'{ 5        "grant_type": "refresh_token" 6        "client_id": "testclient", 7        "client_secret": "abcdef", 8        "refresh_token": "be071d2c6193d32a353d" 9    }' Rob Allen ~ @akrabat

Slide 68

Slide 68

Response  1  HTTP/1.1 200 OK  2  Host: localhost:8888  3  Connection: close  4  Content-Type: application/json  5   6  {  7    "access_token": "eyJ0eXAiOi...tjD8whWBt8h4oRluOMA",  8    "expires_in": 3600,  9    "token_type": "Bearer", 10    "scope": null 11  } Rob Allen ~ @akrabat

Slide 69

Slide 69

Summary Rob Allen ~ @akrabat

Slide 70

Slide 70

Summary • Authorization header contains token • Two actors • Client (id & secret) • User (username & password) • Grants: • Password: 1st party apps • Authorisation code: 3rd party apps • JWT for speed and scale Rob Allen ~ @akrabat

Slide 71

Slide 71

Resources This talk: • https://github.com/akrabat/slim-bookshelf-api • https://akrabat.com/talks/#oauth2 Around the web: • https://oauth.net/2/ • http://bshaffer.github.io/oauth2-server-php-docs • https://aaronparecki.com/oauth-2-simplified/ Rob Allen ~ @akrabat

Slide 72

Slide 72

Questions?   Feedback: https://joind.in/talk/ce818       Rob Allen ~ @akrabat

Slide 73

Slide 73

Thank you!   Feedback: https://joind.in/talk/ce818       Rob Allen ~ @akrabat