Updated: Implementing Single Sign-On

An administrator can implement Single Sign-On to allow users to log in once to access to multiple applications rather than logging in to the individual applications. Single Sign-On can be enabled using SAML 2 or the Custom option.


SAML 2 is a secure Single Sign-On technology that integrates with popular identity management services like Okta and OneLogin.


Instructions on using SAML 2 or the Custom option follow.

Enabling Single Sign-on in Klipfolio using SAML 2

To use Single Sign-on, administrators must enable it in Klipfolio.

  1. Located at the top right of the screen, click Account.
  2. Select the Settings tab.
  3. Click the Single Sign-On link.
  4. Click the Edit link.
  5. At Single Sign-On (SSO), select SAML 2 to allow users to sign in with the single sign-on.  
  6. Paste in the certificate information you received from your identity management service in the Certificate field.  NOTE: The endpoint is: 
    https://app.klipfolio.com/SAML/consume
  7. Click Save.
  8. Log in to your Identity Management service.

Enabling Single Sign-on in Klipfolio using Custom

Implementing Single Sign-On using the Custom option includes the following tasks:
  1. Enable Single Sign-on for users in Klipfolio.
  2. Create and encrypt a JSON string that contains the user's information. Information on creating a JSON string is provided in this article. 
  3. Enable Single Sign-on using the POST method.
  4. OR, Enable Single Sign-On using the GET method. 
Once the , administrators must enable it in Klipfolio.
  1. Located at the top right of the screen, click Account.
  2. Select the Settings tab.
  3. Click the Single Sign-On link.
  4. Click the Edit link.
  5. At Single Sign-On (SSO), select Custom to allow users to sign in with the single sign-on.  
  6. Click the Generate New Secret Key link.
  7. Click the Save button.

Customer guidelines for the Custom Single Sign-On option

The following topics are provided as guidelines for customers creating a Single Sign-On token for Klipfolio and includes a sample of specific implementations based on different languages or platforms.

You can use single sign-on to provide users with direct access to Klipfolio from inside a trusted application, such as your company’s intranet, without prompting a user to log in again.

For users accessing Klipfolio with a modern web browser, such as the latest version of Google Chrome or Mozilla Firefox or Microsoft Internet Explorer 10, it is suggested that you use the POST method to implement Single Sign-On.

For users accessing Klipfolio with an older web browser, such as Microsoft Internet Explorer 9, that does not support cross-origin resource sharing (CORS) requests, it is suggested that you use the GET method to implement Single Sign-On.

You must provide Klipfolio with a JSON string that describes the user who is trying to authenticate. The string must be encrypted using AES encryption. The JSON string must include an email address that uniquely identifies the user or an external_id that is assigned to the user by the API.

The following table lists the JSON string attributes.

Name

Description

emailEmail address.
expiresToken expiry date, specified in epoch time.
external_idOptional: Associated id. If specified, is used as the primary lookup for the user.
logout_urlOptional: Redirection location after logging out.
NoteYou must select either an email address or external_id.

Encrypting the JSON string

Use the following guidelines to encrypt the JSON string.

  1. Start with a JSON string.
  2. Generate the secret key using the Klipfolio administration tools located in the Klipfolio account settings.
  3. Important: A secret key can be regenerated at any time. However, this action invalids any existing tokens and requires an update to the encryption code.

  4. Use the secret key and company id as part of the salt.
  5. Encrypt using AES/CBC/PKCS5Padding.
  6. Prepend the initialization vector to the encrypted token.
  7. Encode with ciphertext Base64.

Tip: You can validate the generated Single Sign-on token for any error messages at the Klipfolio SSO Test Page.

Enable Single Sign-on using the POST method

You must post the encrypted token to the authentication endpoint.

  1. POST the token to the /users/sso_auth directory.
  2. Note: The POST must contain the Single Sign-on token and the ID of the company making the request.

      The token can either be sent in:

    • the request header 'KF-SSO'
    • the request query string parameter 'sso' (url-encoded)

      The company ID can either be sent in:

    • the request header 'KF-Company'
    • the request query string parameter 'company'

  3. Optional: To end a user's session, you can also POST the token to the /users/sso_logout directory.
Enable Single Sign-On using the GET method

You can use GET requests to implement single sign-on between a trusted application and Klipfolio. This parameter automatically redirects a user to the Dashboard after successful authentication.

The parameter is &redirect=true

It is suggested you use this method for users accessing Klipfolio with Microsoft Internet Explorer 9 which does not support CORS requests or Microsoft Internet Explorer 10 configured with restricted security settings.

