Protected Resource

1. Target Readers

The main purpose to implement OAuth 2.0 is to protect Web APIs by access tokens. Therefore, all of you will finally need to read this document.

What people want to do is to protect their Web APIs by access tokens, but they will soon notice that they have to implement RFC 6749 (OAuth 2.0) as a prerequisite. Authlete exists to mitigate the burden to implement the specification and to enable you to focus on implementing your Web APIs.

2. What is “Protected Resource Endpoint”?

Web APIs protected by OAuth 2.0 access tokens are called protected resource endpoints. Such endpoints do the following before they return requested data to client applications.

  1. Extract an access token from a request.
  2. Get detailed information about the access token from the authorization server.
  3. Validate the access token.

The following sections describe how to do these.

3. Extract Access Token

How should a protected resource endpoint accept an access token from a client application? It is possible to devise as many new ways to do it as you like. However, we don’t have to reinvent the wheel. RFC 6750 (Bearer Token Usage) has already defined three ways to accept an access token as listed below.

  1. Via Authorization header. (2.1. Authorization Request Header Field)
  2. Via a form parameter access_token. (2.2. Form-Encoded Body Parameter)
  3. Via a query parameter access_token. (2.3. URI Query Parameter)

The most desirable way is the first one and RFC 6750 says “Resource servers MUST support this method”. Simply saying, when a client application uses the first way, a protected resource endpoint can find an HTTP header like below in the request.

Authorization: Bearer access-token

Below are examples to extract an access token.

private static final Pattern CHALLENGE_PATTERN
    = Pattern.compile("^Bearer *([^ ]+) *$", Pattern.CASE_INSENSITIVE);

/**
 * Entry point for GET method.
 */
@GET
public Response get(
    @HeaderParam(HttpHeaders.AUTHORIZATION) String authorization,
    @QueryParam("access_token") String accessToken)
{
    // Extract an access token from Authorization header or use
    // the value of query parameter 'access_token'.
    accessToken = extractAccessToken(authorization, accessToken);
}

/**
 * Entry point for POST method.
 */
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response post(
    @HeaderParam(HttpHeaders.AUTHORIZATION) String authorization,
    @FormParam("access_token") String accessToken)
{
    // Extract an access token from Authorization header or use
    // the value of form parameter 'access_token'.
    accessToken = extractAccessToken(authorization, accessToken);
}

/**
 * Extract an access token from Authorization header, or return
 * the value of 'access_token' request parameter.
 */
private static String extractAccessToken(
    String authorization, String accessToken)
{
    // If Authorization header is not found in the request.
    if (authorization == null)
    {
        // Return the value of 'access_token' request parameter.
        return accessToken;
    }

    // Pattern matching on the value of Authorization header.
    Matcher matcher = CHALLENGE_PATTERN.matcher(authorization);

    // If the value of Authorization header is in the format of
    // 'Bearer access-token'.
    if (matcher.matches())
    {
        // Return the value extracted from Authorization header.
        return matcher.group(1);
    }
    else
    {
        // Return the value of 'access_token' request parameter.
        return accessToken;
    }
}
# Method to extract an access token from a request.
def extract_access_token(request)
  # The value of Authorization header.
  header = request.env["HTTP_AUTHORIZATION"]

  # If the value is in the format of 'Bearer access-token'.
  if /^Bearer[ ]+(.+)/i =~ header
    # Return the value extracted from Authorization header.
    return $1
  end

  # Return the value of 'access_token' request parameter.
  return request["access_token"]
end

# Entry point for GET method.
get '/hello' do
  # Extract an access token from the request.
  access_token = extract_access_token(request)
end

# Entry point for POST method.
post '/hello' do
  # Extract an access token from the request.
  access_token = extract_access_token(request)
end
/**
 * Function to extract an access token from a request.
 */
function extract_access_token()
{
    // The value of Authorization header.
    $header = $_SERVER['HTTP_AUTHORIZATION'];

    // If the value is in the format of 'Bearer access-token'.
    if ($header != null && preg_match('/^Bearer[ ]+(.+)/i', $header, $captured))
    {
        // Return the value extracted from Authorization header.
        return $captured;
    }

    if ($_SERVER['REQUEST_METHOD'] == 'GET')
    {
        // Return the value of 'access_token' query parameter.
        return $_GET['access_token'];
    }
    else
    {
        // Return the value of 'access_token' form parameter.
        return $_POST['access_token'];
    }
}

