Configuring OAuth for Custom Clients

This topic describes how to configure OAuth support for custom clients.

In this Topic:

Known Issues

The Snowflake multi-factor authentication (MFA) feature is not supported when federated authentication and OAuth are enabled for a user. As a workaround, disable MFA support in Snowflake for the user and enable it instead with your federated authentication provider.

Workflow

The following high-level steps are required to configure OAuth for custom clients:

  1. Register your client with Snowflake. To register your client, create an integration. An integration is a Snowflake object that provides an interface between Snowflake and third-party services, such as a client that supports OAuth.

    The registration process defines a client ID and client secrets.

  2. Configure calls to the Snowflake OAuth endpoints to request authorization codes from the Snowflake authorization server and to request and refresh access tokens.

    The optional “scope” parameters in the initial authorization request limit the role permitted by the access token and can additionally be used to configure the refresh token behavior.

Step 1: Configuring an OAuth Integration

Create an integration using the CREATE SECURITY INTEGRATION command.

Note

Only account administrators (users with the ACCOUNTADMIN role) can execute this SQL command.

The following example creates an OAuth integration that uses key pair authentication. The integration allows refresh tokens, which expire after 1 day (86400 seconds). The integration blocks users from starting a session with SYSADMIN as the active role:

CREATE SECURITY INTEGRATION oauth_kp_int
  TYPE = OAUTH
  ENABLED = TRUE
  OAUTH_CLIENT = CUSTOM
  OAUTH_CLIENT_TYPE = 'CONFIDENTIAL'
  OAUTH_REDIRECT_URI = 'http://localhost.com'
  OAUTH_ISSUE_REFRESH_TOKENS = TRUE
  OAUTH_REFRESH_TOKEN_VALIDITY = 86400
  BLOCKED_ROLES_LIST = ('SYSADMIN')
  OAUTH_CLIENT_RSA_PUBLIC_KEY ='
  MIIBI
  ..
  ';

Blocking Specific Roles from Using the Integration

The optional BLOCKED_ROLES_LIST parameter allows you to list Snowflake roles that a user cannot explicitly consent to using with the integration.

By default, the account administrator (ACCOUNTADMIN) and security administrator (SECURITYADMIN) roles are included in this list and cannot be removed. If you have a business need to allow users to use OAuth with these roles, and your security team is comfortable with allowing it, please contact Snowflake Support to request that these roles be allowed for your account.

Step 2: Calling the OAuth Endpoints

OAuth endpoints are the URLs that clients call to request authorization codes and to request and refresh access tokens. These endpoints refer to specific OAuth 2.0 policies that execute when the endpoint is called.

Snowflake provides the following OAuth endpoints:

Authorization:https://<account>.snowflakecomputing.com/oauth/authorize
Token requests:https://<account>.snowflakecomputing.com/oauth/token-request

Where account_name specifies the full name of your Snowflake account.

For convenience, Snowflake defines the endpoints when a client is registered. To view the endpoints for your integration, execute DESCRIBE SECURITY INTEGRATION.

For example, the following example returns the endpoints for the integration created in Step 1: Configuring an OAuth Integration:

DESC SECURITY INTEGRATION oauth_kp_int;

+----------------------------------+---------------+----------------------------------------------------------------------+------------------+
| property                         | property_type | property_value                                                       | property_default |
|----------------------------------+---------------+----------------------------------------------------------------------+------------------|
..
| OAUTH_AUTHORIZATION_ENDPOINT     | String        | https://myaccount.snowflakecomputing.com/oauth/authorize             |                  |
| OAUTH_TOKEN_ENDPOINT             | String        | https://myaccount.snowflakecomputing.com/oauth/token-request         |                  |
..
+----------------------------------+---------------+----------------------------------------------------------------------+------------------+

Authorization Endpoint

The authorization endpoint is used to obtain an authorization grant after a user successfully authorizes a client with Snowflake. The authorization endpoint is as follows:

https://<account>.snowflakecomputing.com/oauth/authorize

Where account_name specifies the full name of your account (provided by Snowflake). Depending on the cloud platform (AWS or Azure) and Snowflake Region where your account is hosted, the full account name may require additional segments:

Account name details
Structure of Snowflake account hostnames

For example, for an account named xy12345:

