Skip to content
Hans Zandbelt edited this page Feb 2, 2024 · 18 revisions

mod_auth_openidc can authenticate and authorize users. The authorization part is described in more detail here. There are basically two options to do authorization based on the established user identity.

  1. Use the functions that mod_auth_openidc provides to authorize users based on the claims that have been provided for that user by the OpenID Connect provider.

  2. Use another Apache module that performs the authorization based on the user identity provided by mod_auth_openidc.

1. mod_auth_openidc

Using the functionality provided by mod_auth_openidc you can authorize users based on claims that have been provided for that user. The following statement can be used to do that:

Require claim <expression>

If multiple Require claim <expression> statements are specified for a single path they will be evaluated as a logical or. An example that uses exact matching of a claim value follows below:

Require claim sub:joe

Which would allow only users identified by the configured provider as joe, using the sub claim. Additionally you can match claim values against regular expressions by using Require claim~<expression> (note the ~ instead of the : after the claim keyword), e.g.:

Require claim "name~\w+ Jones$"

to match all users with last name Jones and a single first name which is roughly equivalent to Require claim family_name:Jones.

Nested Claims

Since version 2.3.0 nested claims are supported. Suppose your id_token contains the following:

{
	"iss" : "https://test-issuer.example.com",
	"exp" : 149628094,
	"sub" : "0e5d412f2f6edb94d811e3761187faf98bf7cf40168453786dc514cf91a0f094",
	"iat" : 149606494,
	"claims" : {
		"email" : "[email protected]",
		"name" : "Jane Doe",
		"given_name" : "Jane",
		"family_name" : "Doe",
		"address" : {
			"street_address" : "1234 Hollywood Blvd.",
			"locality" : "Los Angeles",
			"region" : "CA",
			"postal_code" : "90210",
			"country" : "US"
		},
	},
	"aud" : "HUNAV",
	"jti" : "ad17ef6b7b380e3e61d9a9947086753b89ed7189368788d24115c7fe6a0b4f3d",
	"auth_time" : 149606494,
	"sub_type" : "pairwise",
	"nonce" : "uYIYs7LmY3s5TraOw5dDF05_LI2eQzeBq4qTPoZVmfM"
}

You can then address the nested claims with a "." as in:

Require claim "claims.name~\w+ Jones$"
Require claim claims.address.region:CA
Claims as nested Array

Suppose the following claims.

{  "realm_access": 
  {
    "roles": [
      "offline_access",
      "uma_authorization",
    ]
  }
}

You can match a claim in a nested Array/List with the ":" as in:

Require claim realm_access.roles:uma_authorization
Complex Expressions

Support for complex authorization expressions that combine several claims in one expression is available since version 2.3.0 but only when compiled from source. JQ-based claims expression matching can be compiled in when compiled from source with --with-jq=<dir> where <dir> points to a libjq installation directory. Expressions can then be defined with Require claims_expr <expression>, e.g.:

Require claims_expr '.aud == "ac_oic_client" and (.scope | index("profile") != null)'
Expressions in Require statements

(Since version 2.4.2) Expressions in Require statements allow for creating e.g. a simple dynamic proxy where the request path dictates what backend a request is proxied to, and also dictates the role the user requires to access that backend. For example:

  ProxyPassMatch "^/(.*)-backend/(.*)$" "https://$1/$2"
  <LocationMatch "^/(?<backend>.*)-backend/">
    Require claim "roles:%{env:MATCH_BACKEND}"
    AuthType openid-connect
  </LocationMatch>

With this configuration, if someone sends a request to https://myproxy/foo-backend/bar/baz then the request will be proxied to https://foo/bar/baz if and only if the authenticated user has the foo role. Instead of needing to make a block for each backend defined like this, it is useful to have a generic way to do it. This also alleviates the need to reload the Apache2 config when a new backend is brought up, which is very useful if backends are spun up and down somewhat rapidly.

2. mod_authnz_ldap

As provided by Nishad Sankaranarayanan:

Authenticate users via mod_auth_openidc and then authorize them accordingly via mod_authnz_ldap. Using the OIDCRemoteUserClaim parameter in httpd.conf configuration, the claim value is set it as REMOTE_USER header variable. This REMOTE_USER is then leveraged by mod_authnz_ldap for performing ldap queries to identify the user.

In the example below, email is used as the REMOTE_USER claim value. The 'REMOTE_USER' value is used to find the user in LDAP, and later 'Require ldap-group ' is used to validate the group membership of the user to grant access.

You will be able to leverage all Require <options> available in mod_authnz_ldap to authorize the logged in user.

OIDCProviderMetadataURL https://accounts.google.com/.well-known/openid-configuration
OIDCClientID <client_id>
OIDCClientSecret <client_secret>
OIDCRedirectURI http://example.com/example/redirect_uri
OIDCScope "openid email profile"

# Set `REMOTE_USER` to the email address.
# this is the value that **mod_authnz_ldap** leverages as the first parameter after basedn. 
# in the example below, REMOTE_USER = email = mail attribute in LDAP.

OIDCRemoteUserClaim email
<Location /example/>
  AuthType openid-connect
  AuthLDAPURL "ldap://example.com/ou=people,dc=example,dc=com?mail?sub?(objectClass=*)"
  AuthLDAPGroupAttribute member
  Require ldap-group cn=myTestAccesss,ou=Groups,dc=example,dc=com
</Location>

3. Access to different URL paths on a per-Provider basis

This is an advanced example of a configuration where you have configured multiple OpenID Connect Providers and you want to be able to restrict certain URL paths to users from certain Providers. In older releases of mod_auth_openidc it is already possible to restrict access to URL paths on a per-provider basis by using the Require claim iss:<issuer> directive (one or more times). (if your OP does not return the iss claim from the user info endpoint then you need a version of mod_auth_openidc > 1.8.2)

But when initial logon is triggered, the users would still get a (single) Discovery page that lists all of the configured Providers. Since 1.8.1rc5 it is possible to specify the OIDCDiscoverURL in Location and Directory primitives which implies that you can skip Discovery by pointing to the "callback" URL of the Discovery page directly (which is equal to the OIDCRedirectURI, see also: https://github.com/OpenIDC/mod_auth_openidc/wiki#12-how-can-i-customize-the-idp-discovery-or-initial-login-page), with an iss query parameter that contains the selected Provider. Example (partial) configuration:

OIDCRedirectURI https://www.example.com/example/redirect_uri

<Location /example/p1>
  AuthType openid-connect
  OIDCDiscoverURL https://www.example.com/example/redirect_uri?iss=https%3A%2F%2Fp1.example.com
  Require claim iss:https://p1.example.com
</Location>

<Location /example/p2>
  AuthType openid-connect
  OIDCDiscoverURL https://www.example.com/example/redirect_uri?iss=https%3A%2F%2Fp2.example.com
  Require claim iss:https://p2.example.com
</Location>

Note that if you're using different Google Apps providers, the Require directives would use the hd parameter as in Require claim hd:<domain1> and Require claim hd:<domain2> as explained in the last paragraph of https://github.com/OpenIDC/mod_auth_openidc/#openid-connect-sso-with-google-sign-in and the OIDCDiscoverURL would look like: OIDCDiscoverURL https://www.example.com/example/redirect_uri?iss=accounts.google.com&auth_request_params=hd%3D<domain1>.