Server-Side Apps

3.1

Server-side apps are the most common type of application encountered when dealing with OAuth 2 servers. These apps run on a web server where the source code of the application is not available to the public, so they can maintain the confidentiality of their client secret.

The diagram below illustrates a typical example where the user interacts with their browser which is communicating with the client. The client and the API server have a separate secure communications channel between them. The user’s browser never makes a request directly to the API server, everything goes through the client first.

The app’s server communicates with the API

Server-side apps use the authorization_code grant type. In this flow, after the user authorizes the application, the application receives an “authorization code” which it can then exchange for an access token.

Authorization Code Grant

The authorization code is a temporary code that the client will exchange for an access token. The code itself is obtained from the authorization server where the user gets a chance to see what the information the client is requesting, and approve or deny the request.

The authorization code offers a few benefits over the other grant types. When the user authorizes the application, they are redirected back to the application with a temporary code in the URL. The application exchanges that code for the access token. When the application makes the request for the access token, that request is authenticated with the client secret, which reduces the risk of an attacker intercepting the authorization code and using it themselves. This also means the access token is never visible to the user, so it is the most secure way to pass the token back to the application, reducing the risk of the token leaking to someone else.

The first step of the web flow is to request authorization from the user. This is accomplished by creating an authorization request link for the user to click on.

The authorization URL is usually in a format such as:

https://authorization-server.com/oauth/authorize
?client_id=a17c21ed
&response_type=code
&state=5ca75bd30
&redirect_uri=https%3A%2F%2Foauth2client.com%2Fauth

The exact URL endpoint will be specified by the service to which you are connecting, but the parameter names will always be the same.

Note that you will most likely first need to register your redirect URL at the service before it will be accepted. This also means you can’t change your redirect URL per request. Instead, you can use the state parameter to customize the request. See below for more information.

After the user visits the authorization page, the service shows the user an explanation of the request, including application name, scope, etc. (See “approves the request” below for an example screenshot.) If the user clicks “approve”, the server will redirect back to the app, with a “code” and the same “state” parameter you provided in the query string parameter. It is important to note that this is not an access token. The only thing you can do with the authorization code is to make a request to get an access token.

Authorization Grant Parameters

The following parameters are used to make the authorization grant. You should build a query string with the below parameters, appending that to the application’s authorization endpoint obtained from its documentation.

client_id

The client_id is the identifier for your app. You will have received a client_id when first registering your app with the service.

response_type

response_type is set to code indicating that you want an authorization code as the response.

redirect_uri (optional)

The redirect_uri may be optional depending on the API, but is highly recommended. This is the URL to which you want the user to be redirected after the authorization is complete. This must match the redirect URL that you have previously registered with the service.

scope (optional)

Include one or more scope values (space-separated) to request additional levels of access. The values will depend on the particular service.

state (recommended)

The state parameter serves two functions. When the user is redirected back to your app, whatever value you include as the state will also be included in the redirect. This gives your app a chance to persist data between the user being directed to the authorization server and back again, such as using the state parameter as a session key. This may be used to indicate what action in the app to perform after authorization is complete, for example, indicating which of your app’s pages to redirect to after authorization. This also serves as a CSRF protection mechanism. When the user is redirected back to your app, double check that the state value matches what you set it to originally. This will ensure an attacker can’t intercept the authorization flow.

Combine all of these query string parameters into the login URL, and direct the user’s browser there. Typically apps will put these parameters into a login button, or will send this URL as an HTTP redirect from the app’s own login URL.

The user approves the request

After the user is taken to the service and sees the request, they will either allow or deny the request. If they allow the request, they will be redirected back to the redirect URL specified along with an authorization code in the query string. The app then needs to exchange this authorization code for an access token.

Exchange the authorization code for an access token

To exchange the authorization code for an access token, the app makes a POST request to the service’s token endpoint. The request will have the following parameters.

grant_type (required)

The grant_type parameter must be set to “authorization_code”.

code (required)

This parameter is for the authorization code received from the authorization server which will be in the query string parameter “code” in this request.

redirect_uri (possibly required)

If the redirect URL was included in the initial authorization request, it must be included in the token request as well, and must be identical. Some services support registering multiple redirect URLs, and some require the redirect URL to be specified on each request. Check the service’s documentation for the specifics.

Client Authentication (required)

The service will require the client authenticate itself when making the request for an access token. Typically services support client authentication via HTTP Basic Auth with the client’s client_id and client_secret. However, some services support authentication by accepting the client_id and client_secret as POST body parameters. Check the service’s documentation to find out what the service expects, since the OAuth 2.0 spec leaves this decision up to the service.

