2024 07 08 cognito refresh token duration issue resolved for react app using cdk - bizkite-co/handterm GitHub Wiki


original file name: Codeium Chat - cgmR9Z4pMF3etKqWEa09W0PntJgS1xYI.md summary: The user is seeking assistance regarding Cognito login integration for their React application using CDK. They are facing issues with the refresh token timing out quickly and inquire about extending its duration. They also ask if Cognito saves user state. The assistant explains how to adjust the refresh token expiration in the Amazon Cognito User Pool settings via the AWS Management Console and responds to further queries about making changes via AWS CLI or CloudFormation. The assistant provides detailed steps for updating the refresh token validity using the AWS CLI and CloudFormation templates. The conversation concludes with clarifications on adjusting the refresh token validity via the CDK and querying the current refresh token validity using the AWS CLI.

date: 2024-07-08T09:53

Codeium Chat Conversation

Note: This is purely the output of the chat conversation and does not contain any raw data, codebase snippets, etc. used to generate the output.

User at 9:53 am, Jul 8, 2024

I profide Cognito login for my React xterm.js web-based TUI with this CDK stack:

// cdk/lib/cdk-stack.ts
import { ENDPOINTS } from '../lambda/cdkshared/endpoints';
import {
 aws_cognito as cognito,
 aws_s3 as s3,
 aws_lambda as lambda,
 aws_iam as iam,
 App,
 CfnOutput,
 Stack,
 StackProps
} from "aws-cdk-lib";
import { Construct } from 'constructs';
import { HttpMethod, HttpApi, CorsHttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; // This path is illustrative and likely incorrect
import { HttpLambdaAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
import { IFunction } from 'aws-cdk-lib/aws-lambda';

const nodeRuntime = lambda.Runtime.NODEJS_16_X;

export class HandTermCdkStack extends Stack {
 constructor(
  scope: Construct,
  id: string,
  props?: StackProps
 ) {
  super(scope, id, props);
  const allowHeaders = [
   'Content-Type',
   'X-Amz-Date',
   'Authorization',
   'X-Api-Key',
   'X-Requested-With',
   'sec-ch-ua',
   'sec-ch-ua-mobile',
   'sec-ch-ua-platform'
  ];
  // Cognito User Pool
  const userPool = new cognito.UserPool(this, 'HandTermUserPool', {
   userPoolName: 'HandTermUserPool',
   selfSignUpEnabled: true,
   userVerification: {
    emailSubject: 'Verify your email for our app!',
    emailBody: 'Hello {username}, Thanks for signing up to our app! Your verification code is {####}',
    emailStyle: cognito.VerificationEmailStyle.CODE,
   },
   signInAliases: {
    email: true
   },
   passwordPolicy: {
    minLength: 8,
    requireUppercase: true,
    requireLowercase: true,
    requireDigits: true,
    requireSymbols: true,
   },
   autoVerify: { email: true }
  });

  // Cognito User Pool Client
  const userPoolClient = userPool.addClient('AppClient', {
   authFlows: {
    userSrp: true,
    userPassword: true // Enable USER_PASSWORD_AUTH flow
   },
   generateSecret: false,
   // Add your API Gateway endpoint URL to the list of callback URLs
  });

  // Cognito Identity Pool
  const identityPool = new cognito.CfnIdentityPool(this, 'HandTermIdentityPool', {
   identityPoolName: 'HandTermIdentityPool',
   allowUnauthenticatedIdentities: false,
   cognitoIdentityProviders: [{
    clientId: userPoolClient.userPoolClientId,
    providerName: userPool.userPoolProviderName,
   }],
  });

  // TODO: Remove this before production. This is only to make signup easier during development
  const preSignupLambda = new lambda.Function(this, 'PreSignupLambda', {
   runtime: nodeRuntime,
   handler: 'preSignup.handler',
   code: lambda.Code.fromAsset('lambda/authentication'),
  });
  userPool.addTrigger(cognito.UserPoolOperation.PRE_SIGN_UP, preSignupLambda);
  // S3 Bucket for User Logs
  const logsBucket = new s3.Bucket(this, 'HandTermHistoryBucket', {
   bucketName: ENDPOINTS.aws.s3.bucketName,
   blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
   encryption: s3.BucketEncryption.S3_MANAGED,
  });

  // Lambda Execution Role
  const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', {
   assumedBy: new iam.CompositePrincipal(
    new iam.ServicePrincipal('lambda.amazonaws.com'),
    new iam.ServicePrincipal('edgelambda.amazonaws.com')
   ),
   managedPolicies: [
    iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
    iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3FullAccess'),
   ],
  });

  const cognitoUserPoolEnvVar = {
   COGNITO_USER_POOL_ID: userPool.userPoolId
  };

  const authorizerLambda: IFunction = new lambda.Function(this, 'AuthorizerFunction', {
   runtime: nodeRuntime,
   handler: 'authorizer.handler',
   code: lambda.Code.fromAsset('lambda/authentication'),
   environment: {
    COGNITO_USER_POOL_ID: userPool.userPoolId
   }
  });

  // Define the authorizer with the correct constructor arguments
  const lambdaAuthorizer = new HttpLambdaAuthorizer('CustomAuthorizer', authorizerLambda, {
   identitySource: ['$request.header.Authorization']
  });

  // Define the HTTP API
  const httpApi = new HttpApi(this, 'HandTermApi', {
   apiName: 'HandTermService',
   description: 'This service serves authentication requests.',
   // CORS configuration if needed
   corsPreflight: {
    allowOrigins: ['http://localhost:5173', 'https://handterm.com'],
    allowMethods: [CorsHttpMethod.GET, CorsHttpMethod.POST, CorsHttpMethod.OPTIONS],
    allowHeaders: ['Content-Type', 'Authorization'],
    allowCredentials: true,
   },
  });

  const signUpLambda = new lambda.Function(this, 'SignUpFunction', {
   runtime: nodeRuntime,
   handler: 'signUp.handler',
   role: lambdaExecutionRole,
   code: lambda.Code.fromAsset('lambda/authentication'),
   environment: {
    COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
   }
  });
  const signUpIntegration = new HttpLambdaIntegration('signup-integration', signUpLambda);
  httpApi.addRoutes({
   path: ENDPOINTS.api.SignUp,
   methods: [HttpMethod.POST],
   integration: signUpIntegration,
  })

  const signInLambda = new lambda.Function(this, 'SignInFunction', {
   runtime: nodeRuntime,
   handler: 'signIn.handler',
   role: lambdaExecutionRole,
   code: lambda.Code.fromAsset('lambda/authentication'),
   environment: {
    COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
   }
  });

  httpApi.addRoutes({
   path: ENDPOINTS.api.SignIn,
   methods: [HttpMethod.POST],
   integration: new HttpLambdaIntegration(
    'post-user-signin',
    signInLambda
   ),
  })

  const refreshTokenLambda = new lambda.Function(this, 'RefreshTokenFunction', {
   runtime: nodeRuntime,
   handler: 'refreshToken.handler',
   role: lambdaExecutionRole,
   code: lambda.Code.fromAsset('lambda/authentication'),
   environment: {
    COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
   }
  });

  httpApi.addRoutes({
   path: ENDPOINTS.api.RefreshToken,
   methods: [HttpMethod.POST],
   integration: new HttpLambdaIntegration(
    'post-user-signin',
    refreshTokenLambda
   ),
  })

  const changePasswordLambda = new lambda.Function(this, 'ChangePasswordFunction', {
   runtime: nodeRuntime,
   handler: 'changePassword.handler',
   role: lambdaExecutionRole,
   code: lambda.Code.fromAsset('lambda/authentication'),
   environment: {
    COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
   }
  });
  const changePasswordIntegration = new HttpLambdaIntegration('change-password-integration', changePasswordLambda);

  httpApi.addRoutes({
   path: ENDPOINTS.api.ChangePassword,
   authorizer: lambdaAuthorizer,
   methods: [HttpMethod.POST],
   integration: changePasswordIntegration,
  })

  const getUserLambda = new lambda.Function(this, 'GetUserFunction', {
   runtime: nodeRuntime,
   handler: 'getUser.handler',
   role: lambdaExecutionRole,
   code: lambda.Code.fromAsset('lambda/userStorage'),
   environment: {
    COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
   }
  });
  const getUserIntegration = new HttpLambdaIntegration('get-user-integration', getUserLambda);
  httpApi.addRoutes({
   path: ENDPOINTS.api.GetUser,
   authorizer: lambdaAuthorizer,
   methods: [HttpMethod.GET],
   integration: getUserIntegration,
  })

  const setUserLambda = new lambda.Function(this, 'SetUserFunction', {
   runtime: nodeRuntime,
   handler: 'setUser.handler',
   role: lambdaExecutionRole,
   code: lambda.Code.fromAsset('lambda/userStorage'),
   environment: {
    COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
   }
  });
  const setUserIntegration = new HttpLambdaIntegration('set-user-integration', setUserLambda);
  httpApi.addRoutes({
   path: ENDPOINTS.api.SetUser,
   authorizer: lambdaAuthorizer,
   methods: [HttpMethod.POST],
   integration: setUserIntegration,
  })

  const saveLogLambda = new lambda.Function(this, 'SaveLogFunction', {
   runtime: nodeRuntime,
   handler: 'saveLog.handler',
   role: lambdaExecutionRole,
   code: lambda.Code.fromAsset('lambda/userStorage'),
   environment: {
    COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
   }
  });
  const saveLogIntegration = new HttpLambdaIntegration('save-log-integration', saveLogLambda);
  httpApi.addRoutes({
   path: ENDPOINTS.api.SaveLog,
   authorizer: lambdaAuthorizer,
   methods: [HttpMethod.POST],
   integration: saveLogIntegration,
  })

  const getLogLambda = new lambda.Function(this, 'GetLogFunction', {
   runtime: nodeRuntime,
   handler: 'getLog.handler',
   role: lambdaExecutionRole,
   code: lambda.Code.fromAsset('lambda/userStorage'),
   environment: {
    COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
   }
  });
  const getLogIntegration = new HttpLambdaIntegration('get-log-integration', getLogLambda);
  httpApi.addRoutes({
   path: ENDPOINTS.api.GetLog,
   authorizer: lambdaAuthorizer,
   methods: [HttpMethod.POST, HttpMethod.GET],
   integration: getLogIntegration,
  })

  const listLogLambda = new lambda.Function(this, 'ListLogFunction', {
   runtime: nodeRuntime,
   handler: 'listLog.handler',
   role: lambdaExecutionRole,
   code: lambda.Code.fromAsset('lambda/userStorage'),
   environment: {
    COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
   }
  });
  const listLogIntegration = new HttpLambdaIntegration('list-log-integration', listLogLambda);
  httpApi.addRoutes({
   path: ENDPOINTS.api.ListLog,
   authorizer: lambdaAuthorizer,
   methods: [HttpMethod.POST, HttpMethod.GET],
   integration: listLogIntegration,
  })

  // Outputs
  new CfnOutput(this, 'UserPoolId', { value: userPool.userPoolId });
  new CfnOutput(this, 'UserPoolClientId', { value: userPoolClient.userPoolClientId });
  new CfnOutput(this, 'IdentityPoolId', { value: identityPool.ref });
  new CfnOutput(this, 'BucketName', { value: logsBucket.bucketName });
  new CfnOutput(this, 'ApiEndpoint', { value: httpApi.url || '' });
 }
}