If the account is in: Full account name is:
AWS US West xy12345
AWS US East xy12345.us-east-1
AWS EU (Frankfurt) xy12345.eu-central-1
AWS EU (Dublin) xy12345.eu-west-1
AWS AP (Sydney) xy12345.ap-southeast-2
   
Azure East US 2 xy12345.east-us-2.azure
Azure West Europe xy12345.west-europe.azure

Important

The structure of your account name is different if either of the following are true:

For more details about regions and platforms, see Snowflake Regions and Cloud Platforms.

method

GET

Important

The authorization endpoint must be opened in a browser that the user can interact with. Do not use cURL with this endpoint.

query parameters
Parameter Data Type Required Description
client_id String Yes Client ID (provided by Snowflake when the client is registered)
response_type String Yes Response type created. Currently supports code value, because Snowflake only issues authorization codes.
redirect_uri String Yes URI where the user is redirected to after successfully authorizing. Must match the value registered with Snowflake during the client registration.
state String No String of no more than 2048 ASCII characters that is returned with the response from the Snowflake authorization server. Typically used to prevent cross-site request forgery attacks.
scope String No Space-delimited string that is used to limit the scope of the access request. For more information, see Scope in this topic.
code_challenge String No Challenge for Proof Key for Code Exchange (PKCE). String generated via a secret and a code challenge method. For more information, see Proof Key for Code Exchange.
code_challenge_method String No String indicating the method used to derive the code challenge for PKCE. For more information, see Proof Key for Code Exchange.

When a user authorizes the client, a redirect will be made to the redirect_uri that contains the following in a GET request:

Parameter Description
code Short-lived authorization code, which can be exchanged at the token endpoint for an access token
state state value provided in the original request, unmodified
scope Scope of the access request; currently the same as the scope value in the initial authorization request but may differ in the future. For more information, see Scope.

Scope

The scope parameters in the initial authorization request optionally limit the operations and role permitted by the access token.

Scope is validated immediately when making an authorization request with respect to semantics, but not necessarily validity. That is, any invalid scopes (e.g. “bogus_scope”) will be rejected before the user authenticates, but a scope the user does not have access to (a particular role, etc) will not result in an error until after the user authenticates. Valid scope parameters are as follows:

query parameters
Parameter Required Description
refresh_token No If included in the authorization URL, Snowflake presents the user with the option to consent to offline access. In this context, offline access refers to allowing the client to refresh access tokens when the user is not present. With user consent, the authorization server returns a refresh token in addition to an access tken when redeeming the authorization code.
session:role:role_name No Used to limit the access token to a single role that the user can consent to for the session. Only one session role scope can be specified. If this scope is omitted, then the default role for the user is used instead. When a user authorizes consent, Snowflake always displays the role for the session regardless if this scope is included in the authorization URL.
    Note that role_name is case-sensitive and must be input in all upper-case unless the role name was enclosed in quotes when it was created using CREATE ROLE. To verify the case, execute SHOW ROLES in Snowflake and see the role name in the output.

Append scope parameters to the authorization URL.

The following example limits authorization to the custom R1 role:

scope=session:role:R1

The following example indicates that access/refresh tokens should use the default role for the user and requests a refresh token so that offline access can occur:

scope=refresh_token

The following example limits authorization to the custom R1 role and requests a refresh token so that offline access can occur:

scope=reresh_token session:role:SYSADMIN

Token Endpoint

This endpoint returns access tokens or refresh tokens depending on the request parameters. The token endpoint is as follows:

https://<account>.snowflakecomputing.com/oauth/token-request

Where account_name specifies the full name of your account (provided by Snowflake). Depending on the cloud platform (AWS or Azure) and Snowflake Region where your account is hosted, the full account name may require additional segments:

Account name details
Structure of Snowflake account hostnames

For example, for an account named xy12345:

If the account is in: Full account name is:
AWS US West xy12345
AWS US East xy12345.us-east-1
AWS EU (Frankfurt) xy12345.eu-central-1
AWS EU (Dublin) xy12345.eu-west-1
AWS AP (Sydney) xy12345.ap-southeast-2
   
Azure East US 2 xy12345.east-us-2.azure
Azure West Europe xy12345.west-europe.azure

Important

The structure of your account name is different if either of the following are true:

For more details about regions and platforms, see Snowflake Regions and Cloud Platforms.

method

POST

query parameters
Parameter Data Type Required Description
grant_type String Yes Type of grant requested:
      authorization_code indicates that an authorization cde should be exchanged for an access token.
      refresh_token indicates a request to refresh an access token.
