Front-End Web & Mobile

Understanding Amazon Cognito Authentication

Amazon Cognito helps you create unique identifiers for your end users that are kept consistent across devices and platforms. Cognito also delivers temporary, limited-privilege credentials to your application to access AWS resources. Today, I’m going to cover the basics of how authentication in Cognito works and explain the life cycle of an identity inside your identity pool.

Basic Authflow

As covered in previous blog posts, a user authenticating through Cognito will go through a three-step process to bootstrap their credentials:

  1. GetId
  2. GetOpenIdToken
  3. AssumeRoleWithWebIdentity

Once they have completed this three-step process, they will be able to access other AWS services that have been defined in your role’s access policies.

GetId

The GetId API call is the first call necessary to establish a new identity in Cognito.

Unauthenticated Access

One of Cognito’s best features is the ability to allow unauthenticated “guest” access in your applications. If this feature is enabled in your identity pool, users can request a new identity ID at any time via the GetId API. The application is expected to cache this identity ID to make subsequent calls to Cognito (the AWS SDK for iOS, AWS SDK for Android, and AWS SDK for JavaScript in the Browser all have credentials providers that handle this caching for you).

Authenticated Access

When you’ve configured your application with support for a public login provider (Facebook, Google+, Login with Amazon), users will also be able to supply tokens (OAuth or OpenID Connect) that identify them in those providers. When used in a call to GetId, Cognito will either create a new authenticated identity or return the identity already associated with that particular login. Cognito does this by validating the token with the provider and ensuring that:

  1. The token is valid and from the configured provider.
  2. The token is not expired.
  3. The token matches the application identifier created with that provider (e.g., Facebook app ID).
  4. The token matches the user identifier.

GetOpenIdToken

The GetOpenIdToken API call is called after we have an established identity ID. If we have a cached identity ID, this can be the first call we make during an app session.

Unauthenticated Access

If we have an unathenticated identity, all that is necessary to get a token for that identity is the identity ID itself. If the ID is authenticated (or disabled), it is not possible to get an unauthenticated token for that identity.

Authenticated Access

If we have an authenticated identity, we must pass at least one valid token for a login already associated with that identity. All tokens passed in during the GetOpenIdToken call must pass the same validation mentioned earlier; if any fail, the whole call fails. The response from the GetOpenIdToken call also includes the identity ID. This is because, in the authenticated case, the identity ID you pass in may not be the one that is returned.

Linking Logins

If we pass in a login token not already associated with any identity, this login is considered to be “linked” to the associated identity. You may only link one login per public provider, attempts to link more than one will result in a ResourceConflictException. If a login is merely linked to an existing identity, the identity ID returned from GetOpenIdToken will be the same as what was passed in.

Merging Identities

If we pass in a login token that is not currently linked to the given identity, but is linked to another identity, the two identities are merged. Once merged, one identity becomes the parent/owner of all associated logins and the other is disabled. In this case, the identity ID of the parent/owner is returned. You are expected to update your local cache if this value differs (this is handled for you if you are using the providers in the iOS SDK, Android SDK, or JavaScript SDK).

AssumeRoleWithWebIdentity

Once we have have an OpenID Connect token, we can then trade this for temporary AWS credentials via the AssumeRoleWithWebIdentity API call in STS. This call is no different than if we were using Facebook, Google+, or Login with Amazon directly, except we are passing a Cognito token instead of a token from one of the other public providers.

Because there’s no restriction on the number of identities that can be created, it’s important to understand the permissions that are being granted to your users. The Cognito team recommends having two different roles for your application: one for unauthenticated users, and one that for authenticated users. The Cognito console will create these for you by default when you first set up your identity pool. The access policy for these two roles will be exactly the same: it will grant users access to the Amazon Cognito Sync service as well as to submit events to Amazon Mobile Analytics. You are welcome and encouraged to modify these roles to meet your needs.

Role Trust and Permissions

The way these roles differ is in their trust relationships. Let’s take a look at an example trust policy for an unauthenticated role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "us-east-1:12345678-dead-beef-cafe-123456790ab"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "unauthenticated"
        }
      }
    }
  ]
}

This policy defines that we want to allow federated users from cognito-identity.amazonaws.com (the issuer of the OpenID Connect token) to assume this role. Additionally, we make the restriction that the aud of the token, in our case the identity pool ID, matches our identity pool. Finally, we specify that the amr of the token contains the value unauthenticated.

When Cognito creates a token, it will set the amr of the token to be either unauthenticated or authenticated and in the authenticated case will include any providers used during authentication. This means you can create a role that trusts only users that logged in via Facebook, simply by changing the amr clause to look like the following:

"ForAnyValue:StringLike": {
  "cognito-identity.amazonaws.com:amr": "graph.facebook.com"
}

Be careful when changing your trust relationships on your roles, or when trying to use roles across identity pools. If your role is not configured to correctly trust your identity pool, you will see an exception from STS like the following:

AccessDenied -- Not authorized to perform sts:AssumeRoleWithWebIdentity

If you see this, double check that you are using an appropriate role for your identity pool and authentication type.

Conclusion

I hope this clarifies how Cognito authentication works and how the credentials providers in the various SDKs can handle these details for you. If you have any comments or questions, please free to leave a comment here or visit our forums and we will try to assist you.