Authenticators

Unfortunately, there is no one standard that defines how authentication nor do vendors fully implement the current standards for authentication. Therefore the Snippet Framework supports the ability to customize your authentication flow. As previously mentioned there are five out-of-the-box authenticators that can be leveraged. These authenticators are described under the REST Authentication section.

When building a custom authenticator it is recommended to start with one of the above authenticators and modify the code accordingly.

Authenticator ABC

All the authenticators must inherit from the same ABC (Abstract Base Class) and thus all have similar structure. Below is an implementation of an Authenticator class that provides no authentication at all. This shows all the required methods that must be defined in a brand new authenticator.

 1from silo.auth import add_auth, Authenticator
 2
 3@add_auth
 4class AuthNone(Authenticator):
 5    """AuthNone encapsulates the authentication lifecycle for no authentication.
 6    :param Authenticator Authenticator: Parent ABC Class
 7    :param silo.apps.collection.credentials.Credential credentials: credentials
 8        retrieved from em7/UCF
 9    """
10
11    def __init__(self, credentials, **kwargs):
12        """Constructor method"""
13        Authenticator.__init__(self, credentials)
14
15    def check_if_authenticated(self):
16        """
17        Required for ABC implementation. Since there is nothing to authenticate against
18        we will always return True.
19        :return: True
20        :rtype: boolean
21        """
22        return True
23
24    def check_result(self, result):
25        """
26        Provides a check on the returned response from the REST request that was made.
27        :param response result: Response object after making a REST call
28        :return: True
29        :rtype: boolean
30        """
31        return True
32
33    def authenticate(self):
34        """
35        Authenticate is a no-op for NoneAuth
36        :return: True
37        :rtype: boolean
38        """
39        return True
40
41    def modify_request(self, res):
42        """Function to apply authorization implementation for request session
43        :param session session: Request to be updated with credentials
44        :return: True
45        :rtype: boolean
46        """
47        res.auth = None
48        return True
49
50    @staticmethod
51    def build(credentials, **kwargs):
52        """Build method to be called by get_authmethod in low_code"""
53        return AuthNone(credentials, **kwargs)
54
55    @staticmethod
56    def get_name():
57        return "AuthNone"
58
59    @staticmethod
60    def get_desc():
61        return "No Authentication for HTTP"

The most important functions to understand are as follows:

  • authenticate() – This function is called before making any API request. Its job is to make sure that authentication has occurred. It is present in all authenticators and doesn’t really do anything in AuthBasic though becomes very important for the token based authentication schemes. In those cases its function is to make the API request to the token server and then stores that token for later use by modify_request().

  • modify_request() - This function is called after authenticate() has been performed and its job is to modify the session with the authentication information. This function has access to the session and thus can add the token to the headers e.g. in AuthToken. However, it can be easily modified e.g. to place the token in a query parameter.

  • get_name() - This function helps define the look up name for the authenticator. The returned value in this function will be the same as the credential’s Authenticator Override value.

Note

The out-of-the-box http step uses the Requests library. Request sessions must be understood as the parameters of a session is what normally needs to be modifed by modify_request(). See Request Sessions

Logging

The Authenticator provides a logger with which to debug authentication issues. This logger provides credential masking if used in conjunction with the authenticator’s credential filter object.

from silo.auth import auth_logger

In order for these debug logs to be visible, the aligned credential must have its Logging Debug value set to True.

Warning

Turning on debug runs the risk of recording a secret to the logs.

Credential Masking/Filtering

Credentials can be masked or filter from the auth_logger output. Suppose we had a AuthBasic whose password was lowcodepassword. When logging is enabled then our logs that would have had lowcodepassword is replaced with *****. Again, the secret is replaced with five asterix.

Adding a secret for masking
from silo.auth import add_auth, Authenticator

@add_auth
class AuthExampleCredFilter(Authenticator):

    def __init__(self, credentials, **kwargs):
        """Constructor method"""
        Authenticator.__init__(self, credentials)
        self.cred_filter.update_secret("secret", "lowcodepassword")
docstring for update secret
def update_secret(self, key, secret):
    """Function to add/modify an additional secret for log removal

    This function will transform the credentials into unicode
    and store its entries as secret items in self._secrets.

    :param str key: key for secret into dict
    :param str secret: actual secret text to remove from log
    """

Note

The key parameter is used for subsequent updates to a secret that must be masked. For example, a token that will be refreshed will need to be updated and the previous secret will no longer need to be masked.

Authenticator Override

Before we dive into developing new or augmenting an existing authenticator, we need to understand how to reuse the Low-code tools: rest v101 credential type in order to point to a different authenticator while stilling being able to re-use existing credential fields.

Below is an image of the Low-code tools: rest v100 credential type. On the bottom right of the image, is a field called Authenticator Override.

../_images/authenticator_override.png

The contents of this field must match the output of the get_name() function. Below is a shortened example of an authenticator. Its name is AuthCustom. If we wanted to use this new authenticator then we would need to update the credential field, Authenticator Override, to be AuthCustom.

@add_auth
class AuthCustom(Authenticator):
    # There are more methods not shown

    @staticmethod
    def get_name():
        return "AuthCustom"

In the following examples, after we create our authenticator we will need to update the Authenticator Override field to match our get_name() value.

Credential Fields

When using Authenticator Override, your custom authenticator has access to the following fields from the credentials.fields dictionary. When creating a custom authenticator, we will be reusing these fields to implement new authentication flows.

Note

Not all fields are view-able/visible from the Credential UI. In the table below, Visibility (AuthType) denotes when a field will be visible given an Authentication Type.

Available Credential Fields

Display Name

