in src/lambda-edge/parse-auth/index.ts [194:290]
function validateQueryStringAndCookies(props: {
querystring: string;
cookies: ReturnType<typeof 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(" ")
);
}
// The querystring state should be a JSON string
let parsedState: { nonce?: string; requestedUri?: string };
try {
parsedState = JSON.parse(
Buffer.from(urlSafe.parse(state), "base64").toString()
);
} catch {
throw new Error(
'Invalid query string. Your query string does not include a valid "state" parameter'
);
}
// The querystring state needs to include the right pieces
if (!parsedState.requestedUri || !parsedState.nonce) {
throw new Error(
'Invalid query string. Your query string does not include a valid "state" parameter'
);
}
// The querystring state needs to correlate to the cookies
const { nonce: originalNonce, pkce, nonceHmac } = props.cookies;
if (
!parsedState.nonce ||
!originalNonce ||
parsedState.nonce !== originalNonce
) {
if (!originalNonce) {
throw new RequiresConfirmationError(
"Your browser didn't send the nonce cookie along, but it is required for security (prevent CSRF)."
);
}
throw new RequiresConfirmationError(
"Nonce mismatch. This can happen if you start multiple authentication attempts in parallel (e.g. in separate tabs)"
);
}
if (!pkce) {
throw new Error(
"Your browser didn't send the pkce cookie along, but it is required for security (prevent CSRF)."
);
}
// Nonce should not be too old
const nonceTimestamp = parseInt(
parsedState.nonce.slice(0, parsedState.nonce.indexOf("T"))
);
if (timestampInSeconds() - nonceTimestamp > CONFIG.nonceMaxAge) {
throw new RequiresConfirmationError(
`Nonce is too old (nonce is from ${new Date(
nonceTimestamp * 1000
).toISOString()})`
);
}
// Nonce should have the right signature: proving we were the ones generating it (and e.g. not malicious JS on a subdomain)
const calculatedHmac = sign(
parsedState.nonce,
CONFIG.nonceSigningSecret,
CONFIG.nonceLength
);
if (calculatedHmac !== nonceHmac) {
throw new RequiresConfirmationError(
`Nonce signature mismatch! Expected ${calculatedHmac} but got ${nonceHmac}`
);
}
return { code, pkce, requestedUri: parsedState.requestedUri || "" };
}