Introduction

When developing applications, getting the functional and business requirements can be a rather intensive and complex matter. Most of the time and energy is often spent on the business and functional requirements, giving the other non- functional requirements less or even no attention.

The security aspects of an application are often perceived as complex and difficult as they impact the software at many places and tend to get “in the way” of developers. As such, security features are often “bolted” on top as an afterthought of the existing functionality.

Concepts

Amdatu Security provides a solution for authentication and authorisation of (web-based) applications. Throughout the rest of this document, several terms and concepts are used that need to be defined in more depth.

Principal

An entity in a (computer) system that is granted permissions by which it can interact with this system. Examples of a principal are: human users, or other systems;

Credentials

A set of attributes that are used to identify principals in a system. Examples of credentials are: username, password, email addresses, and cryptographic keys;

Realm

Defines a perimeter for (parts of) a system with certain well-defined security policies, such as the permitted principals, how they can interact with the system and in what way;

Authentication

The process of proofing the identity of a principal in a system, for example by means of handing over credentials that uniquely and positively identify the principal;

Authorisation

The process of defining whether or not principals have access to (parts of) a system. Authorisations are often defined at an organisational level;

Access Policy

A set of rules that define which principals are granted access to what parts of the system. Access policies are often broader than a system and defined at organisational level;

Access Control

A set of access policies that define the access to a particular system. There are various forms of implementing access control, for example, role-based in which the role of a principal defines whether or not access is granted or attribute-based in which the attributes of the principal, the resource and system define whether or not access granted;

Identity Provider

A system that provides the identity of a principal, often by means of credentials. Examples of identity providers are: social identity providers, such as Google and Facebook, or a local database of usernames and passwords.

The authentication process

Amdatu Security provides configurable means for defining how principals are authenticated. At a high level, the authentication process in Amdatu Security is split in two (see also the figure below):

  1. the principal first needs to prove its identity using a particular identity provider. Multiple identity providers might be configured for a realm, but only one is used to provide the identity of a principal. An identity provider can be an external system outside the perimeter of your system, such as Google or Facebook;

  2. once the identity provider has either confirmed the identity of the principal, it calls back to Amdatu Security with the identity of the principal. Amdatu security then grants the principal with its own access token by which it can access the rest of the system.

as auth overview

More details on the authentication part of Amdatu Security is given in the chapter on Authentication.

Authentication

In the Introduction, we explained the basic process that is supported by Amdatu Security. In this section, we are going to into more depth regarding the use and integration of the authentication part of Amdatu Security into your system or application.

Realms

To use Amdatu Security in your application, you need to define one or more “security perimeters”, called realms. For each realm, the authentication is delegated to a (preconfigured) identity provider. This identity provider verifies a user’s identity. After a successful verification, the user’s identity is See mapped to a local principal which you can use to uniquely identify that user from then on.

Principal management

The basic premise in Amdatu Security’s authentication is that it supports multiple identity providers in a uniform manner. The result of this is that each identity provider can have its own view on the identity of a principal. For example, Google’s OpenID Connect uses your email address as the primary identification of a principal, while the Yahoo OpenID provider uses a kind of opaque URL as primary identification.

If we want to support the use of multiple identity providers at the same time, we somehow need to define a mapping from their view on principals to a local view. If only to determine whether or not a principal is known in the system.

To create such a mapping each realm requires that you provide it with its own PrincipalLookupService. This service needs to be supplied by your application and requires a single method to be implemented:

Optional<String> getPrincipal(Map<String, String> credentials);

This method is given a map of credentials by the identity provider and all it should do is return some kind of identifier that represents the principal in the local system. In case no match could be made for the given credentials an empty optional is returned to indicate this.

the principal identifier returned can be any string that can be used to uniquely identify the principal in the future. It is considered good practise to use opaque identifiers, like UUIDs, for this as to avoid leaking personal information.

As any identity provider will call the getPrincipal method, extra information is needed to perform the mapping. For this, the credentials argument contains additional properties named providerType and propertyName whose values correspond to the identity provider for which the mapping needs to be done.

More information about the supported identity principals can be found later in this chapter.

An example on how an implementation of getPrincipal could look like:

