Quick AWS Cognito
User Pools vs Identity pools
- User Pools : Typical login and signup
- Identity Pools : Give user access to AWS resource like s3
1 User Pools
Cognito User Pool is mandatory default which is just normal email and password.
Federated Identity Pool can be added which can be FB,Google,Apple or SAML or Open ID Connect providers.
2 Hosted Web UI
AWS has their own website that can do the sign-up/sign-in for you.
It redirects to your website URL with the session and other info if sign-in is successful.
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html
3 Example
3.1 Vanilla JS
Below is an example of an extremely simple sign up and log in system with cognito.
Only pulling libraries from a . (Note: specifically for vanilla JS, when generating a
ClientId
must leave “generate client secret box” unchecked )
- Keypoint:
UserPoolId
can be shown publically, it is the id of the cognito pool- Only One UserPoolId
ClientId
can be shown publically, it is like CORS- Multiple ClientId for multiple client apps
<!doctype html>
<html>
<head>
<script type="module" src="https://cdn.jsdelivr.net/npm/amazon-cognito-identity-js@6.1.2/dist/amazon-cognito-identity.min.js"></script>
<!-- optional: only if you use other AWS services -->
<script type="module" src="https://cdnjs.cloudflare.com/ajax/libs/aws-sdk/2.1295.0/aws-sdk.min.js" integrity="sha512-+KWDfuIpaulta79x9a6rt12FiNfCeZfq9lthRy0uKxtKp7juXgjurStedKQT5kwg2ipU+pDD+o2bABXYs6IvEw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<title>Our Funky HTML Page</title>
<meta name="description" content="Our first page">
<meta name="keywords" content="html tutorial template">
</head>
<body>
When creating an App Client in cognito, the generate client secret box must be unchecked because the JavaScript SDK doesn't support apps that have a client secret.
Else you will get a error "Client 4p0o86nuq106dgkmpl11aej8vi is configured for secret but secret was not received"</body>
<button id="signup">sign up </button>
<button id="signin">sign in </button>
<script defer>
document.getElementById("signup").addEventListener("click",(e) =>{
var poolData = {
UserPoolId: 'us-east-1_zknguzw3R', // Your user pool id here
ClientId: '62js8mr29t9kuf432lih20ffld', // Your client id here
;
}var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var attributeList = [];
var dataEmail = {
Name: 'email',
Value: 'email@mydomain.com',
;
}
var dataPhoneNumber = {
Name: 'phone_number',
Value: '+15555555555',
;
}var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
// var attributePhoneNumber = new AmazonCognitoIdentity.CognitoUserAttribute(
// dataPhoneNumber
// );
.push(attributeEmail);
attributeList//attributeList.push(attributePhoneNumber);
.signUp('username1', 'usernamE8&', attributeList, null, function(
userPool,
err
result
) {if (err) {
alert(err.message || JSON.stringify(err));
return;
}var cognitoUser = result.user;
console.log('user name is ' + cognitoUser.getUsername());
;
})
})
document.getElementById("signin").addEventListener("click",(e) =>{
var authenticationData = {
Username: 'username1',
Password: 'usernamE8&',
;
}var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
authenticationData;
)var poolData = {
UserPoolId: 'us-east-1_zknguzw3R', // Your user pool id here
ClientId: '62js8mr29t9kuf432lih20ffld', // Your client id here
;
}var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: 'username1',
Pool: userPool,
;
}var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
.authenticateUser(authenticationDetails, {
cognitoUseronSuccess: function(result) {
var accessToken = result.getAccessToken().getJwtToken();
//POTENTIAL: Region needs to be set if not already set previously elsewhere.
.config.region = 'us-east-1';
AWS
.config.credentials = new AWS.CognitoIdentityCredentials({
AWSIdentityPoolId: '...', // 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>': result
.getIdToken()
.getJwtToken(),
,
};
})
//refreshes credentials using AWS.CognitoIdentity.getCredentialsForIdentity()
.config.credentials.refresh(error => {
AWSif (error) {
console.error(error);
else {
} // Instantiate aws sdk service objects now that the credentials have been updated.
// example: var s3 = new AWS.S3();
console.log('Successfully logged!');
};
}),
}
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
,
};
})
})</script>
</html>
3.2 Example Cognito response
{
"username": "tiwadox692@wiroute.com",
"pool": {
"userPoolId": "us-east-1_jXlvakUOh",
"clientId": "6u94pcpradnp1nlavunbb1qkh1",
"client": {
"endpoint": "https://cognito-idp.us-east-1.amazonaws.com/",
"fetchOptions": {}
},
"advancedSecurityDataCollectionFlag": true,
"storage": {
"amplify-auto-sign-in": "true"
}
},
"Session": null,
"client": {
"endpoint": "https://cognito-idp.us-east-1.amazonaws.com/",
"fetchOptions": {}
},
"signInUserSession": null,
"authenticationFlowType": "USER_SRP_AUTH",
"storage": {
"amplify-auto-sign-in": "true"
},
"keyPrefix": "CognitoIdentityServiceProvider.6u94pcpradnp1nlavunbb1qkh1",
"userDataKey": "CognitoIdentityServiceProvider.6u94pcpradnp1nlavunbb1qkh1.tiwadox692@wiroute.com.userData"
}
3.3 Standalone index.html
<!doctype html>
<html>
<head>
<script type="module" src="https://cdn.jsdelivr.net/npm/amazon-cognito-identity-js@6.1.2/dist/amazon-cognito-identity.min.js"></script>
<!-- optional: only if you use other AWS services -->
<script type="module" src="https://cdnjs.cloudflare.com/ajax/libs/aws-sdk/2.1295.0/aws-sdk.min.js" integrity="sha512-+KWDfuIpaulta79x9a6rt12FiNfCeZfq9lthRy0uKxtKp7juXgjurStedKQT5kwg2ipU+pDD+o2bABXYs6IvEw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<title>Our Funky HTML Page</title>
<meta name="description" content="Our first page">
<meta name="keywords" content="html tutorial template">
</head>
<body>
in cognito, the generate client secret box must be unchecked because the JavaScript SDK doesn't support apps that have a client secret.
When creating an App Client
"Client 4p0o86nuq106dgkmpl11aej8vi is configured for secret but secret was not received"
Else you will get a error </body>
<button id="signup">sign up </button>
<button id="signin">sign in </button>
<button id="confirm">confirm </button>
<button id="resendconfirm">resendconfirm </button>
<div>
username<input type="text" id="username" value="usernameX">
</div>
<div>
password<input type="text" id="password" value="passworD0#">
</div>
<div>
email<input type="text" id="email" value="us@us.com">
</div>
<div>
userpoolid<input type="text" id="userpoolid" value="us-east-1_jWTvr9JZR">
</div>
<div>
clientid<input type="text" id="clientid" value="42khb8knvvp2nuof63t1vepokk">
</div>
<div>
confirmcode<input type="text" id="confirmcode" value="XXXXX">
</div>
<script defer>
document.getElementById("signup").addEventListener("click",(e) =>{
const myuserpoolid = document.getElementById("userpoolid").value
const myclientid = document.getElementById("clientid").value
const myusername = document.getElementById("username").value
const mypassword = document.getElementById("password").value
const myemail = document.getElementById("email").value
var poolData = {
UserPoolId: myuserpoolid, // Your user pool id here
ClientId: myclientid, // Your client id here
;
}var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var attributeList = [];
var dataEmail = {
Name: 'email',
Value: myemail,
;
}
var dataPhoneNumber = {
Name: 'phone_number',
Value: '+15555555555',
;
}var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
// var attributePhoneNumber = new AmazonCognitoIdentity.CognitoUserAttribute(
// dataPhoneNumber
// );
.push(attributeEmail);
attributeList//attributeList.push(attributePhoneNumber);
.signUp(myusername, mypassword, attributeList, null, function(
userPool,
err
result
) {if (err) {
alert(err.message || JSON.stringify(err));
return;
}var cognitoUser = result.user;
console.log('user name is ' + cognitoUser.getUsername());
;
})
})
document.getElementById("signin").addEventListener("click",(e) =>{
const myusername = document.getElementById("username").value
const mypassword = document.getElementById("password").value
const myuserpoolid = document.getElementById("userpoolid").value
const myclientid = document.getElementById("clientid").value
var authenticationData = {
Username: myusername,
Password: mypassword,
;
}var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
authenticationData;
)var poolData = {
UserPoolId: myuserpoolid, // Your user pool id here
ClientId: myclientid, // Your client id here
;
}var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: myusername,
Pool: userPool,
;
}console.log(userData);
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
.authenticateUser(authenticationDetails, {
cognitoUseronSuccess: function(result) {
var accessToken = result.getAccessToken().getJwtToken();
//POTENTIAL: Region needs to be set if not already set previously elsewhere.
.config.region = 'us-east-1';
AWS
.config.credentials = new
AWS//You get below data from Cognito >> Federated Identities >> Identity Pool
//if you get an error 'index.html:149 NotAuthorizedException: Token is not from a supported provider of this identity pool.' when signing in, it means your identity pool isnt using the correct clientid
.CognitoIdentityCredentials({
AWSIdentityPoolId: 'us-east-1:1e84880a-c121-4ae8-b9a0-329fc521616f', // your identity pool id here
Logins: {
// Change the key below according to the specific region your user pool is in.
'cognito-idp.us-east-1.amazonaws.com/us-east-1_jWTvr9JZR': result
.getIdToken()
.getJwtToken(),
,
};
})
//refreshes credentials using AWS.CognitoIdentity.getCredentialsForIdentity()
.config.credentials.refresh(error => {
AWSif (error) {
console.error(error);
else {
} // Instantiate aws sdk service objects now that the credentials have been updated.
// example: var s3 = new AWS.S3();
console.log('Successfully logged!');
};
}),
}
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
,
};
})
})
document.getElementById("confirm").addEventListener("click",async (e) =>{
const myusername = document.getElementById("username").value
const mypassword = document.getElementById("password").value
const myuserpoolid = document.getElementById("userpoolid").value
const myclientid = document.getElementById("clientid").value
const confirmcode = document.getElementById("confirmcode").value
var poolData = {
UserPoolId: myuserpoolid, // Your user pool id here
ClientId: myclientid, // Your client id here
;
}var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: myusername,
Pool: userPool,
;
}console.log(userData);
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
.confirmRegistration(confirmcode,true,(err,result)=>{
cognitoUserif(err){
console.log('error',err.message);
return
}console.log('confirm',result)
})
})
document.getElementById("resendconfirm").addEventListener("click",async (e) =>{
const myusername = document.getElementById("username").value
const mypassword = document.getElementById("password").value
const myuserpoolid = document.getElementById("userpoolid").value
const myclientid = document.getElementById("clientid").value
const confirmcode = document.getElementById("confirmcode").value
var poolData = {
UserPoolId: myuserpoolid, // Your user pool id here
ClientId: myclientid, // Your client id here
;
}var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: myusername,
Pool: userPool,
;
}console.log(userData);
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
.resendConfirmationCode((err,result)=>{
cognitoUserif(err){
console.log(err)
return;
}console.log(result)
})
})</script>
</html>
4 React
The most trivial implementation of a aws-amplify Auth signup in React
import logo from './logo.svg';
import './App.css';
// import {useMachine , useInterpret, useSelector} from '@xstate/react';
// import {sendParent, send, interpret, assign, spawn,createMachine} from 'xstate';
// import { useEffect } from 'react';
import { Auth } from 'aws-amplify';
function App() {
var poolData = {
"userPoolId": 'us-east-1_zknguzw3R', // Your user pool id here
"userPoolWebClientId": '62js8mr29t9kuf432lih20ffld', // Your client id here
;
}
console.log(Auth.configure(poolData))
var signUpPayload = {
"username" : "weqdweqdf",
"password" : "loioihoihiS@#1",
"attributes" : {
'email': "yifamot793@onlcool.com"
}
}
const signInAWS = async () => {
const outcome = await Auth.signIn("weqdweqdf","loioihoihiS@#1")
console.log(outcome)
}
const signUpAWS = async () => {
const outcome = await Auth.signUp(signUpPayload)
console.log(outcome)
}
const signUpConfirmAWS = async (verifyCode) => {
const outcome = await Auth.confirmSignUp("weqdweqdf",verifyCode)
console.log(outcome)
}
return (
<div className="App">
, verify email, signin, signout Flow. Inspect element, look at console log.
Simplest Auth signup<div>
<button onClick={() => {signUpAWS()}} > hi sign up</button>
<button onClick={() => {signUpConfirmAWS("870719")}}> verify email </button>
<button onClick={() => {signInAWS()}}>sign in </button>
<button onClick={() => {Auth.currentUserInfo().then((v)=>console.log(v))}}>Auth.currentUserInfo()</button>
<button onClick={() => {Auth.signOut()}}>Auth.signout</button>
</div>
</div>
;
)
}
export default App;
4.1 A more complex React example
try it out at https://lambdajasonyang.github.io/awsauthcognitoreact/
import logo from './logo.svg';
import './App.css';
// import {useMachine , useInterpret, useSelector} from '@xstate/react';
// import {sendParent, send, interpret, assign, spawn,createMachine} from 'xstate';
// import { useEffect } from 'react';
import { Auth } from 'aws-amplify';
import {useState} from 'react';
function App() {
const [getuserPoolId,setuserPoolId] = useState("us-east-1_zknguzw3R");
const [getuserPoolWebClientId,setuserPoolWebClientId] = useState("62js8mr29t9kuf432lih20ffld");
const [getpoolDataResult,setpoolDataResult] = useState(null)
var poolData = {
"userPoolId": getuserPoolId, // Your user pool id here
"userPoolWebClientId": getuserPoolWebClientId, // Your client id here
;
}
const [getusername,setusername] = useState("weqdweqdf")
const [getpassword,setpassword] = useState("loioihoihiS@#1")
const [getemail,setemail] = useState("yifamot793@onlcool.com")
var signUpPayload = {
"username" : getusername,
"password" : getpassword,
"attributes" : {
'email': getemail
}
}
const [getsignInResult,setsignInResult] = useState(null)
const signInAWS = async () => {
try{
const outcome = await Auth.signIn(getusername,getpassword)
setsignInResult(outcome)
catch(e){
}setsignInResult(e)
}
}
const [getsignUpResult,setsignUpResult] = useState(null)
const signUpAWS = async () => {
try{
const outcome = await Auth.signUp(signUpPayload)
setsignUpResult(outcome)
catch(e){
}setsignUpResult(e)
}
}
const [getverifyCode,setverifyCode] = useState("870719")
const signUpConfirmAWS = async () => {
const outcome = await Auth.confirmSignUp(getusername,getverifyCode)
console.log(outcome)
}
const [getcurrentUserInfo,setcurrentUserInfo] = useState()
return (
<div className="App">
<h1>Simplest Auth signup, verify email, signin, signout Flow. </h1>
<button onClick={()=>{setpoolDataResult(Auth.configure(poolData))}}>Setup userPoolId and userPoolClientId</button>
<div>Setup Pooldata Results: {JSON.stringify(getpoolDataResult)}</div>
<div>
<textarea name="setPoolid" onChange={(e) => {setuserPoolId(e.target.value)}} value={getuserPoolId}/>
<textarea name="setPoolClient" onChange={(e) => {setuserPoolWebClientId(e.target.value)}} value={getuserPoolWebClientId}/>
</div>
<div>
<div>
<input type="text" onChange={(e) => {setusername(e.target.value)}} value={getusername}/>
<input type="text" onChange={(e) => {setpassword(e.target.value)}} value={getpassword}/>
<input type="text" onChange={(e) => {setemail(e.target.value)}} value={getemail}/>
</div>
<button onClick={() => {signUpAWS()}} > hi sign up</button>
<button onClick={() => {signUpConfirmAWS()}}> verify email </button>
<button onClick={() => {signInAWS()}}>sign in </button>
<button onClick={() => {Auth.currentUserInfo().then((v)=>setcurrentUserInfo(v))}}>Auth.currentUserInfo()</button>
<button onClick={() => {Auth.signOut()}}>Auth.signout</button>
</div>
<div>
<input type="text" onChange={(e) => {setverifyCode(e.target.value)}} value={getverifyCode}/>
</div>
<fieldset>
Results:
Sign In <textarea value={JSON.stringify(getsignInResult,null,"\t")}/>
</fieldset>
<fieldset>
Results:
Sign Up <textarea value={JSON.stringify(getsignUpResult,null,"\t")}/>
</fieldset>
<fieldset>
.currentUserInfo():
Auth<textarea value={JSON.stringify(getcurrentUserInfo,null,"\t")}/>
</fieldset>
</div>
;
)
}
export default App;
5 AWS-sdk on backend
AWS amplify doesnt work on nodejs backend since its clientside
We will use the aws-sdk v3
import {
,
ListUsersCommand,
CognitoIdentityProviderClientfrom "@aws-sdk/client-cognito-identity-provider";
}
const DEFAULT_REGION = "us-east-1";
var client = new CognitoIdentityProviderClient({
region: DEFAULT_REGION,
credentials: {
accessKeyId: "AAAAAAAAAAA",
secretAccessKey: "BBBBBBBBBBBBBBBBBBBBBBBBBBBB"
}
})const listUsers = async () => {
const command = new ListUsersCommand({
UserPoolId: "us-east-1_wrA4I0ZmE",
;
})
return client.send(command);
;
}
const runner = async () => {
const b = await listUsers();
console.log(b)
}runner()
#output
{
'$metadata': {
httpStatusCode: 200,
requestId: '6bb63b2b-b640-44d6-b420-f6905fffd2e6',
extendedRequestId: undefined,
cfId: undefined,
attempts: 1,
totalRetryDelay: 0
},
Users: [
{
Attributes: [Array],
Enabled: true,
UserCreateDate: 2023-06-16T02:04:54.046Z,
UserLastModifiedDate: 2023-06-16T02:05:05.123Z,
UserStatus: 'CONFIRMED',
Username: 'weqdweqdf'
}
]
}
import {
,
CognitoIdentityProviderClient,
GetUserCommandfrom "@aws-sdk/client-cognito-identity-provider";
}
const DEFAULT_REGION = "us-east-1";
var client = new CognitoIdentityProviderClient({
region: DEFAULT_REGION,
credentials: { } //getting user id from access token doesnt require account specific data/login
})const getUser = async () => {
const command = new GetUserCommand({
AccessToken: "XXXXXXXXXXX",
;
})
return client.send(command);
;
}
const runner = async () => {
const b = await getUser();
console.log(b)
}runner()
- access token is a jwt
- when user signs in, the accesstoken is given in response
- access token expires after certain time period, you have to sign in again to get new access token
- access token is made by cognito with a secret key(we cannot access this only AWS can access this)
- this means that cognito signs the user id and turns it into a jwt access token
- this means an attacker cannot forge your jwt access token
- access token can be consumed (read) and translated to id by anyone
- ANYONE CAN READ THE CONTENTS OF THE JWT LIKE USER ID
- typically not dangerous
- typically backends with consume the access token and use the id to modify specific profile
- ANYONE CAN READ THE CONTENTS OF THE JWT LIKE USER ID
5.0.1 JWT
JWTs can be either signed, encrypted or both. * If a token is signed, but not encrypted, everyone can read its contents, but when you don’t know the private key, you can’t change it. * If a token is signed, and attacker attempt to tamper with its contents, the receiver will notice that the signature won’t match anymore.
An attacker can steal your session if they steal the JWT BUT the attacker cannot generate a JWT that spoofs them as another user.
- IMPORTANT: BACKEND SHOULD NEVER READ PLAINTEXT USERID, IT MUST READ THE JWT
- CounterExample: Clientside reads the jwt access token and decodes it to userid then sends it to backend.
- This implies the backend allows any plaintext userid which means an attacker can forge any userid and the jwt is just useless