Skip to content

OAuth 2.0 Resource Server

Hans Zandbelt edited this page Nov 3, 2015 · 26 revisions

In addition to its OpenID Connect RP capabilities, mod_auth_openidc can also function as an OAuth 2.0 Resource Server, validating bearer access tokens sent by OAuth 2.0 clients. This can be used to protect static content, hosted APIs or applications or protected content running behind the Apache server when Apache operates as a reverse proxy in front of origin servers, APIs or applications.

There are two modes for validation of access tokens, remote and local.

Remote Validation

Remote validation consists of calling out to an OAuth 2.0 Authorization Server in a so-called "introspection" or "validation" call. This works with arbitrary token types since the token is opaque to the Resource Server. The API between Resource Server and Authorization Server is not (yet) standardized, but mod_auth_openidc supports a number of implementations:

The validation call settings are flexible enough to cater for even different implementations as long as it conforms to the following template:

  • use HTTP POST with form-encoded parameters to the URL specified in OIDCOAuthIntrospectionEndpoint, or use HTTP GET with query parameters when OIDCOAuthIntrospectionEndpointMethod GET is defined
  • use HTTP Basic Authentication against the introspection endpoint with OIDCOAuthClientID and OIDCOAuthClientSecret, or pass those credentials in as POST (or GET) parameters client_id and client_secret respectively (or don't use client authentication)
  • configure the parameter name in which the token is passed with OIDCOAuthIntrospectionTokenParamName (default is token)
  • configure additional parameters sent in as part of the HTTP POST (or GET) to the introspection endpoint in OIDCOAuthIntrospectionEndpointParams
  • the response is a JSON object that contains an "token expiry" claim whose interpretation can be configured using the OIDCOAuthTokenExpiryClaim configuration setting wrt. claim name, timestamp semantics (absolute or relative) and whether the claim is optional or mandatory (the default is to expect a mandatoryexpires_in claim with a relative value as PingFederate's and Google's Authorization Servers provide)

Local Validation

(available since version 1.8.0rc0)
Local validation can be used with bearer access tokens that are JSON Web Tokens. It consists of validating the JWT token against a configured set of symmetric or public keys. Settings used in that case are:

# (Optional)
# The symmetric shared key(s) that can be used for local JWT access token validation.
# NB: this is one or more key tuples where a key tuple consists of:
#  plain|b64|hex#[<key-identifier>]#<key>
# When not defined, no access token validation with shared keys will be performed.
# Examples:
# - a plaintext secret and a key identifier (kid)
#     plain#1#mysecret
# - a base64 encoded secret, no key identifier provided
#     b64##AF515DE==
# - a hex encoded secret, no key identifier provided
#     hex##ede012
#OIDCOAuthVerifySharedKeys ([plain|b64|hex#][<kid>#]<key>)+

# (Optional)
# The fully qualified names of the files that contain the X.509 certificates with the RSA public
# keys that can be used for local JWT access token verification.
# NB: this is one or more key tuples where a key tuple consists of:
#  [<key-identifier>#]<path-to-cert>
# and the key identifier part is optional.
# When not defined, no access token validation with statically configured certificates will be performed.
#OIDCOAuthVerifyCertFiles ([<kid>#]<filename>)+

Alternatively, if your Authorization Server supports the OpenID Connect style of publishing key material on a JWKs URL you can use a more dynamic way of obtaining the verification keys:

# The JWKs URL on which the Authorization publishes the keys used to sign its JWT access tokens.
# When not defined local validation of JWTs can still be done using statically configured keys,
# by setting OIDCOAuthVerifyCertFiles and/or OIDCOAuthVerifySharedKeys.
OIDCOAuthVerifyJwksUri <jwks_url>

Sample Integrations

OAuth-Apis

OAuth-Apis https://github.com/OAuth-Apis/apis

# rpm -ivh  https://dl.fedoraproject.org/pub/epel/7/x86_64/h/hiredis-0.12.1-1.el7.x86_64.rpm
# rpm -ivh  ftp://fr2.rpmfind.net/linux/centos/7.1.1503/os/x86_64/Packages/jansson-2.4-6.el7.x86_64.rpm
# rpm -ivh https://github.com/pingidentity/mod_auth_openidc/releases/download/v1.8.6/mod_auth_openidc-1.8.6-1.el7.centos.x86_64.rpm

# vi /etc/httpd/conf/httpd.conf

<VirtualHost *:80> 

    OIDCOAuthClientID    <client_id>
    OIDCOAuthClientSecret    <client_secret>
    OIDCOAuthIntrospectionEndpoint    https://<oauth_apis_host>:8443/apis/v1/tokeninfo
    OIDCOAuthIntrospectionEndpointMethod    GET
    OIDCOAuthSSLValidateServer    Off
    OIDCOAuthRemoteUserClaim    audience
    OIDCOAuthIntrospectionTokenParamName    access_token
	
    <Location /protected>
	    Authtype oauth20
	    Require valid-user
	    ProxyPass http://<host>:<port>
	    ProxyPassReverse http://<host>:<port>
    </Location>

</VirtualHost>

phpOIDC

As contributed by scott@tropare.com:

So the setup I have is a login server (my OP) and API server (my RP) and a 3rd party developer server who has a shared client_id and secret with my OP.

Basically I want to use mod_auth_openidc to do introspection using Oauth 2.0 and use phpOIDC as my OP.

The 3rd party will redirect to my login server, the end user will log in, redirect back to the 3rd party page, who will then use they access token they received to make a call to my RP, protected by mod_auth_openidc.

The secret sauce here is that phpOIDC does not have a token validate features, at least one that conforms to the spec at:

https://tools.ietf.org/html/draft-ietf-oauth-introspection-05

Then answer is you have to add your own validatetoken function to phpOIDC.

Inside your function that you add to phpOP/index.php, make sure you call is_client_authenticated() in phpOP/index.php (mod_auth_openidc will pass the client and secret as part of it's validate token pass).

Make sure you call the above and fail if the client is not authenticated so you conform to the part of the spec that says:

To prevent unauthorized 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 [RFC6749]

Set your :

OIDCOAuthIntrospectionEndpointAuth client_secret_basic

in apache conf, and also set the same in the phpOIDC configuration.

The other issue I had was having the correct encryption and signing algorithms defined on both ends, make sure they all match up.

Once you've done all of that, grab the access_token from the http request, then do the following in your PHP code.

        if(is_client_authenticated()) {
            $token = db_find_access_token($access_token);
            if($token) {
                    $db_client = db_get_client($token['client']);
                    if(!$db_client)
                            throw new BearerException('invalid_request', 'Invalid Client ID');
                    $tinfo = json_decode($token['info'], true);
                    $userinfo = Array();

                    $db_user = db_get_user($tinfo['u']);
                    $scopes = explode(' ', $tinfo['g']['scope']);
                    if(in_array('openid', $scopes)) {
                            $userinfo['sub'] = wrap_userid($db_client, $tinfo['u']);
                    }

If everything is all good, then do:

                   $token_response = array(
                            'active' =>  true,
                            'sub' => $userinfo['sub']
                    );

        header("Content-Type: application/json");
        header("Cache-Control: no-store");
        header("Pragma: no-cache");
        echo json_encode($token_response);

If it's not good do:

     $token_response = array (
                            'active' => false
                    );

Once I get my code cleaned up a bit, I'll do a pull request on the phpOIDC tree and get it into the base source. Hans was very awesome in answering my noob questions, and I wanted to share my solution, and share the love , and possibly help someone else over the same hump.

Also here is my complete Authintrospection apache config

    OIDCOAuthIntrospectionEndpoint https://login.myhost.com/phpOp/index.php/validatetoken
    OIDCOAuthClientSecret <mysecret>
    OIDCOAuthIntrospectionEndpointParams grant_type=authorization_code&client_id=<myclientid>
    OIDCOAuthIntrospectionEndpointMethod GET
    OIDCOAuthIntrospectionTokenParamName access_token
    OIDCOAuthIntrospectionEndpointAuth client_secret_basic

NOTE: My RP mod_auth_openidc server uses a different client_id and secret than the 3rd party that is sending the user to the login page and making the API requests to the RP server.

I added the code to a pull request at:

https://bitbucket.org/PEOFIAMP/phpoidc/pull-request/5/added-function-to-handle-token-validation/diff

This has the complete code to add to phpOIDC to add this support.