Search code examples
amazon-web-servicesauthenticationamazon-cognitoopenid-connectaws-amplify

AWS Amplify Auth with Cognito User Pool not returning nonce or at_hash claim in JWT id_token


I'm trying to use Amplify Auth to implement an OpenID Connect Implicit Flow to provide SSO to a number of React clients.

I've been able to get this working with Cognito Hosted UI, but that requires other apps users to click a button to confirm login in order to authenticate. I'd prefer for it to be seamless ie when a user is logged in on one site and navigate to another they are automatically authenticated if they have a session with the auth provider.

To try and achieve this I've set up a separate Amplify app that uses the React Authenticator Component.

I'm able to authenticate with this and redirect back to the client. However the id_token doesn't contain the at_hash or nonce claim. Presumably, the at_hash is missing because the authentication provider app is not sending the responseType of token id_token when it authenticates with Cognito. The nonce is missing because I haven't found a way to pass it in.

  1. Is there a way to get Amplify Authenticator SignIn to request an id_token with the at_hash claim?

  2. Is it possible to pass a nonce value through to the id_token claims?

Note I'm trying to adhere to: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowSteps Specifically for this part: 3.2.2.10. ID Token

import React from 'react';
import { Authenticator, ConfirmSignIn, SignIn } from 'aws-amplify-react';
import Amplify, { Auth } from 'aws-amplify';
import awsconfig from './aws-exports';

Amplify.configure(awsconfig);

const getSearchParams = () =>
    window.location.search.substr(1);

const getValueFromSearchParam = (key) =>
    new URLSearchParams(getSearchParams()).get(key);

const getRedirectUri = () => {
    const redirect_uri = getValueFromSearchParam('redirect_uri');
    return redirect_uri ? decodeURI(redirect_uri) : null;
};

const Login = () => {
    const handleAuthStateChange = (state) => {
        if(state === 'signedIn') {
            const redirect_uri = getRedirectUri();
            const state = getValueFromSearchParam('state');
            if(redirect_uri === null) {
                throw new Error('No redirect_uri provided');
            }
            Auth.currentSession().then(currentSession => {
                const id_token = currentSession.idToken.jwtToken;
                const access_token = currentSession.accessToken.jwtToken;
                const redirect = `${redirect_uri}#access_token=${access_token}&id_token=${id_token}&state=${state}`;
                window.location.replace(redirect);
            }).catch(err => console.error(err));
        }
    };

    return (
        <Authenticator
            hideDefault={true}
            onStateChange={handleAuthStateChange}
        >
            <SignIn  />
            <ConfirmSignIn/>
        </Authenticator>
    );
};

Solution

  • A few points to consider:

    • The custom UI may not be the best option for standards based support
    • If you use response_type = token id_token you use the implicit flow is deprecated from a security viewpoint these days
    • If you use the authorization code flow (PKCE) you receive all tokens on the back channel and do not need to make use a nonce or at_hash so the flow is simpler and likely to work with more libraries

    If it helps, my blog's introductory code example uses Cognito with the Hosted UI (not the custom UI). I can confirm that you can use SSO across multiple apps without prompts.