code String Yes Authorization code returned from the token endpoint. Used and required when grant_type is set to authorization_code.
refresh_token String Yes Refresh token returned from an earlier request to the token endpoint when redeeming the authorization code. Used and required when grant_type is set to refresh_token.
code_verifier String No Required only if the authorization request was sent to the Authorization Endpoint with a code_challenge parameter value. Code verifier for PKCE. For more information, see Proof Key for Code Exchange.

Additionally, the client ID and client secret must be included in the authorization header. Currently, Snowflake only supports the Basic Authentication Scheme, which means that the value expected is in the following form:

Basic Base64(client_id:client_secret)

Where:

Parameter Data Type Required Description
client_id String Yes Client ID of the integration. Can be retrieved using the SYSTEM$SHOW_OAUTH_CLIENT_SECRETS function.
client_secret String Yes Client secret for the integration. Can be retrieved using the SYSTEM$SHOW_OAUTH_CLIENT_SECRETS function.

Note the : character between client_id and client_secret.

post body

A JSON object is returned with the following attributes:

Parameter Data Type Description
access_token String Access token used to establish a Snowflake session
refresh_token String Refresh token. Not issued if the client is configured to not issue refresh tokens or if the user did not consent to the refresh_token scope.
expires_at Integer Number of seconds after which the token expires
expires_in Integer Number of seconds remaining until the token expires
token_type String Access token type. Currently, always Bearer
username String Username that the access token belongs to. Currently only returned when exchanging an authorization code for an access token.

The following example shows a successful response when exchanging an authorization code for an access and refresh token:

{
  "access_token":  "ACCESS_TOKEN",
  "expires_at": 1510182608.0602734,
  "expires_in": 600,
  "refresh_token": "REFRESH_TOKEN",
  "token_type": "Bearer",
  "username": "user1",
}

The following example shows an unsuccessful response:

{
  "data" : null,
  "message" : "This is an invalid client.",
  "code" : null,
  "success" : false,
  "error" : "invalid_client"
}

The message string value is a description of the error while error is the error type. For more information on the types of errors returned, please reference the Errors section in this topic.

Proof Key for Code Exchange

Snowflake supports Proof Key for Code Exchange (PKCE) for obtaining access tokens using the authorization_code grant type as described in RFC 7636. PKCE can be used to lessen the possibility of an authorization code interception attack, and is suitable for clients that may not be able to fully keep the client secret secure.

By default, PKCE is optional and is enforced only if the code_challenge and code_challenge_method parameters are both included in the authorization endpoint URL. However, we highly recommend that your client require PKCE for all authorizations to make the OAuth flow more secure.

The following describes how PKCE for Snowflake works:

  1. The client creates a secret called the code verifier and performs a transformation on it to generate the code challenge. The client holds onto the secret.

  2. The client directing the user to the Authorization URL appends the following two query parameters:

    code_challenge

    Specifies the code challenge generated in Step 1.

    code_challenge_method

    Specifies the transformations used on the code verifier in Step 1 to generate the code challenge. Currently, Snowflake only supports SHA256, so this value must be set to S256. The transformation algorithm for SHA256 is BASE64URL-ENCODE(SHA256(ASCII(code_verifier))).

  3. After the user consents to the requested scopes or Snowflake determines that consent is present for that user, the authorization code is issued.

  4. The client receives the authorization code from the Snowflake authorization server, which it then submits along with the code_verifier in the request to the token endpoint.

  5. Snowflake transforms the code_verifier value and verifies that the transformed value matches the code_challenge value used when generating authorizations. If these values match, then the authorization server issues the access and refresh tokens.

Using Key Pair Authentication

Snowflake supports using key pair authentication rather than the typical username/password authentication when calling the token endpoint. This authentication method requires a 2048-bit (minimum) RSA key pair. Generate the PEM (Privacy Enhanced Mail) public-private key pair using OpenSSL. The public key is assigned to the Snowflake user who will use the Snowflake client.

