Getting Started with OAuth 2.0 - MatthewJacques/Wiki 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.