awslabs / cognito-at-edge

Serverless authentication solution to protect your website or Amplify application
Apache License 2.0
168 stars 54 forks source link

Mitigate CSRF attacks #65

Closed vikas-reddy closed 11 months ago

vikas-reddy commented 1 year ago

What would you like to be added:

  1. Mitigate CSRF attacks by using the state, nonce, signed nonce and pkce cookies
  2. Encode and store redirect uri in state to be used later to send users to where they wanted to go, after oauth token exchange is done. (See next section for more details)

https://auth0.com/docs/secure/attack-protection/state-parameters

https://github.com/aws-samples/cloudfront-authorization-at-edge/blob/master/src/lambda-edge/check-auth/index.ts#L71-L83

// _getRedirectToCognitoUserPoolResponse would generate and store nonce, signed nonce, pkce and state in the browser cookie
const nonce = generateNonce();
  const state = {
    nonce,
    nonceHmac: common.sign(
      nonce,
      CONFIG.nonceSigningSecret,
      CONFIG.nonceLength
    ),
    ...generatePkceVerifier(),
  };

https://github.com/aws-samples/cloudfront-authorization-at-edge/blob/017089d8aea9239f22f4bd8bbe8a7b607eb1fe5b/src/lambda-edge/parse-auth/index.ts#L177

// _fetchTokensFromCode would verify that `state` returned by AWS Cognito is related to the one we set in the previous step
function validateQueryStringAndCookies(props: {
  querystring: string;
  cookies: ReturnType<typeof common.extractAndParseCookies>;
}) {
  // Check if Cognito threw an Error. Cognito puts the error in the query string
  const {
    code,
    state,
    error: cognitoError,
    error_description,
  } = parseQueryString(props.querystring);
  if (cognitoError) {
    throw new Error(`[Cognito] ${cognitoError}: ${error_description}`);
  }

  // The querystring needs to have an authorization code and state
  if (
    !code ||
    !state ||
    typeof code !== "string" ||
    typeof state !== "string"
  ) {
    throw new Error(
      [
        'Invalid query string. Your query string does not include parameters "state" and "code".',
        "This can happen if your authentication attempt did not originate from this site.",
      ].join(" ")
    );
  }

Why is this needed:

We are planning to use this library for our new authentication gateway application. As opposed to the intended use case of this library, which is to use the handle method to put static S3 files behind an authentication gate, we are planning to use the individual handler methods directly in our application. Our auth gateway app will be a set of Lambda@Edge handlers that work as an intermediary between React frontend clients and AWS Cognito to do

  1. authentication duties,
  2. exchange code for tokens, and
  3. sending tokens as HttpOnly cookies, which clients can use to communicate with some Amazon internal API's

Handlers

  1. /signIn: Mapped to the existing method _getRedirectToCognitoUserPoolResponse
  2. /parseAuth: Mapped to existing method _fetchTokensFromCode
  3. /refreshToken: Mapped to existing method _fetchTokensFromRefreshToken

In our Cloudfront distribution setup, we'd do something like this

// signIn Lambda@Edge handler
const authenticator = new Authenticator({...})
exports.handler = async (request) => authenticator._getRedirectToCognitoUserPoolResponse(request, redirectUri)

// parseAuth Lambda@Edge handler
const authenticator = new Authenticator({...})
exports.handler = async (request) => authenticator._fetchTokensFromCode(redirectUri, code)

Slack or email me on vikred@amazon.com for additional details