Joshua's Cheatsheets
Light
help

OAuth (OAuth 2.0) - Cheatsheet

Elevator pitch:

OAuth is an agreed-upon standard around securing API usage, that is a (scoped) access token exchange based scheme, as an alternative to hardcoded passwords, or auth tokens that are scoped to the entire account.

A key differentiator of OAuth is that it is a form of delegated access - you are accessing protected resources that a user has granted you limited access to, as opposed to accessing them as the user.


Image Overview:

Image Overview From InterSystems Overview

Image Overview From Digital Ocean Overview


Resources:

What Type Link
Short video overview Video Link
Digital Ocean Intro (excellent overview) Tutorial / Post Link
Testing playground Interactive Link

Steps to OAuth (2.0)

App registration

This is usually done far in advance of the main auth flow, so I wouldn't really consider it part of the main steps. It is more a pre-requisite to using OAuth.

  1. Register your application with the API / service:

    • By providing...

      • Application Name
      • Application Website
      • Redirect URI / Callback URL (more on this later)
    • in order to obtain...

      • Client ID (Public)
      • Client Secret (Private)

Main Auth Flow: (assuming Auth Code)

Summary:

  1. Authorization Request (Outgoing)
  2. Authorization Grant (Incoming)
  3. Token Exchange / Request Access Token (Outgoing)
  4. Receive Access Token (Incoming)
  5. Use the token! (Outgoing, repeatable)

Detailed:

  1. Authorization Request

    • WHAT:

      • Your app asks the user to authorize it, and grant scoped access to parts of their account
    • HOW

      • Your app should construct a URL that is the API/service endpoint + ...

        • client_id

          • Previously obtained
        • redirect_uri

          • Where should the service send the user back to, along with the auth code, if the user approves
          • This is also called a callback url
          • example.com/authCallback.php
        • scope*

          • String that represents what you want the user to authorize your app to access
          • For Google, this might be profile and email
        • response_type**

          • Corresponds with the grant type
        • state**
      • * = unique per API
      • ** = sometimes optional, and unique per API

        • response_type is often assumed based on endpoint, or maybe the API only supports one type of grant
        • state is a randomized string that can serve multiple purposes:

          • Use a session identifier (instead of Cookie)
          • Protection against CSRF (if you make sure to check that the callback state === request state)
      • You then redirect the user to this constructed URL, in the same tab / webview / etc.

        • You wouldn't normally open in new tab or try AJAX, since new tab would require polling, and AJAX doesn't make sense since they have to login and approve your app
    • EXAMPLES

      • Example: Ask Google user for read-only access to see basic account identity stuff (name, email, ID) to prove identity

      • Example: Ask Twitter user for "Read and Write" access to account, so you can build an App that automates scheduled posting.
      • Example: Ask Github user for access to notifications scope, so you can relay notifications from Github to Slack
    • CODE

      • https://github.com/login/oauth/authorize&client_id={{CLIENT_ID}}&redirect_uri={{https://example.com/authCallback.php}}&scope={{notifications}}&state={{asld!f93Cb20}}
  2. Authorization grant (or deny) --- INCOMING - receiving the response

    • WHAT

      • After the user either approves or denies your request for granted access (see: The Authorization Grant Approval Screen), the user will be redirected to the redirect URI that you specified, with information from the third-party service appended as a querystring.
    • HOW

      • This should happen automatically after the user clicks either the approve or deny button on the approval screen.
    • EXAMPLES

      • User approves request and is redirected to our callback page at https://example.com/authCallback.php?code={{AUTH_CODE}}&scope={{notifications}}&state={{asld!f93Cb20}}
      • User denies request and is redirected to our callback page at https://example.com/authCallback.php?error=access_denied&state={{asld!f93Cb20}}
    • CODE

      • Grabbing querystrings param is pretty easy regardless of language... quick examples:
      • PHP:

        $authCode = $_GET['code'];
      • JS:

        var authCode = document.location.search.indexOf('code=') > 0 ? (/code=([^&]+)/.exec(document.location.search)[1]) : null;
  3. Token exchange / Access token request --- OUTGOING

    • WHAT

      • In this step, we are taking the auth code that we just received via GET query parameters, and using it as part of a GET or POST request, to exchange the granted auth code for a access token. The access token is the meat of OAUTH and what will be used all by future API requests by our app.
      • You might be wondering why this step is necessary, and why couldn't step 2 simply return the access token instead of an auth code? The short answer is security - see my sidenote
    • HOW

      • We will take the auth code we just received, bundle it with our client ID and Client Secret, and make a request to the endpoint of the API to get an access token
    • EXAMPLES

      • For GitHub, you POST to https://github.com/login/oauth/access_token, which returns a token that should be valid indefinitely, until the user revokes the app
    • CODE

      • Since this is using a client_secret, any of these examples should only be running server-side
      • PHP: gist
  4. Receive Access Token / Access Token grant --- INCOMING

    • WHAT

      • In response to the GET or POST request in step 3, in which we sent our temporary auth code, client_secret, and client_id, we should get back a response that includes the final access_token. It might also include extra information, like when the token expires, the granted scope, token_type, etc.
    • HOW

      • Usually APIs return this response as json, so grabbing the final access_token should only take a line or two of code, given how almost every programming language has a JSON parser either natively or through a common lib.
      • As a dev, you will want to persist this token, usually in a DB, session, or some other storage, since tokens usually don't expire for a long time, but browser sessions do. If you don't persist the token and instead keep it with your user's session, that would mean that every time they log out and then back into your app, they would have to re-authorize OAuth as well!
    • EXAMPLES

      • The GitHub API currently returns access_token, scope (comma separated), and token_type ("bearer").
    • CODE

      • Should be self-explanatory
  5. Use the token!

    • WHAT

      • Now, every time we need to make an API call to our third-party service, we will need to include the access_token in some form or another
    • HOW

      • This depends on the API, but usually the token is included inside of a special HTTP header field - the 'Authorization' header.

        • Authorization: <type> <credentials>
      • Sometimes an API will allow you to include the token as a querystring instead of a header, but this is not advisable due to security
    • EXAMPLES

      • For GitHub, you can include the token either as a header or a GET query param. And for GH, the name of the auth type is "token" for the HTTP header
    • CODE

      • The generic auth header looks like this:

        Authorization: <type> <credentials>
      • Here is a GitHub request, using cURL:

        curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com

        Some Other Grant Types

        Grant Type Diff compared to authorization code Why use it
        implicit Instead of asking for a code and then exchanging for a token, the API returns a token immediately. Basically step 3 is skipped. Useful for apps with no backend (e.g. a SPA served via static files), since no client_secret is ever exchanged (which would be impossible to keep safe on a front-end only app). The downside is that the access_token is completely exposed to client side code.
        password The user's unencrypted username and password are sent in a POST request, which returns the access_token. Essentially steps 1-3 are skipped. INCREDIBLY insecure, for multiple reasons, including you would have to store username & pass in plaintext in order to re-authenticate when necessary. Not many good reasons to use this, except maybe converting legacy apps.
        client Similar to password grant, except instead of user's username & pass, you send your app's client_id and client_secret (analogous to user & pass) in POST and get back token. This is useful when an API, or a subset of the API, is not scoped to individual users, but rather to clients. For example, an API might restrict WRITE endpoints to using the authorization code grant type, but an endpoint that returns the operational status of the entire platform might be restricted with client, since everyone sees the same data.

