Skip to content

Latest commit

 

History

History
288 lines (187 loc) · 14.2 KB

USAGE.md

File metadata and controls

288 lines (187 loc) · 14.2 KB

SPID/CIE OIDC Federation Starter Kit

Actually only RelyingParty role is managed. Some parts of this documentation is focused on that role.


Both Snapshots and Released artifacts are available on GitHub Packages:

  • if you use Maven
<dependency>
  <groupId>it.spid.cie.oidc</groupId>
  <artifactId>it.spid.cie.oidc.starter.kit</artifactId>
  <version><!--replace with the wanted version --></version>
</dependency>
  • if you use Gradle
implementation group: 'it.spid.cie.oidc', name: 'it.spid.cie.oidc.starter.kit', version: 'xxx'

Unfortunately, as stated in the documentation, to use GitHub packages you have define GitHub repository in your ~/.m2/settings.xml (or settings.gradle) together with your credentials.

The "starter-kit" is a backend library with few dependencies:

PersistenceAdapter

The starter-kit doesn't interact directly with the database: it delegates to the implementer this job.

It exposes an interface, called PersistenceAdapter, and in the implementing class your application is free to use the database engine and the integration framework more suitable.

The starter-kit manages the following models:

  • FederationEntity. Contains all the elements needed to describe an Entity members of the Federation
  • CachedEntityInfo. An EntityInfo is the set of elements (the statement) the Federation Authority has about a Federation Entity. We have to cache this information to reduce network overhead
  • TrustChain. Contains all the elements needed to describe the "chain of trust" between two Federation Entity.
  • AuthnRequest. Describe an Authentication Request and helps to track the authentication flow
  • AuthnToken. Contains the Authentication Token and the Claims received on the authentication flow

Every model contains these helping attributes:

  • createDate
  • modifiedDate
  • storageId

The SpringBoot example project uses H2 as database engine and interact with it using a SpringBoot specific data framework. In the example: * there are annotated model classes able to marshalling/unmarshalling with starter-kit models * the class implementing PersistenceAdapter interact with data framework to store the information or to find it.

Take a look to /examples/relying-party-spring-boot/src/main/resources/sql/schema.sql for the sample database entities structure

Configuration Options

The class RelyingPartyOptions contains all the defaults and the limits for a RelyingParty implementation.

Many options can be customized by your application. Some of them have to be customized because no defaults are present. An example of minimum, mandatory, configuration can be

		Map<String, String> spidProviders = new HashMap<>();

		spidProviders.put(<SPID_PROVIDER>, <TRUST_ANCHOR>);

		RelyingPartyOptions options = new RelyingPartyOptions()
			.setDefaultTrustAnchor(<TRUST_ANCHOR>)
			.setClientId(<RELYING_PARTY>)
			.setSPIDProviders(spidProviders)
			.setTrustAnchors(ArrayUtil.asSet(<TRUST_ANCHOR>))
			.setApplicationName("Sample RP")
			.setRedirectUris(ArrayUtil.asSet(<RELYING_PARTY> + "callback"))
			.setJWK(<RELYING_PARTY_JWK>)
			.setTrustMarks(<RELYING_PARTY_TRUST_MARKS>);