To configure the public/private key pair:

  1. From the command line in a terminal window, generate a private key:

    $ openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8
    

    OpenSSL prompts for a passphrase used to encrypt the private key file. We recommend using a strong passphrase to protect the private key. Record this passphrase. You will input it when connecting to Snowflake. Note that the passphrase is only used for protecting private key and will never be sent to Snowflake.

    Sample PEM private key

    -----BEGIN ENCRYPTED PRIVATE KEY-----
    MIIE6TAbBgkqhkiG9w0BBQMwDgQILYPyCppzOwECAggABIIEyLiGSpeeGSe3xHP1
    wHLjfCYycUPennlX2bd8yX8xOxGSGfvB+99+PmSlex0FmY9ov1J8H1H9Y3lMWXbL
    ...
    -----END ENCRYPTED PRIVATE KEY-----
    
  2. From the command line, generate the public key by referencing the private key:

    $ openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
    

    Sample PEM public key

    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+Fw2qv4Roud3l6tjPH4
    zxybHjmZ5rhtCz9jppCV8UTWvEXxa88IGRIHbJ/PwKW/mR8LXdfI7l/9vCMXX4mk
    ...
    -----END PUBLIC KEY-----
    
  3. Copy the public and private key files to a local directory for storage. Record the path to the files. Note that the private key is stored using the PKCS#8 (Public Key Cryptography Standards) format and is encrypted using the passphrase you specified in the previous step; however, the file should still be protected from unauthorized access using the file permission mechanism provided by your operating system. It is your responsibility to secure the file when it is not being used.

  4. Assign the public key to the integration object using ALTER SECURITY INTEGRATION. For example:

    ALTER SECURITY INTEGRATION myint SET RSA_PUBLIC_KEY='MIIBIjANBgkqh...';
    

    Note

    • Only account administrators can execute the SQL command.
    • Exclude the public key header and footer in the SQL statement.

    Verify the public key fingerprint using DESCRIBE SECURITY INTEGRATION:

    DESC SECURITY INTEGRATION myint;
    
    +----------------------------------+---------------+----------------------------------------------------------------------+------------------+
    | property                         | property_type | property_value                                                       | property_default |
    |----------------------------------+---------------+----------------------------------------------------------------------+------------------|
    ..
    | OAUTH_CLIENT_RSA_PUBLIC_KEY_FP   | String        | SHA256:MRItnbO/123abc/abcdefghijklmn12345678901234=                  |                  |
    | OAUTH_CLIENT_RSA_PUBLIC_KEY_2_FP | String        |                                                                      |                  |
    ..
    +----------------------------------+---------------+----------------------------------------------------------------------+------------------+
    

    Note

    The RSA_PUBLIC_KEY_2_FP property is described in Key Rotation (in this topic).

  5. Modify and execute the sample code, below. The code decrypts the private key file and passes it to the Snowflake authorization server:

  • Update the security parameters:

    • private_key: Open rsa_key.p8 in a text editor, and copy the lines between:
    -----BEGIN RSA PRIVATE KEY-----
    ..
    -----END RSA PRIVATE KEY-----
    
  • Update the session parameters:

    • account_name: Specifies the name of your account (provided by Snowflake).
  • Update the JSON Web Token (JWT) fields:

    post body

    A JSON object with the following standard fields (“claims”):

    Attribute Data Type Required” Description
    iss String Yes Specifies the principal that issued the JWT in the format client_id.public_key_fp where client_id is the client ID of the OAuth client integration and public_key_fp is the fingerprint of the public key that is used during verification.
    sub String Yes Subject of the JWT in the format account_name.client_id where account is the name of the Snowflake account and client_id is the client ID of the OAuth client integration.
          account specifies the full name of your account (provided by Snowflake). Depending on the cloud platform (AWS or Azure) and Snowflake Region where your account is hosted, the full account name may require additional segments. For more information, see the account variable description under Token Endpoint.
    iat Timestamp No Time when the token was issued.
    exp Timestamp Yes Time when the token should expire. This period should be relatively short, so a value of a few minutes should suffice.
Sample code
import datetime
import json
import urllib

import jwt
import requests

private_key = """
-----BEGIN RSA PRIVATE KEY-----
<private_key>
-----END RSA PRIVATE KEY-----"""

public_key_fp = "SHA256:MR..."


def _make_request(payload, encoded_jwt_token):
    token_url = "https://<account_name>.snowflakecomputing/oauth/token-request"
    headers = {
            u'Authorization': "Bearer %s" % (encoded_jwt_token),
            u'content-type': u'application/x-www-form-urlencoded'
    }
    r = requests.post(
            token_url,
            headers=headers,
            data=urllib.urlencode(payload))
    return r.json()