4. Introspect Access Token

RFC 7662

The process to get detailed information about an access token is called introspection. How to introspect an access token depends on each implementation of OAuth 2.0 authorization servers.

However, in Oct. 2015, RFC 7662 (OAuth 2.0 Token Introspection) was released as a standard specification for introspection. If an authorization server you are using supports RFC 7662, your protected resource endpoints can introspect access tokens without depending on a specific authorization server implementation. But still, how to protect the introspection endpoint itself depends on each implementation as mentioned in “2.1. Introspection Request” of “RFC 7662”.

From RFC 7662, 2.1. Introspection Request;

To prevent token scanning attacks, the endpoint MUST also require some form of authorization to access this endpoint, such as client authentication as described in OAuth 2.0 [RFC 6749] or a separate OAuth 2.0 access token such as the bearer token described in OAuth 2.0 Bearer Token Usage [RFC 6750]. The methods of managing and validating these authentication credentials are out of scope of this specification.

Authlete Introspection API

Authlete provides an introspection API at /auth/introspection. The implementation of the API does not comply with RFC 7662, but from a viewpoint of those who develop protected resource endpoints, our API is better than RFC 7662 as listed below.

  1. You can delegate validation to our introspection API.
  2. Our introspection API generates an error message that complies with RFC 6750 when a given access token is invalid.

Simply saying, Authlete’s introspection API does introspection + validation + building an error message complying with RFC 6750. You can find the detailed specification of Authlete’s introspection API in Authlete Web APIs document.

5. Validate Access Token

The next step after getting detailed information about an access token is to validate the access token. Validation involves the following steps.

  1. Has it expired or not?
  2. Has it been revoked or not?
  3. Does it cover necessary permissions?
  4. Is it associated with the expected end-user?

As mentioned, you can delegate validation to Authlete’s introspection API if you want. scopes and subject are optional parameters for the introspection API to specify the scopes (= permissions) that your protected resource endpoint requires and the subject (= unique identifier) of an end-user that your protected resource endpoint expects.

If you utilize scopes parameter and subject parameter accordingly based on your needs, you don’t have to write separate validation code.

6. Error Response

If you want to comply with RFC 6750, responses from your protected resource endpoints must contain WWW-Authenticate header on errors. The detailed format of the value of the header is described in “3. The WWW-Authenticate Response Header Field”. The format is not so simple as you may guess. Below is an example with parameters mentioned in RFC 6750 (some are optional, though).

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
                  scope="read write",
                  error="invalid_token",
                  error_description="The access token expired",
                  error_uri="http://example.com/error/123"

You might be seduced into ignoring this specification because you may doubt it’s worth making an effort just to tell error details to client applications. However, you don’t have to make a compromise if you use Authlete’s introspection API. It’s because a response from the API contains responseContent property whose value can be used as the value of WWW-Authenticate header. As a result, you can easily build an error response like below.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Just embed responseContent here.

7. Dispatch According to Validation Result

If an error is detected on validation, a protected resource endpoint should return 400 Bad Request, 401 Unauthorized or 403 Forbidden (or 500 Internal Server Error) based on the cause of the error.

On the other hand, when an access token is valid, a protected resource endpoint can generate any response it likes. In a typical case, it will return the requested resource in JSON format with 200 OK to the client application.

To make it easy to dispatch based on validation result, a response from Authlete’s introspection API contains action property. It is a string that represents the next action you should take. Values of action and their meanings are as follows.

Value Meaning
INTERNAL_SERVER_ERROR An error occurred in Authlete. 500 Internal Server Error should be returned to the client application.
BAD_REQUEST The request from the client application does not contain an access token. 400 Bad Request should be returned to the client application.
UNAUTHORIZED The presented access token does not exist or has expired. 401 Unauthorized should be returned to the client application.
FORBIDDEN The access token does not cover the required scopes or the subject associated with the access token is different from the expected one. 403 Forbidden should be returned to the client application.
OK The access token is valid.

Below are example implementations to dispatch according to validation result. Each method/function implementation (1) accepts an access token [required], a string array of scopes [optional] and a subject [optional], (2) calls the introspection API, and (3) dispatches the flow according to the value of action property in the response from the introspection API. When action is not OK, responseContent property in the response from the introspection API is used as the value of WWW-Authenticate header in the response to a client application.