const app = new App();
new HandTermCdkStack(app, 'HandTermCdkStack');

The login seems to timeout very quickly. I use a refresh token checker in my getLog and getUser functions, but it doesn't seem to be able to refresh after about a day.

How can I extend the time that the refresh token works for?

Is the Cognito server saving state for the user?


Codeium at 9:53 am, Jul 8, 2024

The timeout duration of your refresh tokens is controlled by the settings of your Amazon Cognito User Pool. Amazon Cognito refresh tokens have a default validity period of 30 days but can be set to any value between 60 minutes and 10 years.

To extend the time that the refresh token works for, you need to adjust the refresh token expiration setting in your Cognito User Pool. This setting is not directly adjustable via the AWS CDK UserPool construct in the code snippet you've provided. Instead, you would adjust this setting in the AWS Management Console, or you could specify this setting if you are using the AWS CLI or SDKs to manipulate the User Pool directly.

For adjusting the refresh token validity in the AWS Management Console:

  1. Go to the Amazon Cognito Console.
  2. Choose "Manage User Pools" and select the user pool you're working with.
  3. Under the "General settings" section, choose "App clients."
  4. Select the app client used by your application.
  5. In the "App client settings," there is a section for "Refresh token expiration." You can set this to the duration you prefer, up to a maximum of 10 years.

