Skip to content

cognito: Using a token for UserPoolIdentityProviderApple doesn't resolve at deploy #31378

@Someone5328546

Description

@Someone5328546

Describe the bug

I'm attempting to create a Cognito user pool using Apple as an identity provider. I created the secret key and manually saved it in Secrets Manager using the AWS console.

Originally, I attempted this:

const applePrivateKeySecret = Secret.fromSecretNameV2(this, 'ApplePrivateKey', 'TheKeyNameInSecretsManager')

const appleProvider = new UserPoolIdentityProviderApple(this, 'UserPoolIdentityProviderApple',
{
    clientId: 'com.myapp',
    teamId: '123456',
    keyId: '123456',
    privateKey: applePrivateKeySecret.secretValue.toString(),
    userPool: this.userPool
})

It didn't work on deploy since the secret is exposed as a string.

I also tried saving the value as a SecureString in AWS Systems Manager and retrieving it like this:

const applePrivateKeyParameter = SecretValue.ssmSecure('AppleSignInPrivateKey').toString()

Which didn't give the same error regarding the value being exposed, but results in a TypeError (described just below), since apparently this token is unresolved when I attempt to deploy.

So I attempted to create a lambda that could be called by my Cognito stack to retrieve the token from Secrets Manager:

The lambda code:

import {
    GetSecretValueCommand,
    SecretsManagerClient
} from '@aws-sdk/client-secrets-manager'

export const handler = async (event: any = {}) : Promise <any> =>
{
    try
    {
        const client = new SecretsManagerClient()
        const command = new GetSecretValueCommand({ SecretId: 'TheKeyNameInSecretsManager' })
        const response = await client.send(command)

        if (response.SecretString)
        {
            return {
                Status: 'SUCCESS',
                PhysicalResourceId: 'ApplePrivateKey',
                Data:
                {
                    SecretValue: response.SecretString
                }
            }
        }
        else
        {
            return {
                Status: 'FAILED',
                Reason: 'SecretString is empty.',
            }
        }
    }
    catch (error: any)
    {
        return {
            status: 'FAILED',
            message: error?.message || 'Unknown Failure.'
        }
    }
}

In my Cognito Stack, I retrieve the token and attempt to use it, but UserPoolIdentityProviderApple is seemingly undefined since the token never resolves.

This line will always throw an error:

this.userPool.registerIdentityProvider(appleProvider)
TypeError: Cannot read properties of undefined (reading 'registerIdentityProvider')

This comment on another issue seems to outline what I'm doing, more or less.

As far as I can tell, aren't tokens supposed to resolve on deployment? But this unresolved token is always causing the above error when I attempt to deploy.

What is the correct way to retrieve a secret from Secrets Manager to use in this context?

The Cognito Stack code:

import {
    AccountRecovery,
    CfnIdentityPool,
    OAuthScope,
    UserPool,
    UserPoolClient,
    UserPoolClientIdentityProvider,
    UserPoolIdentityProviderApple
} from 'aws-cdk-lib/aws-cognito'
import { 
    App,
    CustomResource,
    Stack,
    StackProps,
    Token
} from 'aws-cdk-lib'
import { join } from 'path'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'
import { Provider } from 'aws-cdk-lib/custom-resources'
import { Runtime } from 'aws-cdk-lib/aws-lambda'
import { Secret } from 'aws-cdk-lib/aws-secretsmanager'



export class CognitoStack extends Stack
{
    public userPool: UserPool
    public userPoolClient: UserPoolClient
    public identityPool: CfnIdentityPool

    constructor(
        scope: App,
        id: string,
        props: StackProps
    )
    {
        super(scope, id, props)



        const applePrivateKeySecret = Secret.fromSecretNameV2(this, 'ApplePrivateKey', 'TheKeyNameInSecretsManager')



        // Create a Lambda to securely retrieve the Apple private key at runtime.
        const getApplePrivateKeyLambda = new NodejsFunction(this, 'GetApplePrivateKey',
        {
            runtime: Runtime.NODEJS_20_X,
            functionName: 'getApplePrivateKey',
            entry: join(__dirname, '..', 'lambda', 'getApplePrivateKey.ts')
        })



        // Grant access to `applePrivateKeySecret`.
        applePrivateKeySecret.grantRead(getApplePrivateKeyLambda)



        // Create a custom resource provider for invoking `getApplePrivateKey`.
        const getApplePrivateKeyProvider = new Provider(this, 'GetApplePrivateKeyProvider',
        {
            onEventHandler: getApplePrivateKeyLambda
        })
    


        // Create a custom resource to call `getApplePrivateKey`.
        const getApplePrivateKeyResource = new CustomResource(this, 'GetApplePrivateKeyResource',
        {
            serviceToken: getApplePrivateKeyProvider.serviceToken
        })



        // Access the secret value directly from the custom resource's attributes.
        const applePrivateKey = getApplePrivateKeyResource.getAtt('ApplePrivateKey').toString()

        console.log(applePrivateKey)
        
        if (typeof applePrivateKey === 'undefined')
        {
            throw new Error('Failed to retrieve Apple private key.');
        }

        

        // Configure Apple as an Identity Provider.
        const appleProvider = new UserPoolIdentityProviderApple(this, 'UserPoolIdentityProviderApple',
        {
            clientId: 'com.myapp',
            teamId: '123456',
            keyId: '123456',
            privateKey: applePrivateKey,
            userPool: this.userPool
        })



        // Create a Cognito User Pool.
        this.userPool = new UserPool(this, 'UserPool',
        {
            signInAliases:
            {
                email: false,
                phone: false,
                preferredUsername: false,
                username: true
            }
        })
    


        // Register Apple as an Identity Provider.
        this.userPool.registerIdentityProvider(appleProvider)



        // Create a User Pool Client.
        this.userPoolClient = new UserPoolClient(this, 'UserPoolClient',
        {
            userPool: this.userPool,
            oAuth:
            {
                flows:
                {
                    authorizationCodeGrant: true
                },
                scopes:
                [
                    OAuthScope.OPENID
                ]
            },
            preventUserExistenceErrors: true,
            supportedIdentityProviders:
            [
                UserPoolClientIdentityProvider.APPLE
            ]
        })



        // Create an Identity Pool for Federated Identities.
        this.identityPool = new CfnIdentityPool(this, 'IdentityPool',
        {
            allowUnauthenticatedIdentities: false,
            cognitoIdentityProviders:
            [
                {
                    clientId: this.userPoolClient.userPoolClientId,
                    providerName: this.userPool.userPoolProviderName,
                    serverSideTokenCheck: true
                }
            ]
        })
    }
}

Regression Issue

  • Select this option if this issue appears to be a regression.

Last Known Working CDK Version

No response

Expected Behavior

I expected the token to resolve on deployment so I could use it to create an identity provider for Cognito.

Current Behavior

The token does not resolve and breaks the deployment. If I add a check Token.isUnresolved(), it will always be true.

Reproduction Steps

See above code please.

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.156.0 (build 2966832)

Framework Version

No response

Node.js Version

20.16.0

OS

Windows 10

Language

TypeScript

Language Version

TypeScript 5.5.3

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    @aws-cdk/aws-cognitoRelated to Amazon CognitobugThis issue is a bug.effort/mediumMedium work item – several days of effortp2

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions