Protect Your API with OAuth 2 Slides: http://akrab.at/2nin0yI Rob Allen ~ April 2017 ~ @akrabat
A presentation at PHP Yorkshire in April 2017 in York, UK by Rob Allen
Protect Your API with OAuth 2 Slides: http://akrab.at/2nin0yI Rob Allen ~ April 2017 ~ @akrabat
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
QWxhZGRpbjpPcGVuU2VzYW1l Rob Allen ~ @akrabat
Problems • All clients have to know user's credentials • Credentials are passed in every request Rob Allen ~ @akrabat
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
Roles • The user (Resource Owner) • The (third-party) application (Client) • The API (Resource Server) • The Authorisation server Rob Allen ~ @akrabat
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
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
Password grant (for 1st party apps) Rob Allen ~ @akrabat
Password grant Rob Allen ~ @akrabat
Password flow Rob Allen ~ @akrabat
Password flow Rob Allen ~ @akrabat
Password flow Rob Allen ~ @akrabat
Password flow Rob Allen ~ @akrabat
Implementing in PHP Rob Allen ~ @akrabat
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
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
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
new \OAuth2\Server ( $storage ); Rob Allen ~ @akrabat
addGrantType ( $userCreds ); 13 14 return $server ; 15 }; Rob Allen ~ @akrabat
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
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
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
send (); 8 exit ; 9 } 10 ); Rob Allen ~ @akrabat
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
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
Protecting your API endpoints Rob Allen ~ @akrabat
$token [ 'user_id' ]; Rob Allen ~ @akrabat
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
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
Authorisation Code (for 3rd party apps) Rob Allen ~ @akrabat
Authorisation code Rob Allen ~ @akrabat
Authorisation code flow Rob Allen ~ @akrabat
Authorisation code flow Rob Allen ~ @akrabat
Authorisation code flow Rob Allen ~ @akrabat
Authorisation code flow Rob Allen ~ @akrabat
Authorisation code flow Rob Allen ~ @akrabat
Authorisation code flow Rob Allen ~ @akrabat
Authorisation code flow Rob Allen ~ @akrabat
Authorisation code flow Rob Allen ~ @akrabat
Authorisation code flow Rob Allen ~ @akrabat
Implementing in PHP Rob Allen ~ @akrabat
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
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
addGrantType ( $userCreds ); 8 9 return $server ; 10 }; Rob Allen ~ @akrabat
addGrantType ( $authCode ); 12 13 return $server ; 14 }; Rob Allen ~ @akrabat
Authorise Rob Allen ~ @akrabat
Remember this? Rob Allen ~ @akrabat
It's more like this… Rob Allen ~ @akrabat
'Bearer ' . $webAccessToken , 10 ] 11 ]); Rob Allen ~ @akrabat
send (); exit ; Rob Allen ~ @akrabat
renderPage ( $queryParams [ 'code' ]); Rob Allen ~ @akrabat
Authorised Rob Allen ~ @akrabat
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
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
JWT bearer tokens Rob Allen ~ @akrabat
JWT • Cryptographically signed block of data • Potentially faster • A JWT consists of • Header • Payload • Signature Also: JWT is pronounced "jot" Rob Allen ~ @akrabat
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
Implementation 1. Update token creation to create JWT tokens 2. Update validation to check for JWT tokens Rob Allen ~ @akrabat
new \OAuth2\Server ( $storage ); 6 7 // ... add grants ... Rob Allen ~ @akrabat
true , 7 ]); 8 9 // ... add grants ... Rob Allen ~ @akrabat
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
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
true , 9 ]); Rob Allen ~ @akrabat
$token [ 'user_id' ]; Rob Allen ~ @akrabat
Refresh tokens Rob Allen ~ @akrabat
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
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
Summary Rob Allen ~ @akrabat
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
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
Questions? Feedback: https://joind.in/talk/ce818 Rob Allen ~ @akrabat
Thank you! Feedback: https://joind.in/talk/ce818 Rob Allen ~ @akrabat