Skip to main content

Using a Credential service for single sign-on

A credential service can be used to provide credentials to perform single sign-on to protected applications. Single sign-on can be achieved using basic authentication or using a mechanism which automatically submits forms-based logins.

Credential Service

A credential service is a light-weight web service which implements two endpoints, one for retrieving credentials and another for storing credentials. At runtime, IAG will contact this service to retrieve the credentials when single sign-on is performed.

Note that storing credentials is only performed when using forms-based login with credential learning enabled.

  • Refer to the services/credential YAML reference for information about defining a credential service to IAG.
  • Refer to identity-headers#basic_auth for configuring IAG to provide credentials from the credential service as basic authentication headers.
  • Refer to forms_login for configuring IAG to complete forms-based logins using credentials from the credential service.

IBM Security Verify

A credential service will be available in an upcoming IBM Security Verify update.

IBM Security Verify Access

A credential service will be provided in a future version of IBM Security Verify Access as part of the Advanced Access Control capability.

Implementing a Credential Service

Credential Service Endpoints

A credential service is configured by providing a URL pattern to IAG. The credential service URL must contain the '{resource}' and '{user}' placeholders. IAG will substitute in the resource name and effective username when making requests to this service.

  • The resource name is specified in the resource server configuration which uses this credential service for single sign-on.
  • The effective username is taken from a credential attribute. This can be configured for each credential service. The default credential attribute used is 'AZN_CRED_PRINCIPAL_NAME'.

The credential service must be able to respond to GET and PUT requests on the provided URL pattern when populated.

For example, a credential service with the following URL pattern '/credentials/resources/{resource}/users/{user}' must implement the following two endpoints:

Retrieving Credentials

To retrieve credentials, IAG will issue a GET request to the configured endpoint.

> GET /credentials/resources/{resource}/users/{user}

< 200 OK
< {
<  "username": <username>,
<  "password": <password>
< }

The endpoint must return at a minimum:

  1. Any HTTP status code indicating success (any status code beginning with 2xx)
  2. A JSON body containing the 'username' and 'password' fields. Any other fields will be ignored.

Storing Credentials

When storing credentials, IAG will issue a PUT request to the configured endpoint. The request will contain a JSON body with the 'username' and 'password' fields.

> PUT /credentials/resources/{resource}/users/{user}
> {
>   "username": <username>,
>   "password": <password>
> }

< 201 CREATED

The endpoint must return at a minimum:

  1. Any HTTP status code indicating success (any status code beginning with 2xx)

Encoding the '{user}' URL token

By default, when constructing URLs the '{user}' token is URL encoded. IAG can optionally Base64URL encode this token which is recommended when '{user}' values contain characters which require percent-encoding. Refer to the property 'user_attribute_encoding' in the services/credential YAML reference.

When Base64URL encoding is enabled, IAG will indicate to the credential service that the '{user}' token is encoded by including a query string parameter 'encoding=base64url'.

For example, consider the URL generated for a '{resource}' named 'testResource' with a '{user}' token '星の白金':

# with user_attribute_encoding set to 'base64url':
GET /credentials/resources/testResource/users/5pif44Gu55m96YeR?encoding=base64url

# with user_attribute_encoding unset or set to 'url':
GET /credentials/resources/testResource/users/%E6%98%9F%E3%81%AE%E7%99%BD%E9%87%91

Encryption of Credentials

IAG stores credential passwords as JSON Web Encryption (JWE) tokens. An RSA or ECDSA key must be provided to IAG to perform the encryption operations.

Credential passwords provided by the credential service should conform the following standards used by IAG:

  • The password string must begin with the '{jwe}' prefix to indicate that it is a JWE
  • Following the '{jwe}' prefix must be a JWE representation of the password. This JWE:

    • Must use a 'kid' value which is the label of the certificate IAG will use for decryption
    • Must use the 'enc' value 'A256GCM'
    • For RSA keys, must use one of the following encryption algorithms for 'alg': 'RSA1_5' or 'RSA_OAEP'
    • For ECDSA keys, must use the following encryption algorithm for 'alg': 'ECDH-ES'

If IAG is returned credentials which do not contain the '{jwe}' prefix, they will be treated as in-the-clear and used as is. Storing or providing credentials to IAG in-the-clear is considered unsafe and should not be performed.


Sample IAG JWE Generator/Validator

The following script can be used be used to generate/validate encrypted IAG JWE credential password values.

#!/usr/bin/env python3

import json
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from jwcrypto.jwe import JWE
from jwcrypto.jwk import JWK

class IAGJWE(object):

    def __init__(self, pem):
        """
        This class can be used to generate/verify IAG JWEs.

        :param pem:             Path to the key which is used for crypto.
        """

        # Load the pem data
        with open(pem, "rb") as fh:
            self.pem_data = fh.read()

        # Create a JWK
        self.jwk = JWK.from_pem(self.pem_data)

        # Work out the KID
        self.cert = x509.load_pem_x509_certificate(self.pem_data, backend=default_backend())
        self.kid = self.cert.subject.rfc4514_string()

        # Determine the algorithms which should be used
        signature_algorithm = str(self.cert.signature_algorithm_oid)

        if "ecdsa" in signature_algorithm:
            self.jwe_alg = "ECDH-ES"
        elif "RSA" in signature_algorithm:
            self.jwe_alg = "RSA1_5"
        else:
            print("Invalid signature algorithm ({0})".format(signature_algorithm))
            print("Use an RSA or ECDSA key.")

    def generate_password_jwe(self, password):
        """
        Generates an IAG password JWE.

        :param password: The password to generate the JWE representation for.
        :return:         A string containing the JWE representation, in the 
                         IAG "{jwe}xxx" format.
        """
        # Produce the JWE
        jwe = JWE(password.encode(), json.dumps({
            "alg": self.jwe_alg,
            "enc": "A256GCM",
            "kid": self.kid
        }))
        jwe.add_recipient(self.jwk)

        # Print it in the IAG credential service format
        return "{jwe}" + jwe.serialize(compact=True)

    def validate_password_jwe(self, raw_jwe):
        """
        Validates an IAG password JWE.

        :param raw_jwe: The IAG password JWE, in the IAG "{jwe}xxx" format.
        :return:        The decrypted password.
        """
        # Deserialize and decrypt the JWE
        jwe = JWE()
        jwe_payload = jwe.deserialize(raw_jwe[5:], key=self.jwk)

        return jwe.plaintext.decode()


def usage():
    print("Usage: {0} validate <key> <string>".format(sys.argv[0]))
    print("Usage: {0} generate <key> <string>".format(sys.argv[0]))
    exit(1)


if __name__ == "__main__":

    import sys

    if len(sys.argv) != 4:
        usage()

    if sys.argv[1] == "validate":
        jwe = IAGJWE(sys.argv[2])
        print(jwe.validate_password_jwe(sys.argv[3]))
    elif sys.argv[1] == "generate":
        jwe = IAGJWE(sys.argv[2])
        print(jwe.generate_password_jwe(sys.argv[3]))
    else:
        usage()

    exit(0)