Skip to content

Single Page Applications

Hans Zandbelt edited this page Jul 21, 2017 · 22 revisions

Implicit Client Profile

The canonical solution for using OpenID Connect with Single Page Applications (SPAs) is to use the Implicit grant type and have an access_token and an id_token delivered to the SPA in the hash fragment of a URL (i.e. the Redirect URI). The SPA will then use the access_token to access resources protected by mod_auth_openidc configured as an OAuth 2.0 Resource Server

That solutions has a number of drawbacks (described here) which is why mod_auth_openidc offers an alternative and arguably more secure way of handling OpenID Connect for Single Page Applications, described below.

Session Info

Available since version 2.2.0

For Single Page Applications (SPAs) and other Clients that require access to the access_token or other information related to an authenticated user session, mod_auth_openidc has the ability to return "session info" to the caller when a valid session cookie is presented on an HTTP request to the following endpoint:

<redirect_uri>?info=json

The session info is returned as a JSON object and the information returned in that is configurable through OIDCInfoHook:

# (Optional)
# Define the data that will be returned upon calling the info hook (i.e. <redirect_uri>?info=json)
#   iat (int)                  : Unix timestamp indicating when this data was created
#   access_token (string)      : the access token
#   access_token_expires (int) : the Unix timestamp which is a hint about when the access token will expire (as indicated by the OP)
#   id_token (object)          : the claims presented in the ID token
#   userinfo (object)          : the claims resolved from the UserInfo endpoint
#   refresh_token (string)     : the refresh token (if returned by the OP)
#   session (object)           : (for debugging) mod_auth_openidc specific session data such as "remote user", "session expiry", "session id" and a "state" object
# When not defined the session hook will not return any data but a HTTP 404
OIDCInfoHook [iat|access_token|access_token_expires|id_token|userinfo|refresh_token|session]+

Calling this hook will also reset the session inactivity timer.

For restricting access to this information see: Session Info Authorization in the "Advanced" section below.

Caveat: Note that for this particular hook, the full request handling chain of Apache is executed instead of just the authentication/authorization hooks. That means that any directives like ProxyPass that apply to this path would also execute for "info=json" as opposed to the other calls to OIDCRedirectURI that would terminate early. Hence the OIDCRedirectURI may need to be excluded from such (global) directives to allow the session info hook to complete.

Access Token Refresh

The Client calling the hook can indicate requirements on the "freshness" of the access_token by providing the parameter access_token_refresh_interval:

<redirect_uri>?info=json&access_token_refresh_interval=<seconds>

Providing a value of "0" will refresh the current access_token, but only when a refresh_token was provided as part of the initial authentication flow and was stored in the session.

Providing a value greater than "0" will not refresh the access token unless more than "<seconds>" have elapsed since the last refresh.

UserInfo Refresh

Note that the update frequency of the information returned from the UserInfo endpoint at the OpenID Connect Provider can be configured through the OIDCUserInfoRefreshInterval configuration primitive and that accessing that endpoint may in itself lead to a refresh of the access_token and possibly the refresh_token.

Allowing both OAuth 2.0 and OpenID Connect

In certain SPA use cases content may be served to browsers (as HTML) and to Javascript clients (as JSON) from the same path. In that case the mod_auth_openidc needs to be able to consume and validate both OAuth 2.0 bearer Access Tokens as well as session cookies on the same path. In Apache speak this means specifying both AuthType oauth20 and AuthType openid-connect in the same Location or Directory. This is handled by a 3rd directive AuthType auth-openidc.

Caveat: use this "mixed mode" with care as one typically wants to avoid to accept just any bearer token that was issued by your authorization server, so don't use just Require valid-user!

A secure configuration would be:

<Location /example>
  AuthType auth-openidc
  Require claim client_id:xxx
  Require claim aud:xxx
</Location>

Where the first Require directive restricts the (Javascript) OAuth 2.0 Client having access to this path and the second Require directive ensures that browsers have to present a session cookie issued by this Relying Party instance of mod_auth_openidc.

Note that just including Require valid-user for the latter would defeat the first Require. Hence you must identify sessions more specifically based on the claims in the id_token or obtained from the UserInfo endpoint. In multi-provider setups you may need to add multiple Require claim aud:xxx" directives since typically different client_id`'s would be used with different Providers. Alternatively you can base it on a claim that is issued by all Providers but is NOT part of Access Tokens issued to other Clients than your SPA's Javascript client.

Caveat: Also note that due to the nature of the "mixed mode" you should take care that overlapping claims in the id_token/userinfo and the access_token have the same semantics!

Advanced

Session Info Authorization

By default all Clients presenting an valid session cookie will receive the information configured in OIDCInfoHook. Further refinement of that can be done by using Require directives on the hook itself, i.e. the OIDCRedirectURI, e.g. restricting it to the module's Client identifier for a specific Provider when multiple Providers are used:

<Location /redirect_uri>
  AuthType openid-connect
  Require claim aud:ac_oic_client
</Location>

Or a more advanced example using the Apache 2.4 "<If>" primitive to apply fine-grained authorization only when the session info hook is called:

<Location /redirect_uri>
  <If "%{QUERY_STRING} =~ /.*info=json.*/" >
    Require claim aud:ac_oic_client
  </If>
  <Else>
    Require valid-user
  </Else>
</Location>

Clone this wiki locally