-
Notifications
You must be signed in to change notification settings - Fork 327
OAuth 2.0 Resource Server
Note that this functionality is deprecated since version 2.4.0 in favour of using mod_oauth2
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 and as such make those resources OAuth 2.0 enabled.
The Resource Server behavior is triggered for a particular path by protecting it with:
<Location /api>
AuthType oauth20
Require [ valid-user | claim <value> ]
</Location>
Note you'll need to add a Require
clause to effectively restrict access. Require valid-user
will allow access to any Client that presents a valid access token issued by the Authorization Server. Most probably you want to restrict access a bit more based on claims such as client_id
, scope
or other information such as roles.
The behavior is controlled with the configuration directives that start with OIDCOAuth*
, see the section called "OAuth 2.0 Resource Server Settings" in the sample Apache configuration file that comes with the module.
There are two modes for validation of access tokens, remote and local, described below.
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. mod_auth_openidc supports a number of API variants between the Resource Server and the Authorization server to validate the token:
- any Authorization Server that conforms to the IETF spec RFC 7662 on Token Introspection:
https://tools.ietf.org/html/rfc7662 - PingFederate >= 6.x
- Google OAuth 2.0
The validation call settings are flexible enough to cater for different proprietary API 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 whenOIDCOAuthIntrospectionEndpointMethod GET
is defined - use HTTP Basic Authentication against the introspection endpoint with
OIDCOAuthClientID
andOIDCOAuthClientSecret
, or pass those credentials in as POST (or GET) parametersclient_id
andclient_secret
respectively, depending on theOIDCOAuthIntrospectionEndpointAuth
setting, use OIDC-style authentication withprivate_key_jwt
orclient_secret_jwt
, use TLS Client Authentication viaOIDCOAuthIntrospectionEndpointCert
andOIDCOAuthIntrospectionEndpointKey
or don't use any client authentication. - configure the parameter name in which the token is passed with
OIDCOAuthIntrospectionTokenParamName
(default istoken
) - 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)
(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>)+
(available since version 2.3.7rc3)
If your Authorization Server supports publishing a metadata document according to RFC 8414 you can specify:
# URL where Authorization Provider Provider metadata can be found (e.g. https://example.com/.well-known/oauth-authorization-server)
# as defined in RFC 8414. The obtained metadata will be cached and refreshed every 24 hours.
# If set, individual entries below will not have to be configured but can be used to add
# extra entries/endpoints to settings obtained from the metadata.
# If OIDCOAuthServerMetadataURL is not set, the endpoint entries below it will have to be configured.
OIDCOAuthServerMetadataURL <url>
This setting can obtain the token introspection endpoint and authentication parameters in case of remote validation, or it can obtain the public key material from the JWKs URL in case of local validation.
Note that if an introspection URL is configured, remote validation will take precedence over a JWKs URI for local validation
Alternatively, if your Authorization Server (only) publishes key material on a JWKs URL you can use a more dynamic way of obtaining the verification keys for local validation:
# 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>
Using HTTP Basic Authentication:
OIDCOAuthIntrospectionEndpoint <endpoint>
OIDCOAuthClientID <client_id>
OIDCOAuthClientSecret <client_secret>
OIDCOAuthRemoteUserClaim sub
A different authentication method would be set with:
#OIDCOAuthIntrospectionEndpointAuth [ client_secret_basic | client_secret_post | client_secret_jwt | private_key_jwt]
To setup remote validation of access tokens issued by Keycloak you'll need to configure a confidential client in Keycloak and configure those credentials in OIDCOAuthClientID
and OIDCOAuthClientSecret
.
Be aware that you'll have call the introspection endpoint using the same <host>:<port>
combo as the one that was used when the token was obtained by the client in the call to the token endpoint otherwise the introspection result will always be { "active": "false" }
.
OIDCOAuthIntrospectionEndpoint https://<host:port>/auth/realms/<realm>/protocol/openid-connect/token/introspect
OIDCOAuthIntrospectionEndpointParams token_type_hint=access_token
OIDCOAuthClientID <client_id>
OIDCOAuthClientSecret <client_secret>
Alternatively one can configure local validation since Keycloak issues JWT tokens which can be verified by keys published on Keycloak's JWKs URL. That URL be configured in the OIDCOAuthVerifyJwksUri
primitive.
Temporary: since Keycloak includes an "issued at" timestamp ("iat"
) in the JWT, the "slack" for that needs probably to be increased using OIDCIDTokenIatSlack
but that affects OpenID Connect id_token
validation as well and has a max of 3600
seconds. If you want to use local validation and get around the "iat" limitation mentioned you need to upgrade to 2.3.3rc0 or newer which doesn't put restrictions on the "iat" value.
OIDCOAuthVerifyJwksUri https://<host:port>/auth/realms/<realm>/protocol/openid-connect/certs
OIDCIDTokenIatSlack 3600
Example config for using PingFederate as your OAuth 2.0 Authorization server, based on the OAuth 2.0 PlayGround configuration and doing claims-based authorization, using RFC 7662 compliant Token Introspection.
# remote validation
OIDCOAuthIntrospectionEndpoint https://localhost:9031/as/introspect.oauth2
OIDCOAuthIntrospectionEndpointAuth client_secret_basic
OIDCOAuthRemoteUserClaim Username
OIDCOAuthSSLValidateServer Off
OIDCOAuthClientID rs_client
OIDCOAuthClientSecret 2Federate
<Location /api>
AuthType oauth20
Require claim client_id:ro_client
#Require claim scope~\bprofile\b
</Location>
Sample configuration where mod_auth_openidc acts as an OAuth 2.0 Resource Server using Google as the
Authorization Server. This allows us to expose protected resources only to (non-browser/in-browser/native) clients
that are able to present a valid access token obtained from Google. mod_auth_openidc will validate the
access_token
against Google's token info endpoint and use the claims returned in the response for
authorization purposes. The following configuration allows access only to a specific client:
OIDCOAuthIntrospectionEndpoint https://www.googleapis.com/oauth2/v1/tokeninfo
OIDCOAuthIntrospectionTokenParamName access_token
OIDCOAuthRemoteUserClaim user_id
<Location /example/api/v2/>
Authtype oauth20
Require claim issued_to:412063239660.apps.googleusercontent.com
</Location>
Note that this is not an OpenID Connect SSO scenario where users are authenticated but rather a "pure" OAuth 2.0 scenario where mod_auth_openidc is the OAuth 2.0 Resource Server instead of the RP/client. How the actual client accessing the protected resources got its access token is not relevant to this Apache Resource Server setup.
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>
As contributed by [email protected]:
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.