aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.44k stars 2.13k forks source link

Amplify JS to create 'aws-waf-token' header and send with Auth requests #12308

Open joknoxy opened 1 year ago

joknoxy commented 1 year ago

Is this related to a new or existing framework?

Angular, React

Is this related to a new or existing API?

Authentication

Is this related to another service?

AWS WAF Intelligent threat javascript integrations

Describe the feature you'd like to request

In order for customers using Amplify client JS frameworks to be able to use the AWS WAF intelligent threat managed rulegroups (AWSManagedRulesACFPRuleSet, AWSManagedRulesATPRuleSet, AWSManagedRulesBotControlRuleSet) to protect Cognito endpoints from malicious Bots, the client needs to send an 'aws-waf-token' header with Amplify 'Auth' requests.

The AWS WAF intelligent threat managed rule groups provide operations for running silent challenges against the user's browser, and for handling the AWS WAF tokens that provide proof of successful challenge and CAPTCHA responses.

POST requests that match AWS WAF rules configured with a CAPTCHA or Challenge action are blocked unless they contain a header (or cookie) named 'aws-waf-token' with an encrypted value that indicates whether Challenge/CAPTCHA has successfully previously been completed. The value of the header is contained in a global variable called 'currentToken' and the way it's used by the WAF provided JS can be seen here in challenge.js - we need Amplify Auth to imitate the behavior in 'challenge.js' if the 'currentToken' variable exists.

Please see the following for more information - https://docs.aws.amazon.com/waf/latest/developerguide/waf-javascript-api.html

Describe the solution you'd like

We need Amplify Auth to emulate the behavior in challenge.js and send header 'aws-waf-token' with Auth requests if the 'currentToken' variable exists.

Describe alternatives you've considered

None

Additional context

No response

Is this something that you'd be interested in working on?

cwomack commented 1 year ago

Hello, @joknoxy and thank you for opening this issue. We've marked this as a feature request and will review this with the team internally. This may be something that requires additional partnership with the Cognito team, but I'll double check on that and provide updates as they come.

cwomack commented 1 year ago

Doesn't look like there's a limitation from the Cognito side, we'll just need to implement support on the library side at this point for the new WAF feature.

ivan-kiselev commented 9 months ago

Has anybody found not-too-tedious to implement workaround here?

Also, I am not quite sure that setting aws-waf-token to AWS cognito is the right thing? At least I cannot get successful result with curl.

I do render CAPTCHA and solve it and receive a valid token and then copy the request as cURL from browser, inject aws-waf-token Header and it's still demanding captcha:

curl 'https://cognito-idp.eu-central-1.amazonaws.com/' -X POST \ 
  -H 'User-Agent: some_nonsense' \ 
  -H 'Accept: */*' \ 
  -H 'Accept-Language: en-US,en;q=0.5' \ 
  -H 'Accept-Encoding: gzip, deflate, br' \
  -H 'Referer: $MY_DOMAIN' \
  -H 'content-type: application/x-amz-json-1.1' \
  -H 'x-amz-target: AWSCognitoIdentityProviderService.SignUp' \
  -H 'cache-control: no-store, no-cache' \
  -H 'x-amz-user-agent: aws-amplify/6.0.13 auth/1 framework/1' \
  -H 'Origin: ${MY_ORIGIN}' \
  -H 'DNT: 1' -H 'Connection: keep-alive' \
  -H 'Sec-Fetch-Dest: empty' \
  -H 'Sec-Fetch-Mode: cors' \
  -H 'Sec-Fetch-Site: cross-site' \
  -H 'Sec-GPC: 1' \
  -H 'Pragma: no-cache' \
  -H 'TE: trailers' \
  -H 'aws-waf-token: ${TOKEN}' \ // <- This is the token I receive in browser after successfully solving the challenge
  --data-raw '{"Username":"bot","Password":"botpassword","UserAttributes":[{"Name":"email","Value":"botemail@botdomain"}],"ClientId":"..."}'

{"__type":"ForbiddenException","message":"Request not allowed due to WAF captcha."}%

@joknoxy are you certain that the aws-waf-token header is the right thing? I get it that's what one uses with CloudFront and ALB, but I am not quite sure that works the same way here.. well at least I can't make curl request, identical to what the browser produces but with the aws-waf-token header injected, work?

ivan-kiselev commented 9 months ago

I also wrapped simulated SignUp function logic there to wrap it with another JS Wrapper as described in AWS Documentation here like so:

in index.html:

  <script type="text/javascript"
    src="${path copied from app waf integration}/challenge.js" defer></script>

declare global {
  interface Window {
    AwsWafIntegration: {
      fetch: (url: string, options: { method: string; headers: Record<string, string>, body: Object }) => Promise<any>;
    }
  }
}

export async function signUpCustom(input: SignUpInput): Promise<SignUpOutput> {
  const endpoint = 'https://cognito-idp.eu-central-1.amazonaws.com/';
  const headers = {
    'Content-Type': 'application/x-amz-json-1.1',
    'X-Amz-Target': 'AWSCognitoIdentityProviderService.SignUp',
    'Accept': 'application/json, text/plain, */*',
  };

  const body = {
    ClientId: '',
    Username: input.username,
    Password: input.password,
    UserAttributes: Object.entries(input.options?.userAttributes ?? {}).map(([Name, Value]) => ({ Name, Value })),
    ValidationData: input.options?.validationData,
  };

  const login_response = await window.AwsWafIntegration.fetch(endpoint, {
    method: 'POST',
    headers,
    body: JSON.stringify(body)
  });
  if (login_response.ok) {
    const data = await login_response.json();
    const { UserConfirmed, UserSub, CodeDeliveryDetails } = data;
    return {
      userConfirmed: UserConfirmed,
      userSub: UserSub,
      codeDeliveryDetails: CodeDeliveryDetails ? {
        Destination: CodeDeliveryDetails.Destination,
        DeliveryMedium: CodeDeliveryDetails.DeliveryMedium,
        AttributeName: CodeDeliveryDetails.AttributeName,
      } : undefined,
    };
  } else {
    const errorData = await login_response.json();
    console.error("Error response data:", errorData);
    throw errorData
  }
}

And what I observe is that now it injects x-aws-waf-token header, but still doesn't work:

POST / HTTP/2
Host: cognito-idp.eu-central-1.amazonaws.com
User-Agent: asdasdasdasddas
Accept: application/json, text/plain, */*
Content-Type: application/x-amz-json-1.1
X-Amz-Target: AWSCognitoIdentityProviderService.SignUp
x-aws-waf-token: // <- FRESHLY OBTAINED WAF VOUCHER
Content-Length: 159
DNT: 1
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Sec-GPC: 1

...

other headers

So, WAF doesn't seem to be persuaded even when it's native App integration Javascript library is used?

ivan-kiselev commented 9 months ago

Oh ok, the code from previous example actually works āœ… āœ… āœ… āœ… , I had misconfiguration on WAF side.

dawnlootlabs commented 5 months ago

@cwomack Any update here? We'd like to use AWS WAF Captchas with the amplify library as well.