/**
 * Call Authlete's /api/auth/introspection API.
 * A response from the API is returned when the
 * access token is valid. Otherwise, a
 * WebApplicationException is raised.
 */
private static IntrospectionResponse doIntrospection(
        String accessToken, String[] scopes, String subject)
{
    // Call Authlete's /api/auth/introspection API.
    IntrospectionResponse response = callIntrospectionApi(accessToken, scopes, subject);

    // The value of 'WWW-Authenticate' header on error.
    String content = response.getResponseContent();

    int status;

    // Check the next action.
    switch (response.getAction())
    {
        case INTERNAL_SERVER_ERROR:
            // 500 Internal Server Error.
            //   The API request from this implementation was wrong
            //   or an error occurred in Authlete.
            status = 500;
            break;

        case BAD_REQUEST:
            // 400 Bad Request.
            //   The request from the client application does not
            //   contain an access token.
            status = 400;
            break;

        case UNAUTHORIZED:
            // 401 Unauthorized.
            //   The presented access token does not exist or has expired.
            status = 401;
            break;

        case FORBIDDEN:
            // 403 Forbidden.
            //   The access token does not cover the required scopes
            //   or the subject associated with the access token is
            //   different.
            status = 403;
            break;

       case OK:
            // The access token is valid (= exists and has not expired).
            return response;

        default:
            // This never happens.
            throw new WebApplicationException("Unknown action", 500);
    }

    Response res = Response
            .status(status)
            .header(HttpHeaders.WWW_AUTHENTICATE, content)
            .build();

    throw new WebApplicationException(res);
}
#--------------------------------------------------
# Call Authlete's /api/auth/introspection API.
# A response from the API is returned when the
# access token is valid. Otherwise, a WebException
# is raised.
#--------------------------------------------------
def do_introspection(token, scopes, subject)
  # Call Authlete's /api/auth/introspection API.
  response = call_introspection_api(token, scopes, subject)

  # The value of 'WWW-Authenticate' header on error.
  content = response["responseContent"]

  # "action" denotes the next action.
  case response["action"]
  when "INTERNAL_SERVER_ERROR"
    # 500 Internal Server Error
    #   The API request from this implementation was wrong
    #   or an error occurred in Authlete.
    raise WebResponse.new(500).wwwAuthenticate(content).to_exception

  when "BAD_REQUEST"
    # 400 Bad Request
    #   The request from the client application does not
    #   contain an access token.
    raise WebResponse.new(400).wwwAuthenticate(content).to_exception

  when "UNAUTHORIZED"
    # 401 Unauthorized
    #   The presented access token does not exist or has expired.
    raise WebResponse.new(401).wwwAuthenticate(content).to_exception

  when "FORBIDDEN"
    # 403 Forbidden
    #   The access token does not cover the required scopes
    #   or the subject associated with the access token is
    #   different.
    raise WebResponse.new(403).wwwAuthenticate(content).to_exception

  when "OK"
    # The access token is valid (= exists and has not expired).
    return response

  else
    # This never happens.
    raise WebResponse.new(500, "Unknown action").plain.to_exception
  end
end
/**
 * Call Authlete's /api/auth/introspection API.
 * A response from the API is returned when the
 * access token is valid. Otherwise, a WebException
 * is raised.
 */
function do_introspection($token, $scopes, $subject)
{
    // Call Authlete's /api/auth/introspection API.
    $response = call_introspection_api($token, $scopes, $subject);

    // he value of 'WWW-Authenticate' header on error.
    $content = $response['responseContent'];

    // "action" denotes the next action.
    switch ($response['action'])
    {
        case 'INTERNAL_SERVER_ERROR':
            // 500 Internal Server Error
            //   The API request from this implementation was wrong
            //   or an error occurred in Authlete.
            (new WebResponse(500))->wwwAuthenticate($content)->finish();
            return null; // Not reach here.

        case 'BAD_REQUEST':
            // 400 Bad Request
            //   The request from the client application does not
            //   contain an access token.
            (new WebResponse(400))->wwwAuthenticate($content)->finish();
            return null; // Not reach here.

        case 'UNAUTHORIZED':
            // 401 Unauthorized
            //   The presented access token does not exist or has expired.
            (new WebResponse(401))->wwwAuthenticate($content)->finish();
            return null; // Not reach here.

        case 'FORBIDDEN':
            // 403 Forbidden
            //   The access token does not cover the required scopes
            //   or the subject associated with the access token is
            //   different.
            (new WebResponse(403))->wwwAuthenticate($content)->finish();
            return null; // Not reach here.

        case 'OK':
            // The access token is valid (= exists and has not expired).
            return $response;

        default:
            // This never happens.
            (new WebResponse(500, "Unknown action"))->plain()->finish();
            return null; // Not reach here.
    }
}

