Skip to content

Instantly share code, notes, and snippets.

@JoshM1994
Last active December 2, 2020 21:09
Show Gist options
  • Save JoshM1994/17a9e835dd0e0560aaaf45e766b8dae0 to your computer and use it in GitHub Desktop.
Save JoshM1994/17a9e835dd0e0560aaaf45e766b8dae0 to your computer and use it in GitHub Desktop.
Node.js (TypeScript) AWS Hosted Cognito UI auth code grant programmatically (PKCE, Client Secret, Code Challenge)
// Based on https://forums.aws.amazon.com/thread.jspa?messageID=832982#832982
import { randomBytes, createHash } from 'crypto';
import { Buffer } from 'buffer';
import fetch from 'node-fetch';
import { URLSearchParams } from 'url';
const base64UrlSafe = (b64: string) => b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
async function main() {
const AUTH_DOMAIN = "DOMAIN.auth.REGION.amazoncognito.com";
const CLIENT_ID = "CLIENT_ID";
const CLIENT_SECRET="CLIENT_SECRET"
const RESPONSE_TYPE="code"
const REDIRECT_URI="REDIRECT_URI"
const SCOPE="aws.cognito.signin.user.admin+openid+profile"
const USERNAME="USERNAME"
const PASSWORD="PASSWORD"
const CODE_CHALLENGE_METHOD="S256"
// Code Challenge is the hash of the verifier as per https://tools.ietf.org/html/rfc7636#section-4
// Start by generating the 64-byte code-verifier
const codeVerifier = base64UrlSafe(randomBytes(64).toString('base64'));
// Then hash it
const codeChallenge = base64UrlSafe(createHash('sha256').update(codeVerifier).digest('base64'));
const xsrfTokenResponse = await fetch(`https://${AUTH_DOMAIN}/oauth2/authorize?response_type=${RESPONSE_TYPE}&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${SCOPE}&code_challenge_method=${CODE_CHALLENGE_METHOD}&code_challenge=${codeChallenge}`, {
method: 'GET',
redirect: 'manual'
})
const redirectLocation = xsrfTokenResponse.headers.get('location');
if (!redirectLocation) throw new Error('No redirect location')
const cookieStrings = xsrfTokenResponse.headers.raw()['set-cookie'].map(cookieString => {
const [cookiePart] = cookieString.split(';');
return cookiePart;
});
const cookies = cookieStrings.reduce((accObj: object, cookieString: string) => {
const [key, val] = cookieString.split('=');
return {
...accObj,
[key]: val
}
}, {})
if (Object.keys(cookies).length === 0) throw new Error('Received invalid number of cookies')
const csrfToken = cookies['XSRF-TOKEN'];
if (!csrfToken) throw new Error('Did not receive csrf token');
const urlencoded = new URLSearchParams();
urlencoded.append('_csrf', csrfToken);
urlencoded.append('username', USERNAME);
urlencoded.append('password', PASSWORD);
const loginResponse = await fetch(redirectLocation, {
headers: {
'cookie': `XSRF-TOKEN=${csrfToken}`,
},
method: 'POST',
body: urlencoded,
redirect: 'manual',
})
const authCodeRedirectUrl = loginResponse.headers.get('location');
if (!authCodeRedirectUrl) throw new Error('No redirect location')
const queryStringParams = authCodeRedirectUrl.replace(REDIRECT_URI, '');
const authCodeParams = new URLSearchParams(queryStringParams);
const authCode = authCodeParams.get('code');
if (!authCode) throw new Error('Could not retrieve auth code');
const GRANT_TYPE = 'authorization_code'
const basicAuth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`, 'utf-8').toString('base64');
const urlEncodedAuthCodeParams = new URLSearchParams();
urlEncodedAuthCodeParams.append('grant_type', GRANT_TYPE);
urlEncodedAuthCodeParams.append('client_id', CLIENT_ID);
urlEncodedAuthCodeParams.append('code', authCode);
urlEncodedAuthCodeParams.append('code_verifier', codeVerifier);
urlEncodedAuthCodeParams.append('redirect_uri', REDIRECT_URI);
const authCodeResponse = await fetch(`https://${AUTH_DOMAIN}/oauth2/token`, {
headers: {
Authorization: `Basic ${basicAuth}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
body: urlEncodedAuthCodeParams,
redirect: 'manual'
});
if (authCodeResponse.ok) {
console.log(authCodeResponse.headers)
console.log(await authCodeResponse.json())
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment