Use Amazon Cognito as Identity Provider

GoodData uses OAuth 2.0 and OpenID Connect (OIDC) to handle authentication. This article shows a worked example of setting up Amazon Cognito as a custom OIDC identity provider for GoodData.

The example uses Bash and AWS CLI version 2. If you use a different deployment tool, such as CloudFormation or Terraform, or if you configure Amazon Cognito manually in the AWS Console, adapt the steps accordingly.

Follow these steps:

  1. Set up shared environment variables.
  2. Create the Amazon Cognito resources.
  3. Update the OIDC settings of the organization.
  4. Map users to the organization.
  5. Log in to GoodData.

For a general overview of OIDC authentication in GoodData, see Set Up Authentication Using OpenID Connect Identity Provider.

Set Up Shared Environment

Set the following environment variables in the shell that you will later use for the aws and curl commands.

VARIABLEExample valueExplanation
AWS_REGIONeu-central-1AWS region where Cognito will be deployed
ORG_HOSTNAMEexample.gooddata.comHostname of the GoodData organization
ORGANIZATION_IDalphaOrganization ID
API_TOKENYWRtaW46Ym9vdHN0cmFwOmRlbW8xMjM=Token with MANAGE permission on the organization
ORG_PORT""Set to a port number if it differs from 80 or 443
ORG_SCHEMAhttpsOrganization URL schema, http or https
IDP_IDcognitoID of the identity provider object in GoodData
IDP_IDENTIFIERexample.comEmail domain of users that should authenticate through this IdP
IDP_ISSUER_IDcognitoSuffix used in the OAuth callback URL
COGNITO_POOL_NAMEgdcn-demoName of the Cognito user pool
COGNITO_DOMAIN_PREFIXgdcn-demoPrefix of the Cognito hosted UI domain
COGNITO_CLIENT_NAMEgdcn-clientName of the Cognito app client
export AWS_REGION="eu-central-1"
export ORG_HOSTNAME="example.gooddata.com"
export ORGANIZATION_ID="alpha"
export API_TOKEN="YWRtaW46Ym9vdHN0cmFwOmRlbW8xMjM="
export ORG_PORT=""
export ORG_SCHEMA="https"

export IDP_ID="cognito"
export IDP_IDENTIFIER="example.com"
export IDP_ISSUER_ID="cognito"

export COGNITO_POOL_NAME="gdcn-demo"
export COGNITO_DOMAIN_PREFIX="gdcn-demo"
export COGNITO_CLIENT_NAME="gdcn-client"

export HOST_URL="${ORG_SCHEMA}://${ORG_HOSTNAME}${ORG_PORT:+:$ORG_PORT}"
export CALLBACK_URL="${HOST_URL}/login/oauth2/code/${IDP_ISSUER_ID}"

Use an Email Domain as the Identifier

Set IDP_IDENTIFIER to a domain name that matches your users’ email addresses, for example example.com. GoodData uses this identifier to decide which identity provider should authenticate a user.

Create the Amazon Cognito Resources

Create the Cognito User Pool

The following command creates a Cognito user pool with the following settings:

  • Minimum password length of 8 characters
  • Uppercase, lowercase, and numeric characters required
  • Temporary passwords valid for 7 days
  • Email addresses verified automatically
  • Multi-factor authentication disabled

GoodData expects the name claim to be present in the ID token. This example makes the name attribute required in the user pool schema.

The ID of the created user pool is stored in USERPOOL_ID and used later in the configuration.

