Last active
December 2, 2020 21:09
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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