Overview
This document provides a comprehensive overview of the authentication hooks available for customizing authenticators. It details each hook’s purpose, usage, available parameters, identifies various existing authenticator types, and specifies which hooks are applicable to each.
The following hooks are defined:
get_secret_keys
: Identifies sensitive credential fields to mask in logs. This is only needed when you are using fields not currently designated as senstive fields or have created your own credential. In most cases this is not needed.get_token_fields
: Maps credential fields to token request parameters. This hook is intended to be used when using a custom credential with non-standard key names, when desiring custom validation logic, or when fields need additional processing before being used.get_api_key_fields
: Maps credential fields to token request parameters. This hook is intended to be used when using a custom credential with non-standard key names, when desiring custom validation logic, or when fields need additional processing before being used.generate_cache_key
: Generates a unique cache key for the authenticator instance.modify_token_session
: Modifies any sesssion parameters before the token request is sent.process_response
: Processes the raw token response.modify_session
: Modifies the HTTP session for authenticated requests.
Hooks
get_secret_keys -> List[str]
This hook identifies which additional credential fields contain sensitive
information that should be hidden from logs and debugging output. It returns a
list of field names that the credential filter will mask. This is crucial for
security compliance and preventing accidental exposure of secrets in application
logs. In practice this might include fields like client_secret
,
private_key
, password
, or any other sensitive authentication data
specific to your service or credential. It must return a List[str]
. The
default secret fields username
, password
, proxy_username
, and
proxy_password
will be masked whether the hook is provided or not. If this
hook is not provided, no additional fields will be masked but the default fields
will continue to be masked as usual.
When should I use this?
This hook is intended to be used when using a custom credential or when storing secret values in nonstandard credential fields.
Available Parameters:
metadata: Dict[str, Any]
logger: logging.Logger
credential: silo.apps.collection.Credential
Return Type:
List[str]
List of credential field names to mask
Examples:
def custom_secret_fields(
metadata: Dict[str, Any],
credential: Credential
) -> List[str]:
return [
"oauth2ClientSecret",
"private_key",
"api_key"
]
def get_secret_keys(
metadata: Dict[str, Any],
credential: Credential
) -> List[str]:
secret_fields = [
"oauth2ClientId",
"oauth2ClientSecret",
]
if metadata["oauth_type"] == "ResourceOwner":
secret_fields.append("oauth2ResourceOwnerUsername")
secret_fields.append("oauth2ResourceOwnerPassword")
return secret_fields
get_token_fields -> token_fields
This hook is responsible for mapping fields from the credentials to the specific format required by the Token Authenticator. It returns a named tuple, with the field mapping.
token_url (str): The endpoint to request the token from
token_label (str): The header name to use when sending the token (e.g. Authorization)
token_format (str): How to format the token (e.g. Bearer {})
token_key (str): The dictionary key in the formatted response that contains the token
Input Parameters:
metadata: Dict[str, Any]
logger: logging.Logger
credential: silo.apps.collection.Credential
Return Type: Each authentication function utilizing a field configuration hook features a uniquely named hook, reflecting the variations in configurable fields for each type. The definition of the named tuple containing the field information also varies according to the specific authentication type. Below, you will find the definitions of the hook function names and their corresponding field structures. If no hook is provided, the fields will fall back to the defaults listed.
#create_token_authenticator
get_token_fields() -> token_fields
token_fields # NamedTuple with:
# token_url: str (Default: credential.fields["auth_endpoint"])
# token_label: str (Default: credential.fields["auth_header"])
# token_format: str (Default: credential.fields["token_format"] or "{}")
# token_key: str (Default: credential.fields["token_key"])
# create_api_key_authenticator()
get_api_key_fields() -> api_key_fields
api_key_fields # NamedTuple with:
# token_label: str (Default: credential.fields[auth_header"] or "Authorization")
# prefix: str (Default: credential.fields["prefix"])
# suffix: str (Default: credential.fields["suffix"])
# api_key: str (Default: credential.fields["apikey"])
Example:
def custom_field_mapping(
credential: Credential,
logger: logging.Logger
) -> token_fields:
return token_fields(
token_url="<https://api.example.com/token>",
token_label="Authorization",
token_format="Bearer {}",
token_key="access_token"
)
get_api_key_fields -> api_key_fields
token_label (str): The header name to use when sending the API key
prefix (str): A string to prepend to the front of the API key
suffix (str): A string to append to the end of the API key
apikey (str): The API key itself
When should I use this? This hook is intended to be used when using a custom credential with non-standard key names, when desiring custom validation logic, or when fields need additional processing before being used.
generate_cache_key -> str
This hook generates a unique identifier for the cache key of each authenticator
instance. This unique cache key is used to retrieve a cached token for the
correct authenticator instance and to prevent unnecessary re-authentication.
It must return a str
. If this hook is not provided, a unique identifier
will be generated using all the fields in the credential.
When should I use this?
This hook is intended to be used when needing to set a specific cache key to use for storing token data. It should identify your authenticator instance uniquely.
Available Parameters:
metadata: Dict[str, Any]
logger: logging.Logger
credential: silo.apps.collection.Credential
Return Type:
str
Unique identifier for cache key
Examples:
def get_unique_id(
metadata: Dict[str, Any],
credential: Credential
) -> str:
return f"{credential.fields['client_id']}-{credential.fields['endpoint']}"
def generate_cache_key(
metadata: Dict[str, Any],
credential: Credential
) -> str:
headers = credential.fields.get("headers", {})
header_list = [(key, value) for key, value in headers.items()]
header_list.sort()
headers_string = f"{header_list}"
username = credential.fields["username"]
token_fields = metadata["token_fields"]
key = f"{username}-{token_fields.token_url}-{headers_string}-{token_fields.token_label}-{token_fields.token_format}-{token_fields.token_key}"
return key
def generate_gcp_cache_key(credential: Credential) -> str:
pem_key = credential.fields["oauth2ClientSecret"].replace("\\n", "\n")
pem_hash = hashlib.sha256(pem_key.encode()).hexdigest()
return f"{credential.fields['oauth2ClientId']}{pem_hash}"
get_token_request_args -> Dict[str, Any]
This hook allows the user to return a Dict[str, Any]
that updates the
arguments passed in to the request to retrieve the token. This hook supports all
the parameters mentioned in requests.Session.request
. It may be needed to
alter the authentication process to:
Format credentials in the way the authentication endpoint expects
Add necessary headers, parameters, or body content
Transform credentials into the proper format for the specific API
By default, the token request will be called with the post
method if the
data
or json
parameter is fulfilled. The url for the token request as
defined either in the credential or by the get_token_fields
hook, and the
headers, proxies, and verify SSL settings as defined in the credential. Any
fields defined by the result of this hook will overwrite the default values.
If this hook is not provided, the request will be called with all the default
values listed above, along with a json
body object containing the username and
password defined in the credential.
When should I use this?
This hook is intended to be used when needing to modify how the request to
retrieve the token is formed, such as leveraging the data
parameter instead
of json
in the request or adding additional fields.
Available Parameters:
metadata: Dict[str, Any]
logger: logging.Logger
credential: silo.apps.collection.Credential
Return Type:
Dict[str, Any]
Keyword arguments for requests.Session.request()
Supported Keys (from requests library):
method: str
url: str
params: Dict[str, str]
(query parameters)data: Dict[str, Any]
(form data)json: Dict[str, Any]
(JSON body)headers: Dict[str, str]
cookies: Dict[str, str]
files: Dict[str, Any]
auth: Tuple[str, str]
(basic auth)timeout: float
allow_redirects: bool
proxies: Dict[str, str]
verify: bool
stream: bool
cert: str
Examples:
def get_token_request_data(credential: Credential) -> Dict[str, Any]:
return {
"method": "POST",
"json": {
"grant_type": "client_credentials",
"client_id": credential.fields["client_id"],
"client_secret": credential.fields["client_secret"]
},
"headers": {
"Content-Type": "application/json"
},
"params": {
"scope": "read write"
}
}
def get_token_request_args(credential: Credential) -> Dict[str, Any]:
return({
"json": {
"auth": {
"methods": ["password"],
"password": {
"user": {
"name": credential.fields["username"],
"domain": {
"id": "default"
},
"password": credential.fields["password"]
}
}
}
},
})
def get_jwt_token_request_args(credential: Credential) -> Dict[str, Any]:
issuer_claim = "iss"
scope = "scope"
audience_claim = "aud"
expires_claim = "exp"
issued_at_claim = "iat"
current_time = int(time.time())
payload = {
issuer_claim: credential.fields["oauth2ClientId"],
scope: credential.fields["oauth2ClientCredentialsScope"],
audience_claim: credential.fields["oauth2TokenURL"],
expires_claim: current_time + credential.fields["tokenRefreshTTL"],
issued_at_claim: current_time,
}
signature = jwt.encode(
payload,
credential.fields["oauth2ClientSecret"].replace("\\n", "\n"),
algorithm="RS256",
headers={"alg": "RS256", "typ": "JWT"},
)
return {
"data": {
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": signature,
},
}
def get_oauth_token_request_args(
metadata: Dict[str, Any],
credential: Credential
) -> Dict[str, Any]:
headers = {"Content-Type": "application/x-www-form-urlencoded"}
headers.update(credential.fields.get("headers", {}))
oauth_req_info = metadata["oauth_req_info"]
return {
"headers": headers,
"data": oauth_req_info.body,
"auth": oauth_req_info.client_info,
}
process_response -> token_response_fields
This function processes the token response from the token_url
endpoint.
It’s useful when the response format is nonstandard or must be converted into
text, xml, etc. It can also be useful for extracting specific data from the
token response such as if the token is returned in the response headers, is
found in a nested structure, or otherwise needs transformation. It must return
a token_response_fields
named tuple containing:
expires_in
(int): The time in seconds that the token is valid fortoken
(str): The token to use for subsequent requests
If this hook is not provided, the token_response_fields
attributes will be
set by handling the default logic that leverages the credential and reads the
body of the response as JSON. If a static token TTL is set, it will be used as
the expires_in
time. Otherwise if dynamic token refresh is selected, it will
attempt to retrieve the specified expiry key from the token response. To
retrieve the token, it will attempt to retrieve the credential defined token
payload key from the token response.
When should I use this?
This hook is intended to be used when needing to modify where the token
information is located in the response or how it is accessed. This would be
useful if the token is found in the headers instead of the default location of
the body, or if the expires_in
time is found elsewhere from the token.
Available Parameters:
metadata: Dict[str, Any]
logger: logging.Logger
response: requests.Response
Return Type:
token_response_fields
Named tuple with:
expires_in: Optional[int]
# Seconds until token expirestoken: str
# The actual token value
Examples:
def custom_response_processor(response: requests.Response) -> token_response_fields:
data = response.json()
return token_response_fields(
expires_in=data.get("expires_in"), # Can be None
token=data["access_token"]
)
def process_response(
response: requests.Response,
) -> token_response_fields:
# Expiry time is found in the headers and is in minutes
expiry_time_minutes = response.headers["inner"]["expires_in"]
# Convert expiry time into seconds
expires_in = expiry_time_minutes * 60
# Token is found in the body
token = response.json()["access_token"]
return token_response_fields(
expires_in=expires_in,
token=token,
)
def process_open_stack_response(
response: requests.Response
) -> token_response_fields:
return token_response_fields(
expires_in=None, # No expires_in time returned. Static TTL expected to be set
token=response.headers["X-Subject-Token"],
)
modify_session -> None
This hook customizes how the authenticator modifies HTTP sessions for subsequent API calls after authentication is complete. It allows you to set custom headers, configure proxies, or implement specialized authentication schemes. This is useful when the target API requires non-standard authentication methods, such as custom header formats, token needing to be set in the body, multiple authentication tokens, or when you need to configure session-level settings like SSL verification or connection pooling based on the authentication context.
If this hook is not provided, the session will have its auth
property set to
the generated TokenAuth
named tuple, which when called sets the session
header token_label
key to the formatted token.
When should I use this?
This hook is intended to be used when needing to modify subsequent requests that
might expect the token to be set in a nonstandard location for authentication
where the typical approach of setting the session auth
property is not
sufficient.
Available Parameters:
metadata: Dict[str, Any]
logger: logging.Logger
session: requests.Session
auth_info: TokenAuth
Return Type:
None
Modifies session in-place
Example:
def custom_modify_request(session: requests.Session, auth_info: TokenAuth) -> None:
# Modify session headers
session.headers["X-Auth-Token"] = auth_info.token
session.headers["X-API-Version"] = "v2"
# Or modify other session properties
session.verify = False
session.timeout = 30
def modify_session(session: requests.Session, auth_info: TokenAuth) -> None:
session.headers["X-Auth-Token"] = auth_info.token
Hook Execution Order
Hooks are executed in a particular order as follows. It is important to understand this order since hooks are usually dependent on data provided by previous hooks. It should be noted that some of these hooks occur on every API request and some only occur during a token refresh.
get_secret_keys
– This hook executes on every API requestget_token_fields
- This hook executes on every API requestget_api_key_fields
- This hook executes on every API requestgenerate_cache_key
- This hook executes on every API requestget_token_request_args
– This hook executes ONLY when a token refresh occursprocess_response
- This hook executes ONLY when a token refresh occursmodify_session
- This hook executes on every API request
Hook Name |
Hook Order |
Input Paramters |
Return Type |
Purpose |
Scope |
---|---|---|---|---|---|
get_secret_keys |
1 |
Credential, Logger, Metadata |
|
Identify sensitive credential fields to mask in logs. This is called for every API request |
All Authenticators |
get_api_key_fields |
2 |
Credential, Logger, Metadata |
|
NOTE this is only needed when creating a custom credential. |
API Key |
get_token_fields |
2 |
Credential, Logger, Metadata |
|
NOTE this is only needed when creating a custom credential. |
Token Auth |
get_token_request_args |
3 |
Credential, Logger, Metadata |
Dictionary containing anything defined in |
Map credential fields to token request parameters. This is called for every API request. |
Token Auth, OAuth2 |
generate_cache_key |
4 |
Credential, Logger, Metadata |
|
Generate a unique cache key for the authenticator instance. This is called for every API request. |
All |
process_response |
5 |
Response, Refresh_info, Logger, MetaData |
|
Process the raw token response. When this hook is NOT called, the following occurs:
|
Token Auth, OAuth2 |
modify_session |
6 |
Session, Auth_info, Logger, Metadata |
|
Modify the HTTP session for authenticated requests. |
All |
Input Parameter Definitions
Credential – Refer to the detailed Credential Fields info.
Response – Refer to requests.Response
refresh_info -
refresh_info.type
- is either “static” or “tokenAuthRefreshDynamic” as set by the credentialrefresh_info.ttl
- is an int and defines the time a token is valid for. For static this comes from the credential and for dynamic it normally comes in the response to the token request.
Session – Refer to Advanced Usage - Session Objects
Auth_info – This field contains information about authentication, this varies based on the type of authentication used.
Logger - This can be leveraged for debugging purposes. Refer to Logging
Metadata - Refer to the Metadata Fields section for details.
Parameter Validation
The system uses introspection to validate hook signatures:
Only parameters that exist in the hook’s function signature are passed
If a hook requests a parameter that doesn’t exist, an
InputTypeError
is raisedHooks can request any subset of available parameters for their execution context