USERPOOL_ID=$(
  aws cognito-idp create-user-pool \
    --pool-name "$COGNITO_POOL_NAME" \
    --policies 'PasswordPolicy={MinimumLength=8,RequireUppercase=true,RequireLowercase=true,RequireNumbers=true,RequireSymbols=false,TemporaryPasswordValidityDays=7}' \
    --auto-verified-attributes email \
    --alias-attributes email \
    --verification-message-template 'DefaultEmailOption=CONFIRM_WITH_CODE' \
    --mfa-configuration OFF \
    --user-attribute-update-settings 'AttributesRequireVerificationBeforeUpdate=email' \
    --schema \
      Name=name,AttributeDataType=String,Mutable=true,Required=true \
      Name=email,AttributeDataType=String,Mutable=true,Required=true \
    --email-configuration 'EmailSendingAccount=COGNITO_DEFAULT' \
    --admin-create-user-config 'AllowAdminCreateUserOnly=true' \
    --username-configuration 'CaseSensitive=False' \
    --account-recovery-setting 'RecoveryMechanisms=[{Priority=1,Name=verified_email}]' \
    --query 'UserPool.Id' \
    --output text
)

echo "Created user pool '$USERPOOL_ID'"

Create the Cognito Domain

Cognito needs a domain for the hosted sign-in UI and OAuth 2.0 endpoints. This example uses the AWS-hosted Cognito domain.

The resulting hosted UI URL has this format:

https://<domain-prefix>.auth.<aws-region>.amazoncognito.com/

aws cognito-idp create-user-pool-domain \
  --user-pool-id "$USERPOOL_ID" \
  --domain "$COGNITO_DOMAIN_PREFIX"

Create the Cognito App Client

Create an OAuth 2.0 client that GoodData will use for the authorization code flow.

read COGNITO_CLIENT_ID COGNITO_CLIENT_SECRET < <(
  aws cognito-idp create-user-pool-client \
    --user-pool-id "$USERPOOL_ID" \
    --client-name "$COGNITO_CLIENT_NAME" \
    --generate-secret \
    --supported-identity-providers COGNITO \
    --callback-urls "$CALLBACK_URL" \
    --allowed-o-auth-flows-user-pool-client \
    --allowed-o-auth-scopes openid profile email \
    --allowed-o-auth-flows code \
    --query 'UserPoolClient.[ClientId,ClientSecret]' \
    --output text
)

echo "Created client with id='$COGNITO_CLIENT_ID'"

Callback URL in This Example

This example sets oauthIssuerId to cognito, so the callback URL registered in Amazon Cognito must be:

https://<organization-hostname>/login/oauth2/code/cognito

The CALLBACK_URL variable above uses this exact format.

Update the OIDC Settings of the Organization

GoodData configures identity providers as separate entities and then links the active identity provider to the organization. In this example, you create a Cognito identity provider object with the Identity Providers API and switch the organization to it with the switch endpoint.

Create the Identity Provider Object

Use the client ID and client secret from the previous step to create a custom identity provider in GoodData.

PAYLOAD=$(cat <<EOF
{
  "data": {
    "id": "$IDP_ID",
    "type": "identityProvider",
    "attributes": {
      "identifiers": ["$IDP_IDENTIFIER"],
      "idpType": "CUSTOM_IDP",
      "oauthClientId": "$COGNITO_CLIENT_ID",
      "oauthClientSecret": "$COGNITO_CLIENT_SECRET",
      "oauthIssuerId": "$IDP_ISSUER_ID",
      "oauthIssuerLocation": "https://cognito-idp.$AWS_REGION.amazonaws.com/$USERPOOL_ID"
    }
  }
}
EOF
)

curl --request POST \
  --header "Authorization: Bearer $API_TOKEN" \
  --header 'Content-Type: application/vnd.gooddata.api+json' \
  --data "$PAYLOAD" \
  "$HOST_URL/api/v1/entities/identityProviders"

Use the Cognito Issuer URL

For Amazon Cognito, set oauthIssuerLocation to the issuer URL in this format:

https://cognito-idp.<aws-region>.amazonaws.com/<user-pool-id>

Do not use the Cognito hosted UI domain, such as https://<domain-prefix>.auth.<aws-region>.amazoncognito.com/, as the issuer location.

Cognito Uses the sub Claim by Default

GoodData uses the sub claim for user mapping by default. Because Amazon Cognito includes sub in the ID token, you do not need to set oauthSubjectIdClaim unless you intentionally want to use a different claim.