where:

  • <TRUST_ANCHOR> is the Trust Anchor subject (es: http.//trust-anchor.org/)
  • <SPID_PROVIDER> is the SPID Provider subject (es: http://spid-provider.org/oidc/op/)
  • <RELYING_PARTY> is the RelyingParty subject (es: http://my-relying-party.org/oidc/rp/)
  • <RELYING_PARTY_JWK> is the string representation of a JSON WebKey (JWK), with both private and public parts, of your RelyingParty. This JWK is generated in the on-boarding flow as described below
  • <RELYING_PARTY_TRUST_MARKS> is the string representation of the TrustMarks generated by the Federation Authority at the end of the on-boarding flow

Your application can manage the configuration and how admin user provide it in the more suitable way. The starter kit doesn't force you in some way. You can use a configuration file (like the SpringBoot example) or a database entity or something else.

OIDCException

The starter-kit throws different application Exceptions but all of them are or extends OIDCException. Also standard Exceptions are rethrows as specific OIDCExceptions.

This allows you code to be more flexible: you don't have to change method signature when a new kind of OIDCExcetion will be implemented.

RelyingParty HTTP EndPoint

The starter-kit doesn't expose web-servlets: it delegates to the implementer this job.

Your application can use preferred framework to expose the required endpoints and, internally, call RelyingPartyHandler to process datas. The required endpoints are:

  • .well-known/openid-fedaration. Is the only one with a fixed name and is used to expose federation entity informations to other federation entities

  • authorize. This endpoint is the starting point for OIDC SPID/CIE authentication. The webpath is customizable. Is used inside you application UI on "SignIn with SPID" and "SignIn with CIE" buttons. The request is of type GET and supports the following parameters:

    • provider, REQUIRED. The http url corresponding to a subject id of a SPID/CIE OIDC Provider.
    • redirect_uri, OPTIONAL. One of the redirect_uri available in RP's metadata.
    • scope, OPTIONAL. One or more of the scopes, default is openid.
    • consent, OPTIONAL. Sets SPID or CIE extended consent values.
    • trust_anchor, OPTIONAL. Sets the Trust Anchor to resolve the Federation. Default is options.defaultTrustAnchor.
    • acr_values, OPTIONAL.
    • profile, OPTIONAL. Default: spid. Set (spid, cie)
  • callback. This endpoint corresponds to the redirect uri where the auth code lands. The webpath is customizable and have to be setted in options.redirectUris. This endpoint accepts a request with this parameters:

    • code, REQUIRED. Authorization code
    • state, REQUIRED. State value enclosed in the authentication request
  • logout. This endpoint starts a token revocation flow. The webpath is customizable.


The SpringBoot example use Spring specific approach to expose previous endpoints on `http://hostname[:port]/oidc/rp/` uri. Inside the endpoint implementation the starter-kit `RelyinPartyHandler` il called.

RelyingParty Handler

The RelyingPartyHandler class is the main element of the starter-kit for the RelyingParty role.

To istantiate the class you have to provider the options and the PersistenceAdapter implementation. The correctness of these elements is checked immediately in the constructor.

RelyingPartyHandler handler = new RelyingPartyHandler(options, persistenceAdapter);

A good approach is to instantiate only one handler per application (or per tenant) and to re-instantiate it when configuration changes.

The RelyingPartyHandler manages these following aspects:

Federation OnBoarding

generate the JSON Web Key needed for on-boarding

If you istantiate RelyingPartyOptions with no <RELYING_PARTY_JWK> the following call

WellKnowData wellKnown = handler.getWellKnownData(true);

generates a JWON Web Key (JWK) and return it inside the WellKnowData object as json string.

This information have to be managed by your application (stored somewhere) to be reused to re-instantiate RelyingPartyOptions and RelyingPartyHandler.

For the on-boarding process you have to provide to the Federation Authority only the public part of the JWK (wellKnown.getPublicJwks()).

build intermediate well known federation configuration

If you istantiate RelyingPartyOptions with <RELYING_PARTY_JWK> and no <RELYING_PARTY_TRUST_MARKS> your RelyingParty is in an intermediate on-boarding status.

The starter-kit exposes two methods

public WellKnownData getWellKnownData(boolean jsonMode) throws OIDCException;

public WellKnownData getWellKnownData(String requestURL, boolean jsonMode) throws OIDCException;

The second one is more suitable to be used into .well-known/openid-federation endpoint implementation because extracts entity's subject-id from the requested URL and guarantee your application is calling the right handler object instance.

build final well known federation configuration

When both <RELYING_PARTY_JWK> and <RELYING_PARTY_TRUST_MARKS> are specified into RelyingPartyOptions any call to wellKnownData() methods provides a the final federation entity configuration.

This final information is also stored, via PersistenceAdapter, into your application database and retrieved, via PersistenceAdapter, to avoid rebuilding effort.

Authentication Flow

authorize url

The first step of the authentication flow is to redirect the user to the OIDC Provider sign in page to authenticate itself, and sent back to the provided callback uri.

In your application UI the "SignIn" button invoke your implementation of authorize endpoint. This implementation call the handler with the provided params

String authURL = relyingPartyHandler.getAuthorizeURL(
	oidcProvider, trustAnchor, redirectUri, scope, profile, consent);

than the handler:

  • validate the provider arguments against provided options
  • fetch, via PersistenceAdapter, the TrustChain of the provider
    • use it if still valid
    • otherwise fetch information from the Provider and the TrustAnchor to build a TrustChain
    • store the new TrustChain (via PersistenceAdapter)
  • create and store, via PersistenceAdapter, an AuthnRequest object
  • build the url you application have to use to redirect the browser to the Provider for authentication

If something goes wrong in this process an OIDCException is thrown; otherwise your application receive the URL to redirect the browser.

Callback and UserInfo

On the second step of the authentication flow the control come back to your application to the callback endpoint.

Your application have to forward to RelyingPartyHandler only when sign-in happens successfully; errors have to be managed directly by your code as expected by the used framework. For "not successfully" we mean all the situations when the OIDC Provider reply doesn't contains the required params "code" and "scope".

JSONObject userInfo = relyingPartyHandler.getUserInfo(state, code);

The handler:

  • fetch the AuthnRequest, via PersistenceAdapter, matching provided params
  • obtain, from the OIDC Provider, a valid access token
  • obtain, from the OIDC Provider, the configured Claims (into RelyingPartyOptions)
  • store, via PersistenceAdapter, the AuthnToken with the claims
  • return a JSONObject with only the Claims

The AuthnToken model contains the "userKey" field filled from the value of the configured Claim (options.getUserKeyClaim()). This field is extremely important because is needed in the logout process.

The JSONObject looks like

{
    sub: "e6b06083c2644bdc06f5a1cea22e6538b8fd59fc091837938c5969a8390be944",
    "given_name": "peppe",
    "family_name": "maradona",
    "email": "that@ema.il",
    "https://attributes.eid.gov.it/fiscal_number": "8sada89s7da89sd7a98sd78",
}

If something goes wrong in this process an OIDCException is thrown; otherwise your application can process the claims for its the business needs, for example: - create or update the User profile inside the application database - redirect the user to the right landing page

Your application is the real owner of the logger user profile information. Is responsability of your application to merge it when user sign in with different providers.

Revocation Flow

The revocation flow is started by the user from your application UI. Your implementation of the logout endpoint have to call the handler with two elements:

  • the userKey of the user session
  • a class implementing RelyingPartyLogoutCallback
String redirectURL = relyingPartyHandler.performLogout(userKey, callback);

The handler:

  • fetch the AuthnRequest and AuthnToken, via PersistenceAdapter, for the user session
  • fire the callback to allow your application to invalidate the user session
  • send a revocation request to the OIDC Provider
  • return the logout landing url

If something goes wrong in this process an OIDCException is thrown; otherwise your application will receive the URL and can perform the redirect.

Provider Button Infos

Your application have to provide the user two buttons: "SignIn with SPID" and "SignIn with CIE". Both buttons present a drop-down list with configured OIDC Providers randomly sorted.

While the known providers are setted inside RelyingPartyOptions (setSPIDProviders() and setCIEProviders()), the RelyingPartyHandler provide a method to obtain the information needed to fill the drop-down list.

List<ProviderButtonInfo> infos = relyingPartyHandler.getProviderButtonInfos(OIDCProfile.SPID)

Every ProviderButtonInfo contains:

  • subject id of the provider
  • organization name
  • logo url

To provide these information the handler use the TrustChain model it has of the provider. It could be the one already stored in the database, via PersistenceAdapter, or the one builded on the fly (if the stored one is absent or invalid) and than stored.