localstack / localstack

💻 A fully functional local AWS cloud stack. Develop and test your cloud & Serverless apps offline
https://localstack.cloud
Other
55.92k stars 3.98k forks source link

bug: Cognito custom auth flow lambdas missing `clientMetadata` in `event.request` #11687

Open theotherian opened 3 days ago

theotherian commented 3 days ago

Is there an existing issue for this?

Current Behavior

As a reference point, I'm trying to implement passkey-based authentication in Localstack as a proof of concept based on the following article: https://aws.amazon.com/blogs/security/how-to-implement-password-less-authentication-with-amazon-cognito-and-webauthn/

I have a Cognito setup working with custom challenges executing with Lambas for create, define and verify challenge.

It seems clientMetadata doesn't work as expected. I'm passing a key and value both via the awslocal command by sending --client-metadata '{"key":"value"}' to response-to-auth-challenge and I'm sending the same thing via the JavaScript API and event.request.clientMetadata is always undefined both in the VerifyAuthChallenge lambda and all other lambdas. In fact, event.request doesn't even have a clientMetadata field in it even though it has all the other expected fields like userAttributes, challengeAnswer, etc.

According to https://repost.aws/knowledge-center/cognito-clientmetadata-lambda-trigger the clientMetadata field passed to ResponseToAuthChallenge should not only be present in that request but should also be visible to almost every other custom Lambda in the flow.

Expected Behavior

The event.request.clientMetadata field should both be available in all Cognito custom Lambdas and should also be carried to other Lambas in accordance with the AWS documentation here: https://repost.aws/knowledge-center/cognito-clientmetadata-lambda-trigger

How are you starting LocalStack?

With a docker-compose file

Steps To Reproduce

How are you starting localstack (e.g., bin/localstack command, arguments, or docker-compose.yml)

I have some Lambdas defined locally locally that might be a bit convoluted to strip down and upload along with this example, but if you need them in order to reproduce the issue I could probably create no-op versions.

Docker compose:

version: "3.9"
name: phobos-prototype
services:
  localstack:
    container_name: phobos-localstack
    image: "localstack/localstack-pro:3.8"
    security_opt:
      - no-new-privileges:true
    environment:
      LS_LOG: trace
      AWS_ACCESS_KEY_ID: ABCDEF0123456789
      AWS_SECRET_ACCESS_KEY: ABCDEF0123456789
      LOCALSTACK_AUTH_TOKEN: ${LOCALSTACK_AUTH_TOKEN}
      DOCKER_HOST: unix:///var/run/docker.sock
    ports:
      - "14566:4566"
    volumes:
      - ./init-localstack.sh:/etc/localstack/init/ready.d/init-localstack.sh
      - /var/run/docker.sock:/var/run/docker.sock
      - ./lambdas:/root/lambdas

init-localstack.sh startup script:

#! /bin/bash

# lambda functions

# CreateAuthChallenge lambda
zip -j /root/output/create.zip /root/lambdas/create/index.js
awslocal lambda create-function \
    --function-name create_auth_challenge \
    --runtime nodejs20.x \
    --zip-file fileb:///root/output/create.zip \
    --handler index.handler \
    --role arn:aws:iam::000000000000:role/lambda-role

# DefineAuthChallenge lambda
zip -j /root/output/define.zip /root/lambdas/define/index.js
awslocal lambda create-function \
    --function-name define_auth_challenge \
    --runtime nodejs20.x \
    --zip-file fileb:///root/output/define.zip \
    --handler index.handler \
    --role arn:aws:iam::000000000000:role/lambda-role

# VerifyAuthChallenge lambda
zip -j /root/output/verify.zip /root/lambdas/verify/index.js
awslocal lambda create-function \
    --function-name verify_auth_challenge \
    --runtime nodejs20.x \
    --zip-file fileb:///root/output/verify.zip \
    --handler index.handler \
    --role arn:aws:iam::000000000000:role/lambda-role

USER_POOL="demouserpools"
awslocal cognito-idp create-user-pool \
    --pool-name demo \
    --admin-create-user-config '{"AllowAdminCreateUserOnly":true}' \
    --auto-verified-attributes "email" \
    --user-pool-tags "_custom_id_=us-east-1_${USER_POOL}" \
    --user-pool-add-ons "AdvancedSecurityMode=AUDIT" \
    --schema '[{"Name": "publicKeyCred", "AttributeDataType": "String", "Mutable": true, "Required": false, "StringAttributeConstraints": { "MinLength": "1", "MaxLength": "1024" }}]' \
    --lambda-config '{"CreateAuthChallenge":"arn:aws:lambda:us-east-1:000000000000:function:create_auth_challenge", "DefineAuthChallenge":"arn:aws:lambda:us-east-1:000000000000:function:define_auth_challenge", "VerifyAuthChallengeResponse":"arn:aws:lambda:us-east-1:000000000000:function:verify_auth_challenge"}'

awslocal cognito-idp create-user-pool-client \
    --no-generate-secret \
    --explicit-auth-flows "ALLOW_CUSTOM_AUTH" \
    --write-attributes "custom:publicKeyCred" "name" \
    --read-attributes "name" \
    --user-pool-id "us-east-1_${USER_POOL}" \
    --client-name "_custom_id_:${USER_POOL}client"

# public key is base64 encoded version of
# {"id":"my-key","publicKey":"-----BEGIN PUBLIC KEY-----\n<contents omitted>\n-----END PUBLIC KEY-----\n"}
awslocal cognito-idp sign-up \
    --client-id "${USER_POOL}client" \
    --username "demouser" \
    --password AbCd123! \
    --user-attributes Name="custom:publicKeyCred",Value="<omitted>" Name="name",Value="demo user"

awslocal cognito-idp admin-confirm-sign-up --user-pool-id "us-east-1_${USER_POOL}" --username "demouser"

Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

awslocal cognito-idp initiate-auth --client-id demouserpoolclient --auth-flow CUSTOM_AUTH --auth-parameters USERNAME="demouser"

# you need the session id from the first command to pass as the session to this one
# you'd also need to establish a public/private key inline with the user creation above in order to sign the challenge
awslocal cognito-idp respond-to-auth-challenge --client-id demouserpoolclient --challenge-name "CUSTOM_CHALLENGE" --session "<session identifier>" --client-metadata '{"key":"value"}' --challenge-responses USERNAME="demouser",ANSWER="<omitted signed contents>"

Environment

- OS: MacOS 14.7
- LocalStack:
  LocalStack version: 3.8.1
  LocalStack Docker image sha: 6caa495b79f3
  LocalStack build date: 10/8/2024
  LocalStack build git hash: 307c4c26

Anything else?

No response

localstack-bot commented 3 days ago

Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our Slack Community for real-time help and drop a message to LocalStack Pro Support if you are a Pro user! If you are willing to contribute towards fixing this issue, please have a look at our contributing guidelines and our contributing guide.

theotherian commented 1 day ago

Wanted to add one more piece of detail to this to give a more complete picture.

You should notice right away that clientMetadata is absent in event.request when the VerifyAuthChallenge lambda is invoked, but I also wanted to point out that for the use case I was working towards I was also using a PreTokenGeneration lambda using the PreTokenGenerationConfig approach aiming for V2_0 and that was also missing clientMetadata.

Pointing this out because I know per the AWS docs that VerifyAuthChallenge should forward its clientMetadata to PreTokenGeneration as well as other functions.