Getting Started with OAuth 2.0 - MatthewJacques/Wiki GitHub Wiki
Grant Types
Authorization Code
- Designed for "confidential clients"
- Best for websites with a server back end
- Explicit user & client authentication
Authorization Request
First step is to get the authorization code, which is normally a request to something like: https://authserver.example.com/authorize
.
There are some query parameters that identify the confidential client we are normally something like
response_type=code
- Lets the authorization server know the client wants to use the authorization code flow
client_id=s6BhdRkqt3
- Unique ID to identify the requesting client
redirect_uri=https://client.example.com/callback
- Where the client will be redirected once finished interacting with resource owner
state=xyz
- Recommended, will be echoed back, can be used to make sure the response was meant for us, should not be guessable
scope=api1 api2.read
- Optional space limited string to explicitly request what permissions the client wants to do on behalf of the user. There is a default to fallback on.
Authorization Response
The authorization response will be a redirect to the redirect_uri given in the request with query parameters that contain
code=Sp1x10BeZQQYbYS6WxSbIA
- Physical representation that the user has given their consent
state=xyz
- Must match the state given in the request otherwise you should probably ignore the response. Can also be used for round trip data
Token Request
A token request will be a POST request that should look like the following
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW (Client authentication, such as from client_id and client_secret)
body:
grant_type=authoization_code
code=Sp1x10BeZQQYbYS6WxSbIA
redirect_uri=https://client.example.com/callback (Must be redirect_uri from initial authorization request)
client_id=s6BhdRkqt3 (Can be in body if not using Authorization header)
client_secret=gX1fBat3bv (Can be in body if not using Authorization header)
Basic Authentication & OAuth
OAuth Style(RFC 6749)
Base64(urlformencode(client_id) + ':' + urlformencode(client_secret))
If you are receiving a 401 response and can't understand why, some authentication servers do not use the OAuth Style authentication and still use the Basic Authentication Style
Base64(client_id + ':' client_secret)
Token Response
If all is ok a 200 response with some JSON that will look like the following
HTTP/1.1 200 OK
Content-Type: application/json
body:
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 3600 (in seconds)
"scope": "api2.read" (optional if user consented to all requests scopes, if only partial consent that scope will be included)
}
Implicit
- Designed for "public clients" that cannot keep a secret due to no backend server etc
- Best for clients accessing resources directly from the browser
- No explicit client authentication
Authorization Request
The URI for the authorization request will generally be something like https://authserver.example.com/authorize
.
There are some query parameters that identify the confidential client we are normally something like
response_type=token
- Lets the authorization server know the client wants to use implicit flow
client_id=s6BhdRkqt3
- Unique ID to identify the requesting client
redirect_uri=https://client.example.com/callback
- Where the client will be redirected once finished interacting with resource owner
state=xyz
- Recommended, will be echoed back, can be used to make sure the response was meant for us, should not be guessable
scope=api1 api2.read
- Optional space limited string to explicitly request what permissions the client wants to do on behalf of the user. There is a default to fallback on.
Authorization Response
The authorization response will be a redirect to the redirect_uri given in the request with a url fragment that contains
access_token=2YotnFZFEjr1zCsicMWpAA
- Access token that can be used to authorize further requests
token_type=example
expires_in=3600
- How long the token lasts in seconds
state=xyz
- Must match the state given in the request otherwise you should probably ignore the response. Can also be used for round trip data
Security Concerns
- Access tokens are exposed to resource owner
- Access tokens accessible to third-party JavaScript
- No validation that access tokens are intended for client
Client Credentials
- Designed for client applications who are the resource owner
- Best for machine-to-machine communication
- Requires client authentication
Token Request
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-url-encoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
body:
grant_type=client_credentials
scope=api1 api2.read
Token Response
HTTP/1.1 200 OK
Content-Type: application/json
body:
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 3600 (in seconds)
"scope": "api2.read" (optional if user consented to all requests scopes, if only partial consent that scope will be included)
}
Differences with API Keys/HTTP Basic Authentication
- Not sending the credentials with every request
- Access tokens
- Timed access without manual input
Refresh Tokens
- Can be swapped for new access tokens
- Allows for long-lived access
- Highly confidential
- User should be informed that refresh tokens are being requested
Authorization Request
In the scope of the authorization request, offline_access
should be added to allow for a refresh token to be returned. The user should be asked for consent every time a refresh token is requested, even if they have consented in the past.
Token Response
HTTP/1.1 200 OK
Content-Type: application/json
body:
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 3600, (in seconds)
"refresh_token": "tGzv3JOkF0XG5Qx2T1KWIA",
"scope": "api2.read offline_access" (optional if user consented to all requests scopes, if only partial consent that scope will be included)
}
Refresh Token Request
POST /token HTTP/1.1
Host:: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
body:
grant_type=refresh_token
refresh_token=tGzv3JOkF0XG5Qx2T1KWIA
scope=api1
Refresh Token Response
HTTP/1.1 200 OK
Content-Type: application/json
body:
"access_token": "5BaFFasdfrje2aCsicoiJefn",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": 9oih321s0i2xnoutnsglr",
"scope": "api2.read offline_access"
Best Practices for Native Applications
Native applications are classed as public clients as they run on desktop or mobile. The implicit flow should not be used for native applications. The tokens will be visible to every other application on that device. Authorization Code with PKCE should be used for Native Applications. Private URI schemes can also be used to try and protect the redirect uris.
PKCE
PKCE is method of making sure the client requesting a token is the same client that requested the authorization code. This is done by the client generating a random code verifier and transforming it into a code challenge. This code challenge is then send with the authorization request. When the client then requests a token, the code verifier is sent with this request which the authorization server can check the transformation to see if the code challenge and code verifier match.
Best Practices for Browser-Based Applications
Browser-based applications do not have any secure storage and due to the developer tools built in to the browser any code or network traffic can be inspected to see if there are any secrets. This is why Authorization Code with PKCE is recommended.
SameSite Cookies
SameSite cookies can be used instead of OAuth, this is due to the requests only being able to be sent from the same site for example github.com -> github.com/api g1thub.com x github.com/api
Backend for Frontend
The previous two approaches can be combined to create a backend for frontend approach. This can be useful when there are many different apps on different domains. This can be achieved by using SameSite cookies to access a single API which can handle the authorization code flow. Using this approach, access tokens can be stored in the middle-man API, making them more secure.
OpenID Connect
OpenID Connect is an identity layer on top of OAuth2.0 which formalizes some OAuth ambiguity. The Authorization server becomes an Identity Provider.
Identity Access
When using OpenId Connect there is another protected resource within in the system which is an API typically within the Identity Provider called the UserInfo endpoint. When this endpoint is called using an access token obtained by using OpenID Connect, the claims about the delegated user are returned.
Identity Token
This is a new type of token that is returned via the authorization and requests. While an access token provides access to a protected resource an identity token describes the authentication event itself. An identity token is always a JWT.
Payload
{
"nbf": 1535215761 (when token should not be used before)
"exp": 1535216061 (when the token expires)
"iat": 1535215761 (when it was issued)
"auth_time": 1535215753 (when the end user authenticated)
"iss": "http://localhost:5000" (who issued the token)
"aud": "oidc_client" (the intended audience)
"nonce": "..." (similar to state from access token, verifiable due to digital signature)
"at_hash": hash that could contain things like a access token if issued at the same time)
"sub": "..." (unique identifier of the user)
"idp": "local" (identity provider that issued the identity)
"amr": [ "pwd" ] (authentication method reference, how user authenticated)
}
Hybrid Flow
OpenID Connect allows the request of a identity token at the same time of requesting an authorization code.