The Authorization Grant Approval Screen

  • WHAT

    • This part of the flow is beyond your control as the dev, but I'm including it since it is a crucial part of the process. This is the screen that the user sees, if they are logged into the third party service and asked to authorize your app.
  • EXAMPLES

Some security considerations

Why can't the Access Token be returned directly when user approves access?

First, it can be - that is actually how the implicit grant type works, without using client_secret.

However, back to the question of why this is not used with the authorization code grant type, which is the more common protocol. I'm going to oversimply here, but the basic reason seems to be due to the high-visibility of URLs in GET requests and how they are logged. For example, any Javascript running on a webpage can read and store the current URL of the page, including the query string. If the OAuth protocol allowed for access tokens to be returned to the redirect_uri via a query param, such as callback.php?accesstoken=123ABC, then a third party malicious script might grab it, and later use it to get access to our user's accounts.

The current flow, receiving the auth code in GET and then exchanging it for a token via GET/POST as a secondary request is much better, for several reasons.

  • The exchange to receive the token is secured in two ways

    • It should originate server-side, so client-side scripts, such as Google Analytics or malicious third-party JS scripts, will not see the request and log it.
    • Some API's allow for this to be a POST request, which is a small further guard against MITM attacks, since the important params like client_secret should be in the body of the request, not the URL, and therefore encrypted with SSL.
  • Although the auth code is visible as a query param, typically it expires within minutes of being granted. Even if a third party grabbed it within that short time span, they would need to know your client-secret in order to exchange it for a long-lived access token.
Markdown Source Last Updated:
Fri Sep 27 2019 16:44:30 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Tue Aug 27 2019 07:54:31 GMT+0000 (Coordinated Universal Time)