Federated SAML based Single Sign-On Authentication with AWS Cloud

In a previous post, I have described the technique to implement Single Sign-On security functionality in Java using OpenID Connect (OIDC). In this blog post, I am going to implement federated AWS Single Sign-On (SSO) using SAML which will enable users to authenticate using on-premises credentials and access resources in cloud and third-party SaaS applications on AWS.

Why Federated Single Sign-On for Cloud

How to secure cloud credentials

Users generally require working with multiple applications for business reasons which are often provided and hosted by different organisations. In addition, enterprise organisations of utilising cloud and third-party SaaS applications are also growing continuously and keep getting bigger. This requires users to remember multiple credentials to access each application they need to work with, which results in exposing security vulnerabilities and complex user experience. The organisation needs to control who can access each application without exposing data security risks.

To provide user single point of authentication with seamless federated Single Sign-On, we can separate user authentication logic from the application code, and delegate authentication responsibility to a trusted identity provider (IdP).

AWS also supports federated SAML based single sign-on (SSO) which provides a mechanism to issue temporary security credentials. This mechanism allows application to assume a role in AWS and obtain a temporary access key, secret access key and session token.

The mechanism enables users to log into the Console or invoke the AWS APIs programmatically without requiring the organisation to create an IAM user for each of them. From the user’s perspective, the process happens transparently. The user requests, for instance, could start at the organisation’s internal portal and end up either at the AWS Management Console or invoke programmatic AWS APIs calls by using assertions from a SAML compliant identity provider (IdP). The user authentication happens without ever providing any AWS credentials or creating any AWS config file.

This design approach simplifies development, minimise the requirement for user administration, and also improves user experience of the application. In addition, it also increases cloud security and productivity by avoiding the use of multiple passwords and the need to log in separately to each application. Moreover, by incorporating this mechanism, the attackers will not be able to obtain security credentials and run their malicious code in a data breach incident.

Pre-requisite

Before we can use SAML based federation SSO, we must be running IdP that supports SAML 2.0, for instance, ADFS etc. For configuring ADFS with AWS, the detailed step-by-step guide be found here.

AWS Single Sign-On Implementation

A sample code in Node.js Javascript to implement AWS Single Sign-On (SSO) via SAML for creating Federated authentication token to other applications is illustrated in the example below.

 
//example for brevity

var inputPar = {
    UserName: 'username',
    Kmsi: 'True',
    Password: 'password'    
};

AWS.config.update({    
    region: 'eu-west-1'
});

function authenticateWithAWS(){
  request({
    url: 'https://ADFS_SERVER_FQDN/adfs/ls/idpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices',
      method: 'GET'
  }, function (err, res, body) {
        request({            
            method: 'POST',
            followAllRedirects: true,            
            form: inputPar,
            url: res.request.uri,
            jar: true
        }, function(error, response, body) {
            if (error) {
                console.log(error);
            } else {
                assertion = // parse the body parameter with any parser
                            // and extract SAML assertion value
                            // can be use parser like cheerio etc
                generateToken(assertion);// authenticate without supply any AWS credentials
            }
        });
    });
};

function generateToken(assertion) {
    AWS.config.update({
        credentials: new AWS.SAMLCredentials({
            RoleArn: 'arn:aws:iam::<trustedaccount>:role/SAMLRole',
            PrincipalArn: 'arn:aws:iam::AWS-account-ID:saml-provider/provider-name',
            SAMLAssertion: assertion
        }),
    });
}
Here is an example of generating SAMLAssertion for AWS Single Sign-On (SSO) using Java.
//example for brevity
//imports are also omitted for brevity
public String generateSAMLAssertion() throws IOException {
    URL url = new URL(
        "https://adfs_server_fqdn/adfs/ls/idpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices");        
    Document doc = Jsoup.parse(url, 10000);

    if (doc.getElementById("loginForm") != null) {

        Element loginForm =  doc.getElementById("loginForm");
        String host = "your_adfs_server_fqdn";
        String protocol = "https"; 
        String formSubmitUrl = new URL(protocol, host, loginForm.attr("action")).toString();

        // submit login form with post data
        Response response = Jsoup.connect(formSubmitUrl)
            .timeout(10000)
            .data("UserName", "yourUser", "Password", "yourPassword")
            .method(Method.POST)
            .execute();

        Document document = response.parse();
            
        // find the SAMLResponse attribute and return!!
        Elements samlResponse = document.select("input[name=SAMLResponse]");
        return samlResponse.attr("value");            
    }
    return null;
}

AWS Single Sign-On (SSO) Example with SAML assertion

The example source code below illustrates the retrieval of service catalog information while using SAML assertion token for authentication.

 
// example of an API invocation with a SAML assertion
// Retrieve AWS ServiceCatalogProducts
serviceCatalog = new AWS.ServiceCatalog();


serviceCatalog.searchProducts({}, function(error, res) {
    if (error) {
        console.log("Error", error.stack);
    } else {   
        console.log(res);   
    }
}); 

Refresh Expire AWS STS Token

The temporary AWS security credentials that we use for either logging into the Console or calling the AWS APIs last up to 1 hour. When the access token used by client application to access an API or console expires, the client must request a new access token. To deal with unnecessary credential prompts and also to ensure and maintain high levels of security, a simple example of a refreshing token is illustrated below. The refresh process is performed by invoking the authenticateWithAWS() method again in order to get a new security token.

 
// example for brevity
// Retrieve AWS ServiceCatalogProducts
serviceCatalog = new AWS.ServiceCatalog();

serviceCatalog.searchProducts({}, function(error, res) {
    if (error) {
        console.log("Error", error.stack);
    } else {   
        console.log(res);   
    }
})
.catch(function(error){        
        if (error.stack.indexOf(EXPIRE_TOKEN_ERROR_MESSAGE) > -1) {
            // define retry policy here...
            authenticateWithAWS();
        }
}); 

Troubleshoot AWS SSO SAMLAssertion issues

Handling HTTP Redirect

If we are getting a response statusCode indicating redirect to another URL (301, 302 or 307), or the body is empty, make sure that followAllRedirects config is set to true. By default, the redirects are turned on for GET requests only and we need to add this config to our POST request.

Configuring Proxies

If the expected SAMLAssertion or SAMLResponse attribute value of the input tag doesn't come back, check that we have correctly configured the proxy variables. We may need to configure proxy by setting environment variables (e.g. HTTP_PROXY, HTTPS_PROXY, NO_PROXY etc) before executing the code. This link describes how to configure proxies for Node.js.

Access Denied Error

If we get "Access Denied" error while invoking the AWS API, for instance, by calling serviceCatalog.searchProducts() method, it means that the user is authenticated successfully but the given federated user doesn't have the required permissions. Please follow this link to find out how to give required permissions and roles to a federated user.

More References

2 comments:

  1. Nice, exclude all the libraries and dependencies "for brevity" and have no github repo. As useful as a chocolate teapot

    ReplyDelete