def make_request_for_access_token(oauth_az_code, encoded_jwt_token):
    """ Given an Authorization Code, make a request for an Access Token
    and a Refresh Token."""
    payload = {
        'grant_type': 'authorization_code',
        'code': oauth_az_code
    }
    return _make_request(payload, encoded_jwt_token)


def make_request_for_refresh_token(refresh_token, encoded_jwt_token):
    """ Given a Refresh Token, make a request for another Access Token."""
    payload = {
        'grant_type': 'refresh_token',
        'refresh_token': refresh_token
    }
    return _make_request(payload, encoded_jwt_token)


def main():
    account_name = "<account_name>"
    client_id = "1234"  # found by running DESC SECURITY INTEGRATION
    issuer = "{}.{}".format(client_id, public_key_fp)
    subject = "{}.{}".format(account_name, client_id)
    payload = {
        'iss': issuer,
        'sub': subject,
        'iat': datetime.datetime.utcnow(),
        'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=30)
    }
    encoded_jwt_token = jwt.encode(
            payload,
            private_key,
            algorithm='RS256')

    data = make_request_for_access_token(oauth_az_code, encoded_jwt_token)
    refresh_token = data['refresh_token']
    data = make_request_for_refresh_token(refresh_token, encoded_jwt_token)
    access_token = data['access_token']


if __name__ == '__main__':
    main()

After the token is created, submit it in requests to the token endpoint. Requests require the Bearer authorization format as the authorization header instead of the basic authorization format normally used for the client ID and client secret, as follows:

"Authorization: Bearer JWT_TOKEN"

Key Rotation

Snowflake supports multiple active keys to allow for uninterrupted rotation. Rotate and replace your public and private keys based on the expiration schedule you follow internally.

Currently, you can use the OAUTH_CLIENT_RSA_PUBLIC_KEY and OAUTH_CLIENT_RSA_PUBLIC_KEY_2 parameters for ALTER SECURITY INTEGRATION to associate up to 2 public keys with a single user.

To rotate your keys:

  1. Complete the steps in Using Key Pair Authentication to:

    • Generate a new private and public key set.

    • Assign the public key to the integration. Set the public key value to either OAUTH_CLIENT_RSA_PUBLIC_KEY or OAUTH_CLIENT_RSA_PUBLIC_KEY_2 (whichever key value is not currently in use). For example:

      alter integration myint set oauth_client_rsa_public_key_2='JERUEHtcve...';
      
  2. Update the code to connect to Snowflake. Specify the new private key.

    Snowflake verifies the correct active public key for authentication based on the submitted private key.

  3. Remove the old public key from the integration. For example:

    alter integration myint unset rsa_public_key;
    

Errors

The following are error codes associated with OAuth that can be returned during the authorization flow. the token exchange, or creating a Snowflake session after completing the OAuth flow:

Error Code Error Description
390302 OAUTH_CONSENT_INVALID Issue generating or validating consent for a given user
390303 OAUTH_ACCESS_TOKEN_INVALID Access token provided used when attempting to create a Snowflake session is expired or invalid
390304 OAUTH_AUTHORIZE_INVALID_RESPONSE_TYPE Invalid response_type was provided as a parameter to the authorization endpoint (it should most likely be “code”)
390305 OAUTH_AUTHORIZE_INVALID_STATE_LENGTH State parameter provided as a parameter to the authorization endpoint exceeds 2048 characters
390306 OAUTH_AUTHORIZE_INVALID_CLIENT_ID Integration associated with a provided client id does not exist
390307 OAUTH_AUTHORIZE_INVALID_REDIRECT_URI redirect_uri given as a parameter to the authorization endpoint does not match the redirect_uri of the integration associated with the provided client_id or the redirect_uri is not properly formatted
390308 OAUTH_AUTHORIZE_INVALID_SCOPE Either the scope requested is not a valid scope, or the scopes requested cannot fully be granted to the user

Additionally, the following errors are taken from the RFC and are returned in the JSON blob made during an unsuccessful token request or exchange:

Error Description
invalid_client there was a failure relating to client authentication, such as the client being unknown, a client secret mismatch, etc.
invalid_grant The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
unsupported_grant_type a grant type was provided that Snowflake currently does not support (“refresh_token” and “authorization_code” are the only two supported grant types at the moment).
invalid_request - the request was malformed or could not be processed.