Credential Service
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.
Implementing your own 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:
- Any HTTP status code indicating success (any status code beginning with 2xx)
- 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:
- 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 convert the token to lower case and Base64URL encode it, 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
For example, consider the URL generated for a '{resource}' named 'testResource' with a '{user}' token 'Sample_User_Account_[email protected]':
## with user_attribute_encoding set to 'base64url':
## note that the token '[email protected]' is converted to the lower case
## representation '[email protected]' before the encoding takes place.
GET /credentials/resources/testResource/users/c2FtcGxlX3VzZXJfYWNjb3VudF8xQHRlc3QuY29t?encoding=base64url
## with user_attribute_encoding unset or set to 'url':
GET /credentials/resources/testResource/users/Sample_User_Account_1%40test.com
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)
kid validation
The JWE decryption performed by the IAG performs kid validation. The kid sent in the JWE must match the kid of the private key held by the IAG. If no kid is set for the certificate, the certificate label (friendly name) will be used. By default, the certificate label is set to the Subject DN of the certificate.
This validation is NOT performed by the example script above.
Updated 2 months ago