credentials.fields Name

Visibility (AuthType)

Type

Default Value

Description

Authentication Type

authType

All

string

AuthNone

Fixed string defining the authenticator to use i.e. AuthNone, AuthBasic, AuthToken, AuthApiKey, and AuthOAuth2

URL

url

All

string

“”

URL that should be used to retrieve device data [http(s)://Host:Port/Path]

Token Retrieval Endpoint

authEndpoint

AuthToken

string

“”

Fields indicates that the token should be retrieved from a different API

Authorization Header

authHeader

AuthApiKey, AuthToken

string

Authorization

Where the authentication server expects the key/token from the request header.

Token Key

tokenKey

AuthToken

string

access_token

Key value in the JSON device response that contains the token value to extract. Example: {"token": "tokenvalue123"}. Providing "token" would pull out tokenvalue123

Bearer Token Format

tokenFormat

AuthToken

string

Bearer {}

Formulate how the bearer token should be configured and sent. The typical format is "Bearer {}" but varies per API. {} must be included so that the token is inserted in that location.

Username

username

AuthBasic, AuthToken

string

“”

Username for authentication

Password

password

AuthBasic, AuthToken

string

“”

Password for authentication

Proxy Settings

proxy

All

boolean

False

Toggle-able that allow users to input proxy settings

Proxy Hostname/IP

proxyHost

All

string

“”

hostname/IP of your proxy server (optional)

Proxy Port

proxyPort

All

number

“”

Port used by your proxy server

Proxy User

proxyUser

All

string

“”

Proxy user (optional)

Proxy Password

proxyPasswd

All

string

“”

Proxy password (optional)

HTTP Header 1

httpHeader1

AuthToken, AuthApiKey

string

“”

Optional HTTP header (e.g. X-Sample-Header:Sample Value)

HTTP Header 2

httpHeader2

AuthToken, AuthApiKey

string

“”

Optional HTTP header (e.g. X-Sample-Header:Sample Value)

HTTP Headers (JSON input)

httpHeaders

All

string

“”

Accepts additional header values in the form of JSON. Example: {"header1": "key1", "header2": "key2"}

SSL Peer Verify

sslverifypeer

All

boolean

True

This option determines whether the authenticity of the peer’s certificate should be verified. False means to have an insecure connection.

CA Path

ca_path

All

string

“”

This option specifies the Certificate Authority certificate path to use when making HTTPS requests.

Authentication Failure Retry Time (seconds)

timeToNextRequest

AuthToken, AuthOAuth2

number

60

Number of seconds before retrying a failed authentication request.

Additional Options

opts

AuthToken, AuthApiKey

string

“”

Additional Options (switch that creates additional options field that accepts JSON).

Logging Debug

debug

All

boolean

False

Enables the logging authentication debug output

Token Refresh Implementation

tokenAuthRefresh

AuthToken, AuthOAuth2

string

tokenAuthRefreshDynamic

Refresh algorithm choice. Options tokenAuthRefreshStatic or tokenAuthRefreshDynamic

Token TTL (seconds)

tokenRefreshTTL

AuthToken, AuthOAuth2

number

3600

TTL of the token (in seconds) before auto-refreshing for tokenAuthRefreshStatic

Expiry Time Key

tokenExpiresInSelector

AuthToken, AuthOAuth2

string

expires_in

The key for the expiry time within the JSON response

Client ID

oauth2ClientId

AuthOAuth2

string

“”

Your application’s Client ID

Client Secret

oauth2ClientSecret

AuthOAuth2

string

“”

Your application’s Client Secret

Access Token URL

oauth2TokenURL

AuthOAuth2

string

“”

URL for generating the Oauth2 token

Request Header

oauth2TokenLabel

AuthOAuth2

string

Authorization

Header utilized when making requests with the token

Token Format

oauth2TokenFormat

AuthOAuth2

string

Bearer {}

Formulate how the bearer token should be configured and sent. The typical format is "Bearer {}" but varies per API. {} must be included so that the token is inserted in that location.

Response Token Key

oauth2TokenKey

AuthOAuth2

string

access_token

The key that contains the token within the JSON response

Oauth2 Grant Type

oauth2Type

AuthOAuth2

string

None

Supported authentication methods for Oauth2 i.e. None, ResourceOwner, or ClientCredentials

Client auth method

oauth2AuthType

AuthOAuth2

string

basic_auth

How to send ClientID and ClientSecret i.e. basic_auth or post_body

Token Scope

oauth2ClientCredentialsScope

AuthOAuth2

string

“”

Specify scope for client credential grant type

Resource Owner Username

oauth2ResourceOwnerUsername

AuthOAuth2

string

“”

Resource owner username for requesting the token

Resource Owner Password

oauth2ResourceOwnerPassword

AuthOAuth2

string

“”

Resource owner password for requesting the token

Token Scopes

oauth2ResourceOwnerScope

AuthOAuth2

string

“”

Specify all required scopes for the request. Refer to your application when specifying multiple scopes.

Additional body Parameters

oauth2ResourceOwnerAdditionalParams

AuthOAuth2

string

“”

Any non-standard body parameters that need to be specified for the request. You must enter the params in JSON format as a dictionary. Example: {"scope": "hello", "otherParam": "thanks"}

Prefix

prefix

AuthApiKey

string

“”

Prefix to add to the API Key

Suffix

suffix

AuthApiKey

string

“”

Suffix to add to the API Key

API Key

apikey

AuthApiKey

string

“”

API Key to use for authentication

Authenticator Override

authOverride

All

string

“”

This is only used when a custom authenticator is leveraging this credential type. Enter the name of the custom authenticator.

Examples