Don't Fear the OAuth
AustinPHP + Austin Linux Meetup
January 2020
follow along at https://ian.im/oauthatx20
We're talking about OAuth 2 here
OAuth 1.x is deprecated
How does this work?
Let's see where it redirects to
Let's See Where it redirects to
https://accounts.google.com/o/oauth2/auth?access_type=offline&
client_id=835284083712.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fslides.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&
response_type=code&
scope=email+profile&
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa
Let's See Where it redirects to
https://accounts.google.com/o/oauth2/auth?access_type=offline&
client_id=835284083712.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fslides.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&
response_type=code&
scope=email+profile&
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa
Auth Code Grant
- Operating on behalf of a user
- User can log in on the same device
- Requesting service doesn't have user credentials
- Requesting service may or may not be client-side
- Requesting service may or may not be able to keep secrets secret
Let's See Where it redirects to
https://accounts.google.com/o/oauth2/auth?access_type=offline&
client_id=835284083712.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fslides.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&
response_type=code&
scope=email+profile&
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa
Different behaviors for different clients
- Confidential vs. public
- Allowed grant types
- User authorization for a given app
- Preventing auth code hijacking
- Revocation
Let's See Where it redirects to
https://accounts.google.com/o/oauth2/auth?access_type=offline&
client_id=835284083712.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fslides.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&
response_type=code&
scope=email+profile&
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa
Let's See Where it redirects to
https://accounts.google.com/o/oauth2/auth?access_type=offline&
client_id=835284083712.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fslides.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&
response_type=code&
scope=email+profile&
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa
Let's See Where it redirects to
https://accounts.google.com/o/oauth2/auth?access_type=offline&
client_id=835284083712.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fslides.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&
response_type=code&
scope=email+profile&
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa
Let's See Where it redirects to
https://accounts.google.com/o/oauth2/auth?access_type=offline&
client_id=835284083712.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fslides.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&
response_type=code&
scope=email+profile&
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa
Scopes
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/drive.appdata
https://www.googleapis.com/auth/drive.file
https://www.googleapis.com/auth/drive.metadata
https://www.googleapis.com/auth/drive.metadata.readonly
https://www.googleapis.com/auth/drive.photos.readonly
https://www.googleapis.com/auth/drive.readonly
https://www.googleapis.com/auth/drive.scripts
You probably don't want scopes
that map 1:1 to user roles
Let's authenticate and see
where we get redirected...
We're redirected back to...
https://slides.com/users/auth/google_oauth2/callback?
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa&
code=4%2FvQFTLSQfFlG3jAzgmX2Dg...&
scope=email+profile+openid+...&
authuser=0&
session_state=92238ad3186b0d73713263061d6f5a0aeddbf844..78ff&
prompt=none
We're redirected back to...
https://slides.com/users/auth/google_oauth2/callback?
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa&
code=4%2FvQFTLSQfFlG3jAzgmX2Dg...&
scope=email+profile+openid+...&
authuser=0&
session_state=92238ad3186b0d73713263061d6f5a0aeddbf844..78ff&
prompt=none
We're redirected back to...
https://slides.com/users/auth/google_oauth2/callback?
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa&
code=4%2FvQFTLSQfFlG3jAzgmX2Dg...&
scope=email+profile+openid+...&
authuser=0&
session_state=92238ad3186b0d73713263061d6f5a0aeddbf844..78ff&
prompt=none
We're redirected back to...
https://slides.com/users/auth/google_oauth2/callback?
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa&
code=4%2FvQFTLSQfFlG3jAzgmX2Dg...&
scope=email+profile+openid+...&
authuser=0&
session_state=92238ad3186b0d73713263061d6f5a0aeddbf844..78ff&
prompt=none
We're redirected back to...
https://slides.com/users/auth/google_oauth2/callback?
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa&
code=4%2FvQFTLSQfFlG3jAzgmX2Dg...&
scope=email+profile+openid+...&
authuser=0&
session_state=92238ad3186b0d73713263061d6f5a0aeddbf844..78ff&
prompt=none
Server-side time
POST https://www.googleapis.com/oauth2/v4/token
client_id=835284083712.apps.googleusercontent.com&
client_secret=ifIhadThisIcouldImpersonateSlidesDotCom&
redirect_uri=https%3A%2F%2Fslides.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&
grant_type=authorization_code&
code=4%2FvQFTLSQfFlG3jAzgmX2Dg...
Server-side time: Response
{ "token_type": "Bearer", "expires_in": 3600, "access_token": "xtnYoXsKMQSAR4kA0RlV4SxDERpcVTtYi...", "refresh_token": "o62ibFrTJmea3gTivWOih54Anujelg5A...", "id_token": "JZzEAMTgCcK3vPBkzby.KzwcoAd3Cji.TgK3vP..." }
Server-side time: Response
{ "token_type": "Bearer", "expires_in": 3600, "access_token": "xtnYoXsKMQSAR4kA0RlV4SxDERpcVTtYi...", "refresh_token": "o62ibFrTJmea3gTivWOih54Anujelg5A...", "id_token": "JZzEAMTgCcK3vPBkzby.KzwcoAd3Cji.TgK3vP..." }
Server-side time: Response
{ "token_type": "Bearer", "expires_in": 3600, "access_token": "xtnYoXsKMQSAR4kA0RlV4SxDERpcVTtYi...", "refresh_token": "o62ibFrTJmea3gTivWOih54Anujelg5A...", "id_token": "JZzEAMTgCcK3vPBkzby.KzwcoAd3Cji.TgK3vP..." }
Refreshing access
POST https://www.googleapis.com/oauth2/v4/token
client_id=835284083712.apps.googleusercontent.com&
client_secret=ifIhadThisIcouldImpersonateSlidesDotCom&
grant_type=refresh_token&
refresh_token=o62ibFrTJmea3gTivWOih54Anujelg5A...
Server-side time: Response
{ "token_type": "Bearer", "expires_in": 3600, "access_token": "xtnYoXsKMQSAR4kA0RlV4SxDERpcVTtYi...", "refresh_token": "o62ibFrTJmea3gTivWOih54Anujelg5A...", "id_token": "JZzEAMTgCcK3vPBkzby.KzwcoAd3Cji.TgK3vP..." }
This is a JWT
- base64url(header). '.' . base64url(payload) . '.' . signature
- Header: {"alg": "RS256", "typ": "JWT"}
- Signature: sign(base64url(header) . '.' . base64url(payload))
JWT PAYLOAD FOR GOOGLE'S OPENID CONNECT IMPL
{ "iss": "https://accounts.google.com", "azp": "1234987819200.apps.googleusercontent.com", "aud": "1234987819200.apps.googleusercontent.com", "sub": "10769150350006150715113082367", "at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q", "hd": "example.com", "email": "jsmith@example.com", "email_verified": "true", "iat": 1353601026, "exp": 1353604926, "nonce": "0394852-3190485-2490358" }
JWT Payload for Google's OpenID Connect Impl
{ "iss": "https://accounts.google.com", "azp": "1234987819200.apps.googleusercontent.com", "aud": "1234987819200.apps.googleusercontent.com", "sub": "10769150350006150715113082367", "at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q", "hd": "example.com", "email": "jsmith@example.com", "email_verified": "true", "iat": 1353601026, "exp": 1353604926, "nonce": "0394852-3190485-2490358" }
OIDC Bonus: Discoverability
But What about my Single Page App?
- Operating on behalf of a user
- User can log in on the same device
- Requesting service doesn't have user credentials
- Requesting service is client-side
- Requesting service is not able to keep secrets secret
The old way: implicit grant
The old way: implicit grant
https://my-auth-provider.com&
client_id=a123b123&
redirect_uri=https%3A%2F%2Fspa.mysite.com%2Fauth&
response_type=token&
scope=email+profile&
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa
...which redirects you to...
https://spa.mysite.com/auth#
access_token=xtnYoXsKMQSAR4kA0RlV4SxDERpcVTtYi...&
token_type=Bearer&
expires_id=3600
The new way: PKCE
https://my-auth-provider.com&
client_id=a123b123&
redirect_uri=https%3A%2F%2Fspa.mysite.com%2Fauth&
response_type=code&
code_challenge=XsgVoUAhKm9ZIDP3N53P2crEiz2X3KszLhIPKN...&
code_challenge_method=S256
scope=email+profile&
state=214c1e90730143beca60feb6e9da0807fa68f7be82ef34fa
PKCE Code redemption
POST https://my-auth-provider.com/token
client_id=a123b123&
redirect_uri=https%3A%2F%2Fspa.mysite.com%2Fauth&
grant_type=authorization_code&
code=4%2FvQFTLSQfFlG3jAzgmX2Dg...&
code_verifier=ovMQw176WZXkkm3uFQ4PtwKV...
- Operating on behalf of a user
- User can log in on the same device
- Requesting service can obtain user's credentials
- Requesting service may or may not be client side
- Requesting service may or may not be able to keep secrets secret
- Caveat: Doesn't support special auth flows (e.g. 2FA) inline
Trusted Clients: Password Grant
Password Grant
POST https://my-auth-provider.com/token
client_id=a123b123&
client_secret=ovMQw176WZXkkm3uFQ4PtwKV...&
redirect_uri=https%3A%2F%2Fspa.mysite.com%2Fauth&
grant_type=password&
username=email@my.site&
password=hunter2
App-level access: Client Credentials Grant
- Operating on behalf of an application, not a user
- Requesting service is server-side
- Requesting service is able to keep secrets secret
Client Credentials Grant
POST https://my-auth-provider.com/token
client_id=a123b123&
client_secret=ovMQw176WZXkkm3uFQ4PtwKV...&
redirect_uri=https%3A%2F%2Fspa.mysite.com%2Fauth&
grant_type=client_credentials
assisted access: Device authorization Grant
- Operating on behalf of a user
- User cannot log in on the same device
- Requesting service is client side
- Requesting service may or may not be able to keep secrets secret
- Client is able to poll for an access token
assisted access: Device authorization Grant
- Device asks auth server for a code, including its client_id
- Auth server hands back a device code, user code, and verification URL
- Device tells user to go to the verification URL and enter the user code
- Device polls with its client ID and device code until one of the below:
- User authorizes the device (device gets access/refresh tokens)
- User declines authorization
- Device code expires
What does it look like to serve this?
What does a full implementation look like?
Thanks! Questions?
- ian.im/oauthatx20 - these slides
- oauth.net - Easier to digest than RFCs
- jwt.io - More information on JWTs
- oauth2.thephpleague.com - PHP OAuth 2 Server Library
- laravel.com/docs/6.x/passport - Laravel Passport
Don't Fear the OAuth - AustinPHP January 2020
By Ian Littman
Don't Fear the OAuth - AustinPHP January 2020
- 1,444