Example

The following step-by-step example illustrates using the authorization code grant type.

Step-by-step

The high level overview is this:

  • Create a log-in link with the app’s client ID, redirect URL, and state parameters
  • The user sees the authorization prompt and approves the request
  • The user is redirected back to the app’s server with an auth code
  • The app exchanges the auth code for an access token

The app initiates the authorization request

The app initiates the flow by crafting a URL containing ID, scope, and state. The app can put this into an <a href=""> tag.

<a href="https://authorization-server.com/oauth/authorize?response_type=code
     &client_id=mRkZGFjM&state=5ca75bd30">Connect Your Account</a>

The user approves the request

Upon being directed to the auth server, the user sees the authorization request shown below. If the user approves the request, they will be redirected back to the app along with the auth code and state parameters.

Example Authorization Request

The service redirects the user back to the app

The service sends a redirect header redirecting the user’s browser back to the app that made the request. The redirect will include a “code” in the URL.

https://example-app.com/cb?code=Yzk5ZDczMzRlNDEwY

The app exchanges the auth code for an access token

The app uses the auth code to get an access token by making a POST request to the authorization server.

POST /oauth/token HTTP/1.1
Host: authorization-server.com

code=Yzk5ZDczMzRlNDEwY
&grant_type=code
&redirect_uri=https://example-app.com/cb
&client_id=mRkZGFjM
&client_secret=ZGVmMjMz

The auth server validates the request and responds with an access token and optional refresh token if the access token will expire.

Response:

{
  "access_token": "AYjcyMzY3ZDhiNmJkNTY",
  "refresh_token": "RjY2NjM5NzA2OWJjuE7c",
  "token_type": "bearer",
  "expires": 3600
}

Possible Errors

There are several cases where you may get an error response during authorization.

Errors are indicated by redirecting back to the provided redirect URL with additional parameters in the query string. There will always be an error parameter, and the redirect may also include error_description and error_uri.

For example,

https://example-app.com/cb?error=invalid_scope

Despite the fact that servers return an error_description key, the error description is not intended to be displayed to the user. Instead, you should present the user with your own error message. This allows you to tell the user an appropriate action to take to correct the problem, and also gives you a chance to localize the error messages if you’re building a multi-language website.

Invalid redirect URL

If the redirect URL provided is invalid, the auth server will not redirect to it. Instead, it may display a message to the user describing the problem.

Unrecognized client_id

If the client ID is not recognized, the auth server will not redirect the user. Instead, it may display a message describing the problem.

The user denies the request

If the user denies the authorization request, the server will redirect the user back to the redirect URL with error=access_denied in the query string, and no code will be present. It is up to the app to decide what to display to the user at this point.

Invalid parameters

If one or more parameters are invalid, such as a required value is missing, or the response_type parameter is wrong, the server will redirect to the redirect URL and include query string parameters describing the problem. The other possible values for the error parameter are:

invalid_request: The request is missing a required parameter, includes an invalid parameter value, or is otherwise malformed.

unauthorized_client: The client is not authorized to request an authorization code using this method.

unsupported_response_type: The authorization server does not support obtaining an authorization code using this method.

invalid_scope: The requested scope is invalid, unknown, or malformed.

server_error: The authorization server encountered an unexpected condition which prevented it from fulfilling the request.

temporarily_unavailable: The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.

In addition, the server may include parameters error_description and error_uri with additional information about the error.

Sample Code

Let’s walk through a working example of the authorization code flow with Github. The example code is written in PHP with no external packages required and no framework needed. Hopefully this makes it easy to translate to other languages if desired.

Let’s define a method, apiRequest() which is a simple wrapper around cURL. This function includes the application/json header, and automatically decodes the JSON response. If we have an access token in the session, it sends the proper OAuth header with the access token.

    function apiRequest($url, $post=FALSE, $headers=array()) {
      $ch = curl_init($url);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

      if($post)
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));

      $headers[] = 'Accept: application/json';

      if(isset($_SESSION['access_token']))
        $headers[] = 'Authorization: Bearer ' . $_SESSION['access_token'];

      curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

      $response = curl_exec($ch);
      return json_decode($response);
    }