Check the Linked Identity Provider

To see which identity provider is currently linked to the organization, use:

GET /api/v1/entities/admin/organizations/{id}?include=identityProviders

Example:

curl --request GET \
  --header "Authorization: Bearer $API_TOKEN" \
  "$HOST_URL/api/v1/entities/admin/organizations/$ORGANIZATION_ID?include=identityProviders"

In the response, look for this section:

"identityProvider": {
  "data": {
    "id": "cognito",
    "type": "identityProvider"
  }
}

Switch the Active Identity Provider

Switch the organization to the Cognito identity provider:

curl --request POST \
  --header "Authorization: Bearer $API_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{
    "idpId": "'"$IDP_ID"'"
  }' \
  "$HOST_URL/api/v1/actions/organization/switchActiveIdentityProvider"

Provision a User in Cognito

There are multiple ways to add users to a Cognito user pool. This example uses the admin-create-user API.

USER_EMAIL="john.doe@example.com"
USER_NAME="John Doe"
USER_LOGIN="john.doe"
USER_PASSWORD="NewRandomPa33word"

EXTERNAL_ID=$(
  aws cognito-idp admin-create-user \
    --user-pool-id "$USERPOOL_ID" \
    --username "$USER_LOGIN" \
    --user-attributes \
      Name=email,Value="$USER_EMAIL" \
      Name=email_verified,Value=True \
      Name=name,Value="$USER_NAME" \
    --temporary-password "$USER_PASSWORD" \
    --message-action SUPPRESS \
    --desired-delivery-mediums EMAIL \
    --query 'User.Attributes[?Name==`sub`].Value[]' \
    --output text
)

echo "Created user with login='$USER_LOGIN' and password='$USER_PASSWORD'"
echo "Cognito sub claim is '$EXTERNAL_ID'"

Map users to the organization

This example uses API-based provisioning. Map the user created in Cognito to a GoodData user by storing the Cognito sub claim in the authenticationId attribute.

Avoid Assigning the Same AuthenticationId to Multiple Users

Although the API allows multiple users to share the same authenticationId, only the alphabetically first userId will be able to log in.

Create a New User

PAYLOAD=$(cat <<EOF
{
  "data": {
    "id": "$USER_LOGIN",
    "type": "user",
    "attributes": {
      "authenticationId": "$EXTERNAL_ID"
    },
    "relationships": {
      "userGroups": {
        "data": [
          {
            "id": "adminGroup",
            "type": "userGroup"
          }
        ]
      }
    }
  }
}
EOF
)

curl --request POST \
  --header "Authorization: Bearer $API_TOKEN" \
  --header 'Content-Type: application/vnd.gooddata.api+json' \
  --data "$PAYLOAD" \
  "$HOST_URL/api/v1/entities/users"

Update an Existing User

If the user already exists in GoodData, update it instead:

curl --request PUT \
  --header "Authorization: Bearer $API_TOKEN" \
  --header 'Content-Type: application/vnd.gooddata.api+json' \
  --data "$PAYLOAD" \
  "$HOST_URL/api/v1/entities/users/$USER_LOGIN"

For more information about users and user groups, see Manage Users.

Log In to GoodData

Open your organization URL in a web browser and start the sign-in flow.

GoodData redirects the user to the Cognito hosted sign-in page. Log in with the Cognito username and temporary password. On the first successful login, Cognito prompts the user to change the password.

Cleanup

When you are done testing, you can remove the AWS resources created in this example:

aws cognito-idp delete-user-pool-domain \
  --user-pool-id "$USERPOOL_ID" \
  --domain "$COGNITO_DOMAIN_PREFIX"

aws cognito-idp delete-user-pool \
  --user-pool-id "$USERPOOL_ID"

If you also want to remove the GoodData identity provider object, first switch the organization to a different active identity provider, and then delete the Cognito identity provider:

curl --request DELETE \
  --header "Authorization: Bearer $API_TOKEN" \
  "$HOST_URL/api/v1/entities/identityProviders/$IDP_ID"