Quick AWS Cognito

Posted on October 2, 2021
Tags: aws

User Pools vs Identity pools

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 cdn. (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
//    );

    attributeList.push(attributeEmail);
    //attributeList.push(attributePhoneNumber);

    userPool.signUp('username1', 'usernamE8&', attributeList, null, function(
        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);
cognitoUser.authenticateUser(authenticationDetails, {
	onSuccess: function(result) {
		var accessToken = result.getAccessToken().getJwtToken();

		//POTENTIAL: Region needs to be set if not already set previously elsewhere.
		AWS.config.region = 'us-east-1';

		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>': result
					.getIdToken()
					.getJwtToken(),
			},
		});

		//refreshes credentials using AWS.CognitoIdentity.getCredentialsForIdentity()
		AWS.config.credentials.refresh(error => {
			if (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>
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>
    <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
//    );

    attributeList.push(attributeEmail);
    //attributeList.push(attributePhoneNumber);

    userPool.signUp(myusername, mypassword, attributeList, null, function(
        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);
    cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: function(result) {
            var accessToken = result.getAccessToken().getJwtToken();

            //POTENTIAL: Region needs to be set if not already set previously elsewhere.
            AWS.config.region = 'us-east-1';

            AWS.config.credentials = new 
            //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
            AWS.CognitoIdentityCredentials({
                IdentityPoolId: '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()
            AWS.config.credentials.refresh(error => {
                if (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);
       cognitoUser.confirmRegistration(confirmcode,true,(err,result)=>{
       if(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);
        cognitoUser.resendConfirmationCode((err,result)=>{
            if(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">
      Simplest Auth signup, verify email, signin, signout Flow. Inspect element, look at console log.
      <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>
        Sign In Results: 
        <textarea value={JSON.stringify(getsignInResult,null,"\t")}/>
      </fieldset>
      <fieldset>
        Sign Up Results:
        <textarea value={JSON.stringify(getsignUpResult,null,"\t")}/>
      </fieldset>
      <fieldset>
        Auth.currentUserInfo(): 
        <textarea value={JSON.stringify(getcurrentUserInfo,null,"\t")}/>
      </fieldset>
    </div>
  );
}

export default App;
sequenceDiagram participant Frontend as Frontend Application participant Cognito as AWS Cognito participant Backend as Backend Server participant Database as SQL Database Frontend->>Cognito: Sign In Request Cognito->>Frontend: Verify Credentials alt Invalid Credentials Cognito->>Frontend: Return Error Response else Valid Credentials Cognito->>Frontend: Generate JWT Frontend->>Backend: Include JWT in Request Header Backend->>+Database: Process Request with JWT Database->>-Backend: Fetch User-Specific Data Backend->>Frontend: Return Data Response end Frontend->>Backend: Modify Account Request (with JWT) Backend->>+Database: Validate User ID in JWT Database->>-Backend: User ID Verification Result alt Invalid User ID Backend->>Frontend: Return Error Response else Valid User ID Backend->>+Database: Modify User Account Data Database->>-Backend: Account Modification Result Backend->>Frontend: Return Success Response end

5 AWS-sdk on backend

AWS amplify doesnt work on nodejs backend since its clientside
We will use the aws-sdk v3

import {
    ListUsersCommand,
    CognitoIdentityProviderClient,
  } from "@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,
    GetUserCommand,
  } from "@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()

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