Now let’s set up a few variables we’ll need for the OAuth process.

    // Fill these out with the values you got from Github
    $githubClientID = '';
    $githubClientSecret = '';

    // This is the URL we'll send the user to first to get their authorization
    $authorizeURL = 'https://github.com/login/oauth/authorize';

    // This is the endpoint our server will request an access token from
    $tokenURL = 'https://github.com/login/oauth/access_token';

    // This is the Github base URL we can use to make authenticated API requests
    $apiURLBase = 'https://api.github.com/';

    // Start a session so we have a place to store things between redirects
    session_start();

First, we set up the “logged-in” and “logged-out” views.

    // If there is an access token in the session the user is logged in
    if(isset($_SESSION['access_token'])) {
      // Make an API request to Github to fetch basic profile information
      $user = apiRequest($apiURLBase . 'user');

      echo '<h3>Logged In</h3>';
      echo '<h4>' . $user->name . '</h4>';
      echo '<pre>';
      print_r($user);
      echo '</pre>';

    } else {
      echo '<h3>Not logged in</h3>';
      echo '<p><a href="?login">Log In</a></p>';
    }

The logged-out view contains a link to our login URL which starts the OAuth process.

When the user is logged in, we make an API request to Github to retrieve basic profile information and display their name.

Now that we have the necessary variables set up, let’s start the OAuth process.

The first thing we’ll have people do is visit this page with ?action=login in the query string to kick off the process.

    // Start the login process by sending the user to Github's authorization page
    if(isset($_GET['login'])) {
      // Generate a random hash and store in the session for security
      $_SESSION['state'] = bin2hex(random_bytes(16));
      unset($_SESSION['access_token']);

      $redirectURI = 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF']
      $params = array(
        'client_id' => $githubClientID,
        'redirect_uri' => $redirectURI,
        'scope' => 'user',
        'state' => $_SESSION['state']
      );

      // Redirect the user to Github's authorization page
      header('Location: ' . $authorizeURL . '?' . http_build_query($params));
      die();
    }

We build up an authorization URL and then send the user there. The URL contains our public client ID, the redirect URL which we previously registered with Github, the scope we’re requesting, and a “state” parameter.

We use the state parameter as an extra security check so that when Github sends the user back here with the state in the query string, we can verify that we did actually initiate this request and it’s not someone else hijacking the session.

At this point, the user will be directed to Github and they will see the standard OAuth authorization prompt, illustrated below.

Example Authorization Request

When the user approves the request, they will be redirected back to this page with code and state parameters in the request. The code below handles this request.

    // When Github redirects the user back here, there will be a "code" and "state" 
    // parameter in the query string
    if(isset($_GET['code'])) {
      // Verify the state matches our stored state
      if(!isset($_GET['state'])  || $_SESSION['state'] != $_GET['state']) {
        header('Location: ' . $_SERVER['PHP_SELF'] . '?error=invalid_state');
        die();
      }

      // Exchange the auth code for a token
      $redirectURI = 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'];
      $token = apiRequest($tokenURL, array(
        'client_id' => $githubClientID,
        'client_secret' => $githubClientSecret,
        'redirect_uri' => $redirectURI,
        'code' => $_GET['code']
      ));
      $_SESSION['access_token'] = $token->access_token;

      header('Location: ' . $_SERVER['PHP_SELF']);
      die();
    }

The key here is sending a request to Github’s token endpoint. The request contains our public client ID as well as the private client secret. We also send the same redirect URL as before along with the authorization code. If everything checks out, Github generates an access token and returns it in the response. We store the access token in the session and redirect to the home page and the user is logged in.

We have not included any error handling code in this example for simplicity’s sake. In reality you would want to check for errors returned from Github and display an appropriate message to the user.

Download the Sample Code

You can download the complete sample code on GitHub.

User Experience Considerations

In order for the authorization code grant to be effective, the authorization page must appear in a web browser the user is familiar with, and must not be embedded in an iframe popup or an embedded browser in a mobile app. As such, it is most useful for traditional “web apps” where the user is already in a web browser and redirecting to the server’s authorization page is not too jarring.

Security Considerations

The authorization code grant is designed for clients which can protect their client ID and secret. As such, it is most appropriate for web apps running on a server which does not make its source code available.

If an app wants to use the authorization code grant but can’t protect its secret (i.e. native mobile apps), then the client secret is not required when making a request to exchange the auth code for an access token. However, some services will not accept the authorization code exchange without the client secret, so native apps might need to use an alternate method for those services.

While the OAuth 2.0 spec does not specifically require that redirect URLs use TLS encryption, it is highly recommended. The only reason it is not required is because deploying an SSL website is still somewhat of a hurdle for many developers and this would discourage wide adoption of the spec. Some APIs do require https for their redirect endpoints, but many still do not.