@Override
public Optional<String> getPrincipal(Map<String, String> credentials) {
    String email = getEmail(credentials); (1)

    return m_profiles.values().stream()
        .filter(p -> p.getEmail().equals(email)) (2)
        .map(UserProfile::getUserID) (3)
        .findAny();
}
1 extracts the email address from the given credentials;
2 look for any “profile” whose email address matches;
3 return the identifier as used for that particular user.

Identity providers

One realm has one or more identity providers that it can use to provide the identity of principals. The identity providers in Amdatu Security can be considered as proxies for the real, potentially remote, identity providers.

The following identity providers are supported out of the box:

  • a “local” identity provider, that identifies principals based on a set of principals that are managed locally;

  • a “OpenID” identity provider, that uses OpenID to identify principals;

  • a “OpenID Connect” identity provider, that uses OpenID Connect (an abstraction on top of OAuth2) to identify principals.

Each identity provider is identified by a “unique” name, the providerName, that should be used to indicate which identity provider should be used in the authentication process. Typically, the front-end supplies the providerName value when starting the authentication process.

The following figure denotes the interactions between Amdatu Security and a particular identity provider, in this case, Google OpenID Connect.

as idp interaction

As can be seen from the figure above, the identity provider provides has two responsibilities:

  1. obtaining the exact location of the remote identity provider, such as Google OpenID connect in our example, and;

  2. obtaining the user identity once the remote identity provider has acknowledged the identity of the principal. The user identity consists of several credentials that are returned to Amdatu Security.

In the following sections, each identity provider is discussed in more detail.

OpenID Connect identity provider

OpenID Connect is an authorisation abstraction on top of OAuth2, and considered as the logical successor to OpenID. There are many third parties that provide support for OpenID Connect, like Google, Microsoft and Okta.

To make use of an OpenID Connect provider, you need to supply a configuration to the managed service factory with the PID org.amdatu.security.authentication.idprovider.openidconnect with the following properties:

name

(required, string) defines a unique name for your OpenID Connect identity provider. This type is used to differentiate between multiple OpenID Connect identity providers and typically consists of a single, lowercased, word;

clientId

(required, string) the client identifier as provided by the OpenID Connect identity provider;

clientSecret

(required, string) the client secret as provided by the OpenID Connect identity provider;

openIdConfig

(optional, JSON string) the OpenID Connect metadata, as defined in the OpenID Connect Discovery specification. This property should contain the JSON blob that represents the discovery metadata;

openIdConfigURL

(optional, URL) the OpenID Connect Discovery endpoint. It should return the same JSON blob with the metadata of your OpenID Connect provider.

claims

(optional, string array) the JWT claims that should be extracted from the user token obtained from the OpenID Connect provider. If omitted, the following claims are extracted: sub, name, given_name, family_name, middle_name, nickname, preferred_username, profile, picture, website, email, email_verified, gender, birthdate, zoneinfo, locale, phone_number, phone_number_verified, address, updated_at, upn, unique_name;

scopes

(optional, string) the space separated list of scope(s) to request access for at the OpenID Connect provider. Defaults to openid email profile;

responseType

(optional, string) the type of response an OpenID Connect provider should return. The value depends on the various flows supported by OpenID Connect. If omitted, the default code flow is used;

responseMode

(optional, string) defines the method by which the OpenID Connect provider should return its response. Either form_post or query responses are supported. Defaults to query if omitted;

callUserInfoEndpoint

(optional, boolean) defines whether or not the “user info” endpoint should be called to obtain the extra user claims. Several OpenID Connect providers already return basic user information without an explicit call to the “user info” endpoint. Defaults to true;

stateTimeout

(optional, integer) the time out, in seconds, a state value is considered valid. State values are temporary token that are passed between Amdatu Security and the (remote) OpenID Connect provider. If omitted, state tokens will be valid for 120 seconds (2 minutes);

secureRandomAlgorithm

(optional, string) defines what algorithm to use for the secure number generator. If omitted, the platform default will be used (see Java Security Architecture documentation for details).

Either a openIdConfig or a openIdConfigURL property should be provided!

The bare minimum configuration to use Google as remote OpenID Connect provider looks like:

name = google
clientId = «client identifier»
clientSecret = «client secret»
openIdConfigURL = https://accounts.google.com/.well-known/openid-configuration

The credentials returned by this identity provider are:

  • a providerType property that is set to oidc;

  • a providerName property that matches the name of the OpenID Connect identity provider that handled the authentication request;

  • optionally, an accessToken property as given by the OpenID Connect provider after a successful authentication request. Can be used to access additional services from the OpenID Connect provider (for example, access Google Docs);

  • optionally, a refreshToken properties that can be used to request a new access token without explicit user consent;

  • any of the claims/public credentials that were configured to be included in the user identity.

OpenID identity provider

If you want to use OpenID as identity provider, like Yahoo or Steam, you need to supply a configuration to the managed service factory with the PID org.amdatu.security.authentication.idprovider.openid with the following properties:

name

(required, string) defines a unique name for your OpenID identity provider. This type is used to differentiate between multiple OpenID identity providers and typically consists of a single, lowercased, word;

endpoint

(required, URI) defines the endpoint where the OpenID discovery settings can be found. This endpoint is queried to obtain the exact details for communicating with the OpenID provider.

An example configuration could look like:

name = yahoo
endpoint = https://me.yahoo.com/

The credentials returned by this identity provider consist of:

  • a providerType property that is set to openid;

  • a providerName property that matches the name of the OpenID identity provider that handled the authentication request;

  • a verifiedId property that represents the OpenID identity of the user (actually an URI).

Local identity provider

The local identity provider can be used if you already have a, or want to manage your own, ``database`" of principals that you want to provide access to your application. The premises for using this provider are:

  1. you can identify a principal by a set of credentials, for example, username and password;

  2. each principal has an identifier that uniquely identifies that principal in your “database”.

To instantiate a local identity provider, you need to supply a configuration to the managed service factory with the PID org.amdatu.security.authentication.idprovider.local. This configuration consists of the following properties:

name

(required, string) defines a unique name for your local identity provider. This type is used to differentiate between multiple local identity providers and typically consists of a single, lowercased, word;

signingKeyUri

(required, URI) defines the secret key for signing requests and validate responses in the local identity provider. The following values are accepted:

  • random:<N> generates a random (cryptographically secure) string of N bytes;

  • data:<B64> uses a fixed set of bytes, provided as a base64 encoded string;

  • file:<path> uses the bytes from a given file.

encryptionKeyUri

(optional, URI) defines the encryption key for encrypting and decrypting requests/responses in the local identity provider. It accepts the same values as signingKeyUri. If omitted, the signing secret key is used to encrypt the data. However, it is strongly recommended that you provide a different encryption key;

idCredentialProviderSelect

(optional, string) represents a LDAP-style filter that is used to lookup the LocalIdCredentialProvider service that should be used to lookup principals. If omitted, the highest ranked service will be used. Note that there should be at least one LocalIdCredentialProvider present when using the local identity provider;

timestepWindow

(optional, integer) defines the amount of time, in seconds, an “authentication token” will be valid for this local identity provider. Defaults to 30 seconds if omitted;

secureRandomAlgorithm

(optional, string) defines what algorithm to use for the secure number generator. If omitted, the platform default will be used (see Java Security Architecture documentation for details).

The bare minimum configuration could look like:

name = local
signingKeyUri = random:32

The credentials returned by a local identity provider consist of:

  • a providerType property that is set to local;

  • a providerName property that matches the name of the local identity provider that handled the authentication request;

  • a localId property that represents the local identity of the user;

  • any additional public credentials as returned by the LocalIdCredentialProvider#getPublicCredentials method.

Managing local identities

Multiple local identity providers can be configured for a single application. For instance, one can have a local identity provider to provide access to end- users, and another local identity provider to provide access to external applications. To facilitate this, each local identity provider uses a LocalIdCredentialProvider that provide the local identity information based on a set of given credentials.

There is an important difference between a PrincipalLookupService and a LocalIdCredentialProvider. The former translates a identity from any identity provider to an identity of your application. In contrast, the latter verifies local identities. It can be perfectly fine for your LocalIdCredentialProvider to directly return the identity used in the rest of your application. In that case, the PrincipalLookupService can just return such identities as-is.

…​

Resources