The following information is provided as a guideline to implementing the GET method of single sign-on.

  • GET the token: /users/sso_auth.
  • Note: The GET request must contain the Single Sign-on token and the ID of the company making the request.

      The token can either be sent in:

    • the request header 'KF-SSO'
    • the request query string parameter 'sso' (url-encoded)

      The company ID can either be sent in:

    • the request header 'KF-Company'
    • the request query string parameter 'company'

  • Optional: To end a user's session, you can also POST the token to the /users/sso_logout directory.
  • Error handling

    The JSON response can help you troubleshoot problems related to Single Sign-on. A successful request provides information about the authorized user. An unsuccessful request provides one of the following error codes.

    Code

    Error

    Description

    1no_sso_supportSingle-sign-on is not enabled.  Enable SSO in Account > Settings > Single-Sign-On.
    2token_expiredThe current time is greater than the token’s specified ‘expires’ time.
    3token_parseUnable to parse the token as a JSON object.
    4no_user_foundUnable to find a user for a given email address or external_id.
    5user_does_not_belong_to_companyThe resolved user does not belong to the company making the request.
    6decryptionUnable to decrypt the token. Ensure that the token is encrypted/transported to specification.
    7no_token_paramThe request does not contain the SSO token in either the request header or query string.
    8no_company_paramThe request does not contain the company-id in either the request header or query string.
    9no_companyUnable to find the company for the given id.
    10expires_date_parseUnable to parse the ‘expires’ field. Specify the date in epoch time.

    Code samples

    The following blocks of code are provided as examples.

    Example of a user token that is identified by email:

    {

        email : "user@acme.com",

        expires: "1377272230"

    }

    Example of a user token that is identified by customer ID:


    {

        external_id : "123456",

        expires: "1377272230"

    }

    Language code snippets

    Click the following links to download code snippets for most major languages.

    https://static.klipfolio.com/images/saas/sso_ruby.txt 
    https://static.klipfolio.com/images/saas/sso_py.txt 
    https://static.klipfolio.com/images/saas/sso_php.txt 
    https://static.klipfolio.com/images/saas/sso_pl.txt 
    https://static.klipfolio.com/images/saas/sso_cs.txt

    Example Code: Java


    import org.apache.commons.codec.binary.Base64;

    import org.apache.commons.codec.digest.DigestUtils;

    import org.apache.commons.codec.net.URLCodec;

    import org.apache.commons.httpclient.HttpClient;

    import org.apache.commons.httpclient.methods.PostMethod;

    import org.apache.commons.lang3.ArrayUtils;

    import org.json.JSONObject;


    import javax.crypto.*;

    import javax.crypto.spec.IvParameterSpec;

    import javax.crypto.spec.SecretKeySpec;

    import java.io.ByteArrayInputStream;

    import java.io.ByteArrayOutputStream;

    import java.io.InputStream;

    import java.io.OutputStream;

    import java.security.InvalidAlgorithmParameterException;

    import java.security.InvalidKeyException;

    import java.security.NoSuchAlgorithmException;


    public class KFTokenGenerator

    {

        private SecretKeySpec secretKeySpec;

        private IvParameterSpec ivSpec;

        private URLCodec urlCodec = new URLCodec("ASCII");

        private Base64 base64 = new Base64();



        public KFTokenGenerator(String klipfolioCompanyId, String ssoKey)

        {

            String salted = ssoKey + klipfolioCompanyId;

            byte[] hash = DigestUtils.sha(salted);

            byte[] saltedHash = new byte[16];

            System.arraycopy(hash, 0, saltedHash, 0, 16);


            secretKeySpec = new SecretKeySpec(saltedHash, "AES");

        }



        private void encrypt(InputStream in, OutputStream out) throws Exception

        {

            try

            {

                byte[] buf = new byte[1024];


                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);


                out.write(cipher.getIV());


                CipherOutputStream cipherOutputStream

    = new CipherOutputStream(out, cipher);


                int numRead = 0;

                while ((numRead = in.read(buf)) >= 0)

                {

                    cipherOutputStream.write(buf, 0, numRead);

                }

                cipherOutputStream.close();


            }

            catch (Exception e)

            {

                e.printStackTrace();

            }


        }



        public String create(JSONObject json) throws Exception

        {

            byte[] data = json.toString().getBytes("UTF-8");

            ByteArrayOutputStream out = new ByteArrayOutputStream();

            encrypt(new ByteArrayInputStream(data), out);


            return new String(base64.encode(out.toByteArray()));

        }


        public static void main(String[] args)

        {

            try

            {

                JSONObject jsonObj = new JSONObject();


                jsonObj.put("external_id", "1234");

                jsonObj.put("expires", "2013-08-01T00:00:00Z");

                jsonObj.put("email", "user@klipfolio.com");


                KFTokenGenerator tokenGenerator = new KFTokenGenerator(

    "<your company id>",

    "<your klipfolio-provided sso key>"

    );


                String token = tokenGenerator.create(jsonObj);

                System.out.println("token = " + token);

            }

            catch (Exception e)

            {

                e.printStackTrace();

            }

        }

    }

    Example Transport Code: JavaScript

    The following example uses jQuery to POST the Single Sign-on token to Klipfolio.

    In this example, to create a token for a specific user, the value of encrypted token is generated on the server first, and then passed to the user interface.

    function initKlipfolioSSO( onSuccess, onError ){

        $.ajax({


            url : "https://app.klipfolio.com/users/sso_auth",

            type: "post",

            xhrFields:{

                withCredentials:true

            },

            headers:{

            "KF-SSO":"<encrypted token>",

                "KF-Company":"<company id>"

            },

            dataType:"json",

           

    // on success, the data object will contain information about

            // the authenticated user

            // ----------------------------------------------------------

            success : function(data){

                console.log(data);
                        onSuccess(data);

            },


            // on error, err.responseJSON will contain an error code

            // -----------------------------------------------------

            error : function(err){

                console.log("sso auth failed",err.responseJSON);

                        onError(data);

            }

    })

    }

    After the POST is completed, the user is provided with an authenticated session to Klipfolio. As a best practice, only request a session when a user intends to interact with their dashboard.

     

    // create an SSO session on page-load.

    // ------------------------------------------------

    $( initKlipfolioSSO() );

    For example, the above code is not recommended as it sends a session request for every page load and will return invalid usage data. Instead, it is preferable to only create the Single Sign-on session on demand as shown below.

    // only request an SSO session when the user wants to view their dashboard

    // ---------------------------------------------------------------------------


    $("#dashboard-link").click( function( ){

        initKlipfolioSSO(

            function(data){

                document.location = "https://app.klipfolio.com/dashboard"

            },

            function(data){

                alert("Dashboard is temporarily unavailable.");

            },

    );


    })

    Related Links

    Custom single sign-on fails when logging into Klipfolio

    Feedback and Knowledge Base