Introduction
In this article, we'll look at a simple JavaScript application that can make authenticated HTTP requests to an AWS API Gateway instance using credentials from a Cognito login.
Background
The source code for this sample project can be found on GitHub.
This video on YouTube is a screencast that shows how to deploy this code to AWS with API Gateway, Cognito, Lambda and S3.
Using the Code
We start by importing the Amplify
and Auth
classes from the AWS Amplify
library:
import Amplify, {Auth} from 'aws-amplify';
The Amplify
class needs to be configured with the AWS region, Cognitio User Pool ID, and the Cognitio App Client ID. We source these from URL query parameters to make the example generic.
var urlParams = new URLSearchParams(window.location.search);
Amplify.configure({
Auth: {
region: 'us-east-1',
userPoolId: urlParams.get('poolId'),
userPoolWebClientId: urlParams.get('appId'),
}
});
This is where we do the initial login. The username and password are sent to Cognito with the Auth.signin()
method, and the response will either be success, or requests for additional information.
We capture only the request for a password change here, as the Cognito service forces every user created via the AWS web console into a state where the initial password must be changed. But there are a number of other cases that you may need to respond to including requests for phone numbers, email address, two factor authentication tokens, etc.
Once the login or password change succeeds, we call the displayTokens()
function.
async function signIn(username, password) {
const user = await Auth.signIn(username, password);
if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
initiallogin.style.display = "none";
changepassword.style.display = "";
changepass.onclick = async () => {
await Auth.completeNewPassword(
user,
newpassword.value
).then((user) => {
displayTokens(user);
});
};
} else {
displayTokens(user);
}
}
In the displayTokens()
function, we get the user session, from which we can get the ID and access tokens. These tokens are sent in the Authorization
header when calling the API Gateway endpoint (passed in via the invokeURL
query parameter).
It is important to note here that we do not add the bearer
prefix in the header value, even though the HTTP specification says you must do this. It is a known bug in API Gateway Cognito authorizers that this prefix is not supported.
function displayTokens(user) {
initiallogin.style.display = "none";
changepassword.style.display = "none";
results.style.display = "";
user.getSession((err, session) => {
accessToken.value = session.accessToken.jwtToken;
idToken.value = session.idToken.jwtToken;
fetch(urlParams.get('invokeURL') + "/test",
{headers: {'Authorization': session.idToken.jwtToken}})
.then(response => response.text())
.then(text => apiCall.value = text);
});
}
The last step is to hook up an event to the initial login button click.
login.onclick = () => signIn(username.value, password.value);
Here is the HTML page that displays the login form:
<html>
<head>
</head>
<body>
<div id="initiallogin">
<label>Username</label>
<input id="username"
type="text"/>
<label>Password</label>
<input id="password"
type="text"/>
<input id="login"
type="button"
value="Login"/>
</div>
<div id="changepassword"
style="display: none">
<label>New Password</label>
<input id="newpassword"
type="text"/>
<input id="changepass"
type="button"
value="Change Password"/>
</div>
<div id="results"
style="display: none">
<h3>ID Token</h3>
<textarea cols="100"
id="idToken"
readonly="true"
rows="15"></textarea>
<h3>Access Token</h3>
<textarea cols="100"
id="accessToken"
readonly="true"
rows="15"></textarea>
<h3>API Call Result</h3>
<textarea cols="100"
id="apiCall"
readonly="true"
rows="15"></textarea>
</div>
<script src="main.js"></script>
</body>
</html>
History
- 30th December, 2019 - Initial post