If you wish to automate this configuration through CDK or another infrastructure as code tool, you would typically need to use a custom resource or directly manipulate the User Pool using the AWS SDK in a setup script, as the CDK's UserPoolClient construct does not expose a property for refresh token validity as of my last update.

Regarding your question about Cognito saving state for the user, Amazon Cognito does maintain session information for authenticated users, including the issuance of access, ID, and refresh tokens. The state regarding these sessions, including their expiration and validity, is managed by Cognito. Your application can use these tokens to manage sessions and perform actions on behalf of the user, including refreshing the session using the refresh token before it expires.

If your refresh tokens are expiring too quickly for your use case, adjusting the refresh token expiration as described above should address this issue. Additionally, ensure your application logic correctly handles token refresh scenarios, using the refresh token to obtain new access and ID tokens without requiring the user to re-authenticate.


User at 9:57 am, Jul 8, 2024

I would prefer all AWS changes to be done through the AWS CLI or CloudFormation.


Codeium at 9:58 am, Jul 8, 2024

To adjust the refresh token expiration setting of an Amazon Cognito User Pool client via the AWS CLI or CloudFormation, you can follow these steps:

AWS CLI

To update the refresh token expiration for a user pool client using the AWS CLI, you can use the update-user-pool-client command. The refresh token validity period is specified in days. Here’s an example command that sets the refresh token validity to 90 days for a specified user pool client:

aws cognito-idp update-user-pool-client --user-pool-id <YourUserPoolId> --client-id <YourUserPoolClientId> --refresh-token-validity <Days>

Replace <YourUserPoolId>, <YourUserPoolClientId>, and <Days> with your user pool ID, your user pool client ID, and the number of days you want the refresh token to be valid, respectively.

For instance, to set it to 90 days:

aws cognito-idp update-user-pool-client --user-pool-id us-east-1_example --client-id 2a3b4c5d6e7f89012345 --refresh-token-validity 90

CloudFormation

When defining your user pool client in AWS CloudFormation, you can specify the RefreshTokenValidity property under AWS::Cognito::UserPoolClient. This property defines the refresh token expiration in days. Here’s an example snippet for a CloudFormation template:

Resources:
  MyUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      UserPoolId: !Ref MyUserPool
      ClientName: my-app-client
      RefreshTokenValidity: 90
      ExplicitAuthFlows:
        - ALLOW_USER_SRP_AUTH
        - ALLOW_REFRESH_TOKEN_AUTH

This snippet defines a user pool client with a refresh token validity period of 90 days. Ensure you replace MyUserPool with the actual reference or ID of your Cognito User Pool.

Updating this setting via CloudFormation will cause an update to your existing stack, applying the changes to the user pool client as specified. If you're adapting the provided CDK code, note that as of the last update, the AWS CDK for TypeScript does not directly expose a property for RefreshTokenValidity in the UserPoolClient construct. You might need to implement a custom resource or directly manipulate the template JSON to set this property if you're working within the CDK and wish to avoid manual adjustments via the CLI or console.

Remember, when using the AWS CLI or CloudFormation to adjust these settings, it will apply to all new tokens issued after the change. Existing refresh tokens will retain their original expiration settings until they're renewed or expire.


User at 10:00 am, Jul 8, 2024

I thought the CDK was able to produce any CloudFormation output. You are saying that the RefreshTokenValidity cannot be set through the CDK?

How can I query the current RefreshTokenValidity by using the AWS CLI?


Codeium at 10:00 am, Jul 8, 2024

The AWS CDK does indeed aim to support the entirety of AWS CloudFormation's capabilities, often providing higher-level abstractions for CloudFormation resources. My previous response might have been unclear regarding the RefreshTokenValidity setting through the AWS CDK. While some specific configurations may not be directly exposed through high-level constructs, you can often set these properties using escape hatches or by directly defining lower-level CloudFormation resources within CDK applications.

For setting RefreshTokenValidity in CDK, you can use the UserPoolClient construct and specify the refreshTokenValidity property, like so:

import * as cognito from '@aws-cdk/aws-cognito';

const userPool = new cognito.UserPool(this, 'HandTermUserPool', {
  // User pool properties...
});

const userPoolClient = new cognito.UserPoolClient(this, 'AppClient', {
  userPool,
  refreshTokenValidity: cdk.Duration.days(90) // Sets refresh token validity to 90 days
});

This approach directly utilizes CDK's support for configuring the refresh token's validity period.

Querying the Current RefreshTokenValidity Using the AWS CLI

To query the current RefreshTokenValidity of a User Pool Client using the AWS CLI, you can use the describe-user-pool-client command. This command retrieves the configuration information of the specified user pool client, including the refresh token validity period.

