Amazon Cognito Authentication without using Amplify
When you want to add authentication service from AWS for your app, you have few ways to do that.
1. You can use AWS Amplify
2. You can use amazon-cognito-identity-js library
3. You can use Cognito AWS SDK
AWS Amplify is a solution which let’s you to integrate your app easily with many other AWS Services. But if you don’t need those other services and you only need to integrate Cognito authentication service with your app, then you have amazon-cognito-identity-js package at your service. One of the main advantages of choosing this package over AWS Amplify is, you will get a reduced bundle size which will positively affect the performance of your app.
To begin with, Let’s create our react app by running below command.
npx create-react-app react-aws
After that, let’s run below two commands to install webpack and amazon-cognito-identity-js packages
npm install --save-dev webpack-cli
npm install --save amazon-cognito-identity-js
Since we’ll be using forms inside our react app for authentication purposes, we’ll have to install formik (form library for react ) and yup (for validation).
npm install formik –save
npm install yup --save
Also for state management, styling and routing functionalities, we’ll need redux, sass and react-router-dom packages.
npm install @reduxjs/toolkit react-redux sass react-router-dom
Now that we have finished installing modules to our react app, let’s focus on setting up the configurations from AWS side.
Configuring Amazon Cognito
This article is written based on the assumption that you already have an AWS account. If you don’t have an account, please follow the instructions in this link.
Through out this step by step guide, if I have not mentioned about a particular configuration option, leave those fields as they are to finish this tutorial successfully. If you have any questions, you are always welcome to ask those in the comment section of this article.
- Log in to AWS Cognito Console and choose User Pools.
- Then click on ‘Create user pool’
- Under ‘Cognito user pool sign-in options’ section, select the options that allow your users to get signed in with. For example, If you choose all the options there (Username, email and phone number), you will have to get values for all those options when the user is getting signed up, and then your user can use one of them (Either Username or email or phone number) to log in to your app. For this tutorial, I’ll be using only ‘Email’ option. After selecting your options, Click ‘Next’ button.
- In Password Policy, You can either choose Cognito defaults or Custom mode. I’ll go with Cognito defaults
- Under Multi-factor authentication section, you can choose the recommended method (Require MFA) to enable multi-factor authentication. Since we are building a simple app here, I will go with “No MFA” option. MFA option will be covered in another article in near future.
- For User Account Recovery, I checked ‘Enable self-service account recovery — Recommended’ box and choose ‘Email Only’ as the delivery method for user account recovery messages. Then click on ‘Next’.
- Check ‘Allow Cognito to automatically send messages to verify and confirm — Recommended’ and select ‘Send email message, verify email address’ option
- Under Custom attributes section, add below attribute
=================
Name — telnum
Type — String
Mutable — checked
=================
Click ‘Next’ after adding that attribute. - As Email Provider, select ‘Send email with Cognito’ option and then click ‘Next’
- Enter a user pool name of your choice. I’ll go with ‘react-test’
- Under ‘Initial app client’, Select App type as ‘Public Client’ and enter an app client name. the name that I entered is ‘react-test_AppClient’
- Select ‘Don’t generate a client secret’ then click next. Review all the settings that you have updated. Below are my settings.
13. After checking everything, Click on ‘Create user pool’ button.
14. Sometimes, even if your user pool gets created successfully, the creation of your app client might have got failed (You may receive a red alert at the top of your cognito dashboard, if the app client creation has got failed). In such scenarios, you will have to create the app client again. Let’s see how you can do that. If you didn’t receive such alert, you can skip to step 19. Click on the name of the user pool that you created from the list of user pools.
15. Switch to ‘App Integration’ tab and scroll to the bottom until you find ‘App client list’ section
16. Click on ‘Create App Client’ button there.
17. Select App type as ‘Public Client’ and enter an app client name. the name that I entered is ‘react-testAppClient’
18. Select ‘Don’t generate a client secret’ then click ‘Create app client’.
19. (If you have skipped from step 14 to here, please click and open the user pool that you created from the list of user pools before starting this step.)
Let’s add a domain to our user pool. In ‘App Integration’ tab. Under Domain Section, Click on ‘Action’ button, and then select ‘Create Cognito domain’ from the list.
20. Enter a domain prefix of your choice. You cannot enter a phrase which includes either aws or amazon or cognito words. So I decide to put ‘react-test1’ there. If the prefix that you choose is acceptable, you will see a tick with the word ‘Available’. Once it is done, click on ‘Create cognito domain’ button.
Getting user pool credentials into react project
- Open the react-aws project that we created earlier from Visual Studio Code (Or any other software that you are familiar with)
- First of all, we need to create a .env file which we will store the credentials of our user pool. Create a file named .env inside react-aws project folder. (It should be located along with package.json file).
- Inside that file, write below two lines
REACT_APP_USERPOOL_ID=
REACT_APP_APPCLIENT_ID= - Now we need to find the values for above two variables. In order to do that, select and open the user pool that you created in AWS Cognito.
- Copy the value of User pool ID and paste it as the value for the variable REACT_APP_USERPOOL_ID in .env file.
6. After that, switch to ‘App Integration’ tab and scroll down to the bottom and find ‘App Client list’ section and get the Client ID from there and paste it as the value for REACT_APP_APPCLIENT_ID variable in .env file.
Changing the verification email type from code to link
- When a user gets signed up, by default he/she will recieve a code from cognito via email to verify his/her email address. Instead of this code, let’s update the verification email settings to send a link via email. So that the user will only have to click on that link to confirm his/her registration. In order to do that, we need to switch to ‘Messaging’ tab in your user pool settings, and scroll down to the bottom of that tab and find ‘Message templates’ section.
2. Click on ‘Verification Message’ in that table. After that, click on ‘Edit’ button in the popup box just opened.
3. Under Verification Type, Click on ‘Link’ radio button and save the changes.
It’s time for some coding
Now let’s get back to our react project
- Inside src folder, create two folders named styles and redux.
- Create the additional files and folders based on the screenshot below.
3. Update App.js file as in below code. Here we have removed the default code lines as they are not needed.
// App.js
import './App.css';
function App() {
return (
<></>
);
}
export default App;
4. Update index.js file as in below code. Here we have added routing and redux configurations
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Login from "./aws/Login";
import store from "./redux/store";
import { Provider } from "react-redux";
import WelcomeScreen from "./aws/WelcomeScreen";
import { SignUp } from "./aws/SignUp";
// routing configurations
const router = createBrowserRouter([
{
path: "/",
element: <Login />,
},
{
path: "/signup",
element: <SignUp />,
},
{
path: "/welcome",
element: <WelcomeScreen />,
},
]);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
</Provider>
);
reportWebVitals();
Signing up the users
Update SignUp.jsx file as in below code. This file will render the signup page of our app. We have created a CognitoUserPool object by using the cognito credentials obtained from .env file. process.env.REACT_APP_USERPOOL_ID and process.env.REACT_APP_APPCLIENT_ID )
- handleSubmit() function will get the form values from formik and creates an attribute list based on those values. Then signup function will get invoked on userPool object to complete the signup process.
- useFormik hook has the default values (initialValues) for the form. Yup is used to define the validationSchema for each form field.
- Once the user enters the correct details in signup form and submits it, an email will be sent to the user with the confirmation link. the user has to open that email and click on that link to complete the registration process.
// modules
import React, { useEffect } from "react";
import {
CognitoUserPool,
CognitoUserAttribute,
} from "amazon-cognito-identity-js";
import styled from "styled-components";
import { useFormik } from "formik";
import * as Yup from "yup";
import { useNavigate } from "react-router-dom";
// styling
import "./styles/SignUp.scss";
const userPool = new CognitoUserPool({
UserPoolId: process.env.REACT_APP_USERPOOL_ID,
ClientId: process.env.REACT_APP_APPCLIENT_ID,
});
export const SignUp = () => {
const navigate = useNavigate();
const handleSubmit = (values) => {
const email = values.email.trim();
const password = values.password.trim();
const attributeList = [
new CognitoUserAttribute({
Name: "email",
Value: email,
}),
new CognitoUserAttribute({
Name: "name",
Value: values.name,
}),
new CognitoUserAttribute({
Name: "custom:telnum",
Value: values.telnum,
}),
];
userPool.signUp(email, password, attributeList, null, (err, result) => {
if (err) {
console.log(err);
return;
}
console.log("call result: ", result);
navigate("/");
});
};
const formik = useFormik({
initialValues: {
name: "",
telnum: "",
email: "",
password: "",
},
validationSchema: Yup.object({
name: Yup.string()
.max(30, "Must be 15 characters or less")
.required("Required"),
telnum: Yup.string()
.max(10, "Must be 10 characters or less")
.required("Required"),
email: Yup.string().email("Invalid email address").required("Required"),
password: Yup.string()
.required("No password provided.")
.matches(/[0-9]/, "Password requires a number")
.matches(/[a-z]/, "Password requires a lowercase letter")
.matches(/[A-Z]/, "Password requires an uppercase letter")
.matches(/[^\w]/, "Password requires a symbol")
.min(8, "Password is too short. There should be 8 chars minimum."),
}),
onSubmit: (values) => {
handleSubmit(values);
},
});
return (
<>
<div className="signup__container">
<div className="signup__form" onSubmit={formik.handleSubmit}>
<h1>Sign Up</h1>
<div className="signup__fieldName" htmlFor="name">
Name
</div>
<input
className="signup__inputField"
name="name"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.name}
/>
{formik.touched.name && formik.errors.name ? (
<div className="signup__error">{formik.errors.name}</div>
) : null}
<div className="signup__fieldName" htmlFor="telnum">
Telephone Number
</div>
<input
className="signup__inputField"
name="telnum"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.telnum}
/>
{formik.touched.telnum && formik.errors.telnum ? (
<div className="signup__error">{formik.errors.telnum}</div>
) : null}
<div className="signup__fieldName" htmlFor="email">
Email Address
</div>
<input
className="signup__inputField"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div className="signup__error">{formik.errors.email}</div>
) : null}
<div className="signup__fieldName" htmlFor="password">
Password
</div>
<input
className="signup__inputField"
name="password"
type="password"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.password}
/>
{formik.touched.password && formik.errors.password ? (
<div className="signup__error">{formik.errors.password}</div>
) : null}
{/* <div className="signup__fieldName" htmlFor="confirmPassword">
Confirm Password
</div>
<input
className="signup__inputField"
name="confirmPassword"
type="password"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.confirmPassword}
/>
{formik.touched.confirmPassword && formik.errors.confirmPassword ? (
<div className="signup__error">{formik.errors.confirmPassword}</div>
) : null} */}
<button
className="signup__submitButton"
name="Hello"
onClick={() => handleSubmit(formik.values)}
>
Submit
</button>
</div>
</div>
</>
);
};
- In case if you are using an email which doesn’t exist (for testing purposes), you will not be able to receive the verification email to confirm the user account. In such scenarios, you can still confirm the user account using cognito dashboard. Just open the user pool that you created and switch to ‘Users’ tab. Find and click on the user that you want to confirm from the users list.
- Click on ‘Actions’ button in right side, and select ‘Confirm Account’ option from the menu that gets popped up. Now your user is ready to get logged into the app.
6. Update SignUp.scss file as in code below
.signup__container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
.signup__form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.signup__fieldName {
align-self: flex-start;
margin-top: 20pt;
}
.signup__inputField {
margin: 1rem 0 0.25rem;
width: 15rem;
}
.signup__error {
font-size: 12px;
color: red;
}
.signup__submitButton {
background: white;
margin: 1.5rem;
padding: 0.5rem 1rem;
color: #2196f3;
border: 1px solid #2196f3;
border-radius: 10px;
font-weight: 700;
&:hover {
background: #2196f3;
color: white;
}
}
User Signing in
7. Update Login.jsx file as in below code. This file will render the login page in the app.
- handleSubmit() function will create a cognitoUser Object and an authenticationDetails object. After that, authenticateUser function will get invoked on cognitoUser Object. Once the authentication gets succeeded, it will get the user data using getUserAttributes method and then it will dispatch an action to update that value in Redux store.
// modules
import React from "react";
import {
CognitoUserPool,
CognitoUser,
AuthenticationDetails,
} from "amazon-cognito-identity-js";
import { useFormik } from "formik";
import * as Yup from "yup";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { updateData } from "../redux/slices/authSlice";
// styling
import "./styles/Login.scss";
const userPool = new CognitoUserPool({
UserPoolId: process.env.REACT_APP_USERPOOL_ID,
ClientId: process.env.REACT_APP_APPCLIENT_ID,
});
const Login = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const handleSubmit = (values) => {
const cognitoUser = new CognitoUser({
Username: values.email,
Pool: userPool,
});
const authenticationDetails = new AuthenticationDetails({
Username: values.email,
Password: values.password,
});
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
cognitoUser.getUserAttributes(function (err, result) {
if (err) {
console.log("err", err);
return;
}
dispatch(
updateData({
name: result[2].Value,
email: values.email,
})
);
navigate("/welcome");
});
},
onFailure: (err) => {
console.log("login failed", err);
},
});
};
const formik = useFormik({
initialValues: {
email: "",
password: "",
},
validationSchema: Yup.object({
email: Yup.string().email("Invalid email address").required("Required"),
password: Yup.string().required("No password provided."),
}),
onSubmit: (values) => {
handleSubmit(values);
},
});
return (
<>
<div className="login__container">
<div className="login__form" onSubmit={formik.handleSubmit}>
<h1>Sign In</h1>
<div className="login__fieldName" htmlFor="email">
Email Address
</div>
<input
className="login__inputField"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div className="login__error">{formik.errors.email}</div>
) : null}
<div className="login__fieldName" htmlFor="password">
Password
</div>
<input
className="login__inputField"
name="password"
type="password"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.password}
/>
{formik.touched.password && formik.errors.password ? (
<div className="login__error">{formik.errors.password}</div>
) : null}
<button
className="login__submitButton"
onClick={() => handleSubmit(formik.values)}
>
{" "}
Log in{" "}
</button>
</div>
</div>
</>
);
};
export default Login;
8. Update Login.scss file as in below code.
.login__container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
.login__form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.login__fieldName {
align-self: flex-start;
margin-top: 20pt;
}
.login__inputField {
margin: 1rem 0 0.25rem;
width: 15rem;
}
.login__error {
font-size: 12px;
color: red;
}
.login__submitButton {
background: white;
margin: 1.5rem;
padding: 0.5rem 1rem;
color: #2196f3;
border: 1px solid #2196f3;
border-radius: 10px;
font-weight: 700;
&:hover {
background: #2196f3;
color: white;
}
}
9. Update WelcomeScreen.jsx file as in below code.
- Here the code will obtain the name of the user which was stored in redux store and display it in the welcome screen.
- User can log out from the app by clicking Logout button which will invoke signout() method on cognitoUser object.
// modules
import React, { useEffect } from "react";
import {
CognitoUserPool
} from "amazon-cognito-identity-js";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
// styling
import "./styles/Welcome.scss";
const userPool = new CognitoUserPool({
UserPoolId: process.env.REACT_APP_USERPOOL_ID,
ClientId: process.env.REACT_APP_APPCLIENT_ID,
});
const WelcomeScreen = () => {
const navigate = useNavigate();
const userData = useSelector((state) => state.auth.userData);
const signOut = () => {
const cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
cognitoUser.signOut();
}
navigate("/");
};
return (
<div className="Container">
<h1 className="Greeting">Hi {userData.name}!</h1>
<button
className="SignoutButton"
onClick={() => {
signOut();
}}
>
Logout
</button>
</div>
);
};
export default WelcomeScreen;
10. Update WelcomeScreen.scss file as in below code.
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
.Container {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: 'Roboto', sans-serif;
font-size: 2rem;
}
.Column {
display: flex;
flex-direction: column;
justify-content: center;
}
.Row {
display: flex;
flex-direction: row;
width: 100%;
}
.justifyRight {
justify-content: right;
}
.Greeting {
justify-self: center;
}
.SignoutButton {
padding: 10px 20px;
color: rgb(36, 114, 224);
font-weight: 700;
border-radius: 10px;
border-width: 1px;
border-color: rgb(36, 114, 224);
background: none;
margin: 20px;
&:hover {
background-color: rgb(36, 114, 224);
color: white;
}
}
11. Update authSlice.js file as in below code.
// authSlice.js
import { createSlice } from "@reduxjs/toolkit";
export const authSlice = createSlice({
name: "auth",
initialState: {
userData: { name: "", email: "" },
},
reducers: {
updateData: (state, action) => {
state.userData = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const { updateData } = authSlice.actions;
export default authSlice.reducer;
12. Update store.js file as in below code.
// store.js
import { configureStore } from '@reduxjs/toolkit'
import authSlice from './slices/authSlice'
export default configureStore({
reducer: {
auth: authSlice,
},
})
That’s all and now you are good to go with your simple app.
In addition to above scenarios, here are some other scenarios that take advantage of some more cognito functionalities. These codes are taken from amazon-cognito-identity-js repo.
For most of the below scenarios where cognitoUser object is used invoke a certain method, Below code snippet needs to be there to define the cognitoUser object.
var poolData = {
UserPoolId: '...', // Your user pool id here
ClientId: '...', // Your client id here
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: 'username',
Pool: userPool,
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
Scenario 1 - Confirming a registered, unauthenticated user using a confirmation code received via SMS or Email.
cognitoUser.confirmRegistration('123456', true, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('call result: ' + result);
});
Scenario 2 - Resending a confirmation code via SMS for confirming registration for a unauthenticated user.
cognitoUser.resendConfirmationCode(function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('call result: ' + result);
});
Scenario 3 - Resending a confirmation code via SMS for confirming registration for a unauthenticated user.
cognitoUser.resendConfirmationCode(function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('call result: ' + result);
});
Scenario 4 - Retrieve user attributes for an authenticated user.
cognitoUser.getUserAttributes(function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
for (i = 0; i < result.length; i++) {
console.log(
'attribute ' + result[i].getName() + ' has value ' + result[i].getValue()
);
}
});
Scenario 5 - Verify user attribute for an authenticated user.
Note that the inputVerificationCode method needs to be defined but does not need to actually do anything. If you would like the user to input the verification code on another page, you can set inputVerificationCode to null. If inputVerificationCode is null, onSuccess will be called immediately (assuming there is no error).
cognitoUser.getAttributeVerificationCode('email', {
onSuccess: function(result) {
console.log('call result: ' + result);
},
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
inputVerificationCode: function() {
var verificationCode = prompt('Please input verification code: ', '');
cognitoUser.verifyAttribute('email', verificationCode, this);
},
});
Scenario 6 - Delete user attribute for an authenticated user.
var attributeList = [];
attributeList.push('nickname');
cognitoUser.deleteAttributes(attributeList, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('call result: ' + result);
});
Scenario 7 - Update user attributes for an authenticated user.
var attributeList = [];
var attribute = {
Name: 'nickname',
Value: 'joe',
};
var attribute = new AmazonCognitoIdentity.CognitoUserAttribute(attribute);
attributeList.push(attribute);
cognitoUser.updateAttributes(attributeList, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('call result: ' + result);
});
Scenario 8 - Changing the current password for an authenticated user.
cognitoUser.changePassword('oldPassword', 'newPassword', function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('call result: ' + result);
});
Scenario 9 - Starting and completing a forgot password flow for an unauthenticated user.
For example:
<body>
<label for="#code">Code: </label>
<input id="code"></input>
</br>
<label for="#new_password">New Password: </label>
<input id="new_password" type="password"></input>
<br/>
</body>
cognitoUser.forgotPassword({
onSuccess: function(data) {
// successfully initiated reset password request
console.log('CodeDeliveryData from forgotPassword: ' + data);
},
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
//Optional automatic callback
inputVerificationCode: function(data) {
console.log('Code sent to: ' + data);
var verificationCode = document.getElementById('code').value;
var newPassword = document.getElementById('new_password').value;
cognitoUser.confirmPassword(verificationCode, newPassword, {
onSuccess() {
console.log('Password confirmed!');
},
onFailure(err) {
console.log('Password not confirmed!');
},
});
},
});
Scenario 10 - Deleting an authenticated user.
cognitoUser.deleteUser(function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('call result: ' + result);
});
Scenario 11 - Signing out from the application.
cognitoUser.signOut();
Scenario 12 - Global signout for an authenticated user(invalidates all issued tokens).
cognitoUser.globalSignOut(callback);
Scenario 13 - Retrieving the current user from local storage.
var poolData = {
UserPoolId: '...', // Your user pool id here
ClientId: '...', // Your client id here
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
cognitoUser.getSession(function(err, session) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('session validity: ' + session.isValid());
// NOTE: getSession must be called to authenticate user before calling getUserAttributes
cognitoUser.getUserAttributes(function(err, attributes) {
if (err) {
// Handle error
} else {
// Do something with attributes
}
});
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: '...', // your identity pool id here
Logins: {
// Change the key below according to the specific region your user pool is in.
'cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>': session
.getIdToken()
.getJwtToken(),
},
});
// Instantiate aws sdk service objects now that the credentials have been updated.
// example: var s3 = new AWS.S3();
});
}
Scenario 14 - Integrating User Pools with Cognito Identity.
var cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
cognitoUser.getSession(function(err, result) {
if (result) {
console.log('You are now logged in.');
//POTENTIAL: Region needs to be set if not already set previously elsewhere.
AWS.config.region = '<region>';
// Add the User's Id Token to the Cognito credentials login map.
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'YOUR_IDENTITY_POOL_ID',
Logins: {
'cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>': result
.getIdToken()
.getJwtToken(),
},
});
}
});
}
//call refresh method in order to authenticate user and get new temp credentials
AWS.config.credentials.refresh(error => {
if (error) {
console.error(error);
} else {
console.log('Successfully logged!');
}
});
Scenario 15 - List all remembered devices for an authenticated user. In this case, we need to pass a limit on the number of devices retrieved at a time and a pagination token is returned to make subsequent calls. The pagination token can be subsequently passed. When making the first call, the pagination token should be null.
cognitoUser.listDevices(limit, paginationToken, {
onSuccess: function(result) {
console.log('call result: ' + result);
},
onFailure: function(err) {
alert(err.message);
},
});
Scenario 16 - List information about the current device.
cognitoUser.getDevice({
onSuccess: function(result) {
console.log('call result: ' + result);
},
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
Scenario 17 - Remember a device.
cognitoUser.setDeviceStatusRemembered({
onSuccess: function(result) {
console.log('call result: ' + result);
},
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
Scenario 18 - Do not remember a device.
cognitoUser.setDeviceStatusNotRemembered({
onSuccess: function(result) {
console.log('call result: ' + result);
},
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
Scenario 19 - Forget the current device.
cognitoUser.forgetDevice({
onSuccess: function(result) {
console.log('call result: ' + result);
},
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
Scenario 20 - Authenticate a user and set new password for a user that was created using AdminCreateUser API.
var cognitoUser, sessionUserAttributes; // global variables to handle completeNewPasswordChallenge flow
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
// User authentication was successful
},
onFailure: function(err) {
// User authentication was not successful
},
mfaRequired: function(codeDeliveryDetails) {
// MFA is required to complete user authentication.
// Get the code from user and call
cognitoUser.sendMFACode(mfaCode, this)
},
newPasswordRequired: function(userAttributes, requiredAttributes) {
// User was signed up by an admin and must provide new
// password and required attributes, if any, to complete
// authentication.
// the api doesn't accept this field back
delete userAttributes.email_verified;
// store userAttributes on global variable
sessionUserAttributes = userAttributes;
}
});
// ... handle new password flow on your app
handleNewPassword(newPassword) {
cognitoUser.completeNewPasswordChallenge(newPassword, sessionUserAttributes);
}
Scenario 21 - Retrieve the MFA settings for the user.
cognitoUser.getUserData((err, data) => {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
const { PreferredMfaSetting, UserMFASettingList } = data;
console.log(
JSON.stringify({ PreferredMfaSetting, UserMFASettingList }, null, 2)
);
});
E.g.
{
"PreferredMfaSetting": "SMS_MFA",
"UserMFASettingList": ["SMS_MFA"]
}
Scenario 22 - Authenticating a user with a passwordless custom flow.
cognitoUser.setAuthenticationFlowType('CUSTOM_AUTH');
cognitoUser.initiateAuth(authenticationDetails, {
onSuccess: function(result) {
// User authentication was successful
},
onFailure: function(err) {
// User authentication was not successful
},
customChallenge: function(challengeParameters) {
// User authentication depends on challenge response
var challengeResponses = 'challenge-answer';
cognitoUser.sendCustomChallengeAnswer(challengeResponses, this);
},
});
Scenario 23 - Using cookies to store cognito tokens
To use the CookieStorage you have to pass it in the constructor map of CognitoUserPool and CognitoUser (when constructed directly):
var poolData = {
UserPoolId : '...', // Your user pool id here
ClientId : '...' // Your client id here
Storage: new AmazonCognitoIdentity.CookieStorage({domain: ".yourdomain.com"})
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: 'username',
Pool: userPool,
Storage: new AmazonCognitoIdentity.CookieStorage({domain: ".yourdomain.com"})
};
The CookieStorage object receives a map (data) in its constructor that may have these values:
- data.domain Cookies domain (mandatory)
- data.path Cookies path (default: ‘/’)
- data.expires Cookie expiration (in days, default: 365)
- data.secure Cookie secure flag (default: true)
- data.sameSite Cookie request behaviour (default: null)
Scenario 24 - Selecting the MFA method and authenticating using TOTP.
var authenticationData = {
Username: 'username',
Password: 'password',
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
authenticationData
);
var poolData = {
UserPoolId: '...', // Your user pool id here
ClientId: '...', // Your client id here
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: 'username',
Pool: userPool,
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function(result) {
var accessToken = result.getAccessToken().getJwtToken();
},
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
mfaSetup: function(challengeName, challengeParameters) {
cognitoUser.associateSoftwareToken(this);
},
associateSecretCode: function(secretCode) {
var challengeAnswer = prompt('Please input the TOTP code.', '');
cognitoUser.verifySoftwareToken(challengeAnswer, 'My TOTP device', this);
},
selectMFAType: function(challengeName, challengeParameters) {
var mfaType = prompt('Please select the MFA method.', ''); // valid values for mfaType is "SMS_MFA", "SOFTWARE_TOKEN_MFA"
cognitoUser.sendMFASelectionAnswer(mfaType, this);
},
totpRequired: function(secretCode) {
var challengeAnswer = prompt('Please input the TOTP code.', '');
cognitoUser.sendMFACode(challengeAnswer, this, 'SOFTWARE_TOKEN_MFA');
},
mfaRequired: function(codeDeliveryDetails) {
var verificationCode = prompt('Please input verification code', '');
cognitoUser.sendMFACode(verificationCode, this);
},
});
Scenario 25 - Enabling and setting SMS MFA as the preferred MFA method for the user.
var smsMfaSettings = {
PreferredMfa: true,
Enabled: true,
};
cognitoUser.setUserMfaPreference(smsMfaSettings, null, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
}
console.log('call result ' + result);
});
Scenario 26 - Enabling and setting TOTP MFA as the preferred MFA method for the user.
var totpMfaSettings = {
PreferredMfa: true,
Enabled: true,
};
cognitoUser.setUserMfaPreference(null, totpMfaSettings, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
}
console.log('call result ' + result);
});
Scenario 27 - Authenticating a user with a user password auth flow.
cognitoUser.setAuthenticationFlowType('USER_PASSWORD_AUTH');
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function(result) {
// User authentication was successful
},
onFailure: function(err) {
// User authentication was not successful
},
mfaRequired: function(codeDeliveryDetails) {
// MFA is required to complete user authentication.
// Get the code from user and call
cognitoUser.sendMFACode(verificationCode, this);
},
});
Scenario 28 - Retrieve the user data for an authenticated user.
cognitoUser.getUserData(function(err, userData) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('User data for user ' + userData);
});
// If you want to force to get the user data from backend,
// you can set the bypassCache to true
cognitoUser.getUserData(
function(err, userData) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('User data for user ' + userData);
},
{ bypassCache: true }
);
Scenario 29 - Handling expiration of the Id Token.
var refresh_token = session.getRefreshToken(); // receive session from calling cognitoUser.getSession()
if (AWS.config.credentials.needsRefresh()) {
cognitoUser.refreshSession(refresh_token, (err, session) => {
if (err) {
console.log(err);
} else {
AWS.config.credentials.params.Logins[
'cognito-idp.<YOUR-REGION>.amazonaws.com/<YOUR_USER_POOL_ID>'
] = session.getIdToken().getJwtToken();
AWS.config.credentials.refresh(err => {
if (err) {
console.log(err);
} else {
console.log('TOKEN SUCCESSFULLY UPDATED');
}
});
}
});
}
References
- amazon-cognito-identity-js repo - https://github.com/aws-amplify/amplify-js/tree/main/packages/amazon-cognito-identity-js
- Amazon Cognito Developer Guide - https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html
- Amazon Cognito API Reference - https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_Operations.html
- Github repo of this tutorial - https://github.com/sankharr/react-aws