8. Endpoint Sample

This section shows example implementations to (1) extract an access token and (2) validate the access token in a protected resource endpoint (so-called Web API). Each implementation requires two scopes, read and write.

private static final String[] SCOPES = { "read", "write" };

@GET
public Response hello(
    @HeaderParam(HttpHeaders.AUTHORIZATION) String authorization,
    @QueryParam("access_token") String accessToken)
{
    // Extract an access token from the request.
    accessToken = extractAccessToken(authorization, accessToken);

    // Introspect and validate the access token.
    IntrospectionResponse response = doIntrospection(accessToken, SCOPES, null);

    // The access token is valid. Write the main code after here.
    ......
}
get '/hello' do
  # Extract an access token from the request.
  token = extract_access_token(request)

  begin
    # Introspect and validate the access token.
    response = do_introspection(token, ["read", "write"], nil)
  rescue WebException => e
    # The access token is invalid.
    return e.to_response
  end

  # The access token is valid. Write the main code after here.
  ......
end
<?php
require('../include/introspection-functions.php');

// Extract an access token from the request.
$token = extract_access_token();

// Introspect and validate the access token.
$response = do_introspection($token, ['read', 'write'], null);

// The access token is valid. Write the main code after here.
......
?>

9. Scope

How Are Scopes Used?

In OAuth context, scope is a technical term and it represents a permission. When a client application makes an authorization request to an authorization endpoint of a service, the application may list scopes it wants using scope parameter. The following is an example authorization request specifying two scopes: read and write.

https://host/authorize?response_type=token&client_id=your-client-id&scope=read+write

After the end-user authorizes the authorization request, the authorization server issues an access token which are associated with the scopes. The client application uses the access token when it accesses protected resource endpoints of the service. The following is an example protected resource request that uses access_token query parameter to pass an access token to a protected resource endpoint.

https://host/profile?access_token=access-token

A protected resource endpoint extracts an access token from the protected resource request and validates the access token. In a typical case, an implementation of protected resource endpoint checks whether the presented access token is associated with scopes which the endpoint requires. The following is a conceptual code to extract and validate an access token.

// Extract an access token from the protected resource request.
access_token = extract_access_token_from_request(request);

// If the access token has expired.
if (access_token.has_expired) {
    return unauthorized;
}

// Scopes associated with the access token.
scopes = access_token.scopes;

// If the access token covers "read" and "write".
if (scopes.contains("read") && scopes.contains("write")) {
    return ok;
} else {
    return forbidden;
}

Scope Name Definition

A scope name is a case-sensitive ASCII characters. The exact specification is described in “RFC 6749, 3.3. Access Token Scope” as shown below. %x20 (space), %x22 (double quotation mark) and %x5C (back slash) are not allowed to use.

scope-token = 1*( %x21 / %x23-5B / %x5D-7E )

According to the specification, %x2C (comma) can be used for scope names, but Facebook uses commas as the delimiter to list scope names. It is a violation against the specification.

Standard Scopes

OpenID Connect Core 1.0 has defined some standard scopes. The table below is the list.

Name Section
address 5.4. Requesting Claims using Scope Values
email 5.4. Requesting Claims using Scope Values
openid 3.1.2.1. Authentication Request
offline_access 11. Offline Access
phone 5.4. Requesting Claims using Scope Values
profile 5.4. Requesting Claims using Scope Values

Scope Registration

Scopes need to be registered in advance before use. How to register scopes depends on each authorization server implementation.

Authlete provides GUI for you to edit the list of scopes supported by your service. Login Service Owner Console, click “Edit” button of a service whose scopes you want to edit, and click “Token” tab. You can see “supported scopes” at the bottom like below.

editing token supported scopes

After editing the list, don’t forget to press “Update” to apply changes.

10. Summary

Authlete provides an introspection API (/api/auth/introspection) for implementations of protected resource endpoints. The API is better than RFC 7662 because it does not only introspection but also validation and building an error message complying with RFC 6750.