Here's how you can use it:

aws cognito-idp describe-user-pool-client --user-pool-id <UserPoolId> --client-id <UserPoolClientId>

Replace <UserPoolId> with your Cognito User Pool ID and <UserPoolClientId> with the ID of the client you're interested in. The output will include a RefreshTokenValidity field showing the validity period of the refresh token in days.

For example:

aws cognito-idp describe-user-pool-client --user-pool-id us-east-1_example --client-id 2a3b4c5d6e7f89012345

Look for the RefreshTokenValidity field in the JSON output to find the current setting.


User at 11:16 am, Jul 8, 2024

How do I query my current user pools?


Codeium at 11:16 am, Jul 8, 2024

To query your current Amazon Cognito user pools using the AWS CLI, you can use the list-user-pools command. This command lists the user pools associated with an AWS account. Since user pools are regional resources, ensure you specify the correct region in your AWS CLI configuration or by using the --region parameter with your command.

Here's the basic syntax for the command:

aws cognito-idp list-user-pools --max-results <MaxResults> --region <Region>
  • Replace <MaxResults> with the maximum number of user pools you want to list. The maximum you can specify is 60.
  • Replace <Region> with the AWS region for which you want to list the user pools (e.g., us-east-1).

For example, to list up to 10 user pools in the us-east-1 region, you would use:

aws cognito-idp list-user-pools --max-results 10 --region us-east-1

This command returns a list of user pools, including their names and IDs, up to the maximum number specified. If you have more user pools than the --max-results value, the response includes a pagination token that you can use to retrieve the next set of user pools.

To manage your AWS CLI default region setting or for temporary region overrides, you can also use these approaches:

  • Default region setting: Configure your default region by running aws configure and setting the Default region name.
  • Temporary region override: Use the --region parameter in your commands to temporarily override the default region setting for that specific command execution.

Remember, if you're working within an organization or a setup with multiple AWS profiles, ensure you're using the correct profile by either configuring it globally through aws configure or by specifying the --profile parameter in your commands.


User at 11:21 am, Jul 8, 2024

If I do this:

🕐[11:19:59]❯ aws cognito-idp list-user-pool-clients --max-results 20 --user-pool-id us-east-1_h1HKtyGeM
- UserPoolClients: []

I get no results.


Codeium at 11:21 am, Jul 8, 2024

If you're running the aws cognito-idp list-user-pool-clients command and receiving an empty list in the response, like:

- UserPoolClients: []

This result typically suggests that there are no user pool clients currently configured for the specified user pool ID, or there might be an issue with the command or the environment you're working in. Here are a few steps to troubleshoot and resolve this issue:

  1. Verify User Pool ID: Double-check that the user pool ID us-east-1_h1HKtyGeM is correct and that it belongs to the region you're targeting. The ID should be in the format region_xxxxx. It's easy to accidentally misspell or misconfigure these values.

  2. Check AWS Region: Make sure you're targeting the correct AWS region where the user pool is located. If your AWS CLI default region is different from the region of your user pool, you need to specify the correct region in your command using the --region parameter. For example:

    aws cognito-idp list-user-pool-clients --user-pool-id us-east-1_h1HKtyGeM --max-results 20 --region us-east-1

    Replace us-east-1 with the actual region of your user pool if it's different.

  3. AWS CLI Version: Ensure that your AWS CLI is up to date. Older versions of the CLI might not support all features or might have bugs that have been fixed in later releases. You can update the AWS CLI to the latest version by following the instructions on the official AWS documentation.

  4. Permissions: Ensure that the AWS IAM user or role you're using with the AWS CLI has the necessary permissions to list user pool clients in Amazon Cognito. The IAM policy should include permissions for cognito-idp:ListUserPoolClients.

  5. Confirm User Pool Clients Exist: Log in to the AWS Management Console, navigate to the Amazon Cognito service, select your user pool, and then check the "App clients" section to confirm that there are indeed app clients created for this user pool. If there are no app clients, the empty list response is accurate.

  6. Syntax Check: Ensure there are no typos or errors in your AWS CLI command. Although your command looks syntactically correct, it's always good to double-check for any possible mistakes.

If, after these checks, you're still unable to retrieve the list of user pool clients and you're sure they exist, consider reaching out to AWS Support for further assistance.

⚠️ **GitHub.com Fallback** ⚠️