DEV Community

Cover image for Adding Cognito Authentication to Self-Hosted Langfuse with AWS CDK
Matsuda for AWS Community Builders

Posted on

2

Adding Cognito Authentication to Self-Hosted Langfuse with AWS CDK

Langfuse provides built-in authentication using ID/password by default, but it also allows using other IdPs (NextAuth.js is used).

https://langfuse.com/self-hosting/authentication-and-sso

Although the default authentication method in Langfuse is ID/password, there are cases where integrating an external IdP is desirable for enhanced security.

In this post, I have added authentication using Amazon Cognito (hereafter referred to as Cognito) to Langfuse with AWS CDK, which was created in the previous post.

After configuration, you will be able to log in with Cognito as shown below:

Image description

Additionally, it is possible to disable the default authentication mechanism and enforce authentication via Cognito.

Image description

This post summarizes the key points of this setup.

The GitHub repository is available below:

https://github.com/mazyu36/langfuse-with-aws-cdk

Why Use an External IdP?

One of the main motivations for using an external IdP is to enhance security.

When using Langfuse's built-in authentication, users sign up via the following screen:

Image description

During sign-up, users enter their email addresses, but there is no additional verification step such as sending a verification code via email. As a result, accounts are created immediately, and users are redirected to the Langfuse dashboard.

This means that as long as someone has access, they can sign up and gain access right away, raising concerns about the misuse of API keys.

Another drawback is the limited configurability of authentication settings.

By using an external IdP, you can enforce email verification, restrict sign-up to pre-created users only, and flexibly customize authentication according to your requirements.

This is particularly useful when the application is widely available but you want to limit the users who can issue API keys.

Configuring Cognito

After creating a Cognito user pool and an app client, authentication can be enabled by setting the following environment variables in Langfuse Web (refer to the Langfuse documentation):

  • AUTH_COGNITO_CLIENT_ID: ID of the user pool client
  • AUTH_COGNITO_CLIENT_SECRET: Secret of the user pool client
  • AUTH_COGNITO_ISSUER: Set this to https://cognito-idp.{region}.amazonaws.com/{PoolId}. This value is also mentioned in the NextAuth.js documentation.
  • AUTH_DISABLE_USERNAME_PASSWORD: Setting this to true disables Langfuse’s built-in authentication. This is optional.
  • AUTH_COGNITO_ALLOW_ACCOUNT_LINKING: Setting this to true merges accounts if the same email address exists in Langfuse's built-in authentication. This is useful when migrating existing users to an external IdP. This setting is also optional.

Additionally, you must configure Cognito's app client with a callback URL set to https://{Langfuse domain}/api/auth/callback/cognito. Without this, authentication will not function correctly.

One important point to note is that Cognito requires callback URLs (except for localhost) to use https. Therefore, when implementing Cognito authentication in a self-hosted environment, HTTPS is mandatory.

Furthermore, you need to configure Cognito’s managed login (hosted UI) and domain settings.

The details of the CDK implementation will be covered later.

Architecture

Below is the overall architecture. Compared to the previous post, a Cognito UserPool and an associated ACM certificate have been added.

Image description

In this setup, a custom domain is used for Cognito’s authentication page, which internally creates a CloudFront distribution. Since ACM certificates must be issued in Virginia (us-east-1), deploying Langfuse in a different region results in a cross-region setup.

To handle this in CDK, the implementation is split into two stacks:

  1. A stack specifically for Cognito’s custom domain ACM certificate (us-east-1 fixed)
  2. A stack for all other resources (region can be any)

Ref: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html

CDK Implementation

The key implementation points are as follows:

  1. Create a stack for us-east-1
  2. Create Cognito’s UserPool, UserPool Client, and Domain
  3. Configure Langfuse Web environment variables with Cognito information

1. Creating a Stack for us-east-1

A new stack is added to create the ACM certificate for Cognito’s custom domain. Since ACM certificates must be issued in us-east-1, the stack's region is fixed.

crossRegionReference is enabled to allow references across regions. This is a CDK-specific mechanism that utilizes SSM parameters. For more details, refer to the CDK documentation.

// Stack for creating ACM certificate
if (appConfig.enableCognitoAuth === true) {
  usEast1Stack = new UsEast1Stack(app, `UsEast1Stack-${envName}`, {
    env: {
      account: appConfig.env?.account ?? process.env.CDK_DEFAULT_ACCOUNT,
      region: 'us-east-1',  // Fixed to us-east-1
    },
    crossRegionReferences: true,  // Enable cross-region references
    domainConfig: appConfig.domainConfig,
  });
}

const stack = new LangfuseWithAwsCdkStack(app, `LangfuseWithAwsCdkStack-${envName}`, {
  env: {
    account: appConfig.env?.account ?? process.env.CDK_DEFAULT_ACCOUNT,
    region: appConfig.env?.region ?? process.env.CDK_DEFAULT_REGION,  // Region for deploying Langfuse
  },
  envName,
  hostName: appConfig.domainConfig?.hostName,
  domainName: appConfig.domainConfig?.domainName,
  crossRegionReferences: true,  // Enable cross-region references
  disableEmailPasswordAuth: appConfig.disableEmailPasswordAuth,
  certificateForCognito: usEast1Stack?.certificateForCognito,  // Pass ACM certificate
});
Enter fullscreen mode Exit fullscreen mode

2. Creating Cognito UserPool, UserPool Client, and Domain

We will now define the resources related to Cognito. The implementation can be found in lib/constructs/auth/cognito-auth.ts.

First, let's create a user pool. There are no particular points to highlight here. Customize the authentication methods as needed based on authentication requirements.

this.userPool = new cognito.UserPool(this, 'UserPool', {
  accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
  signInAliases: {
    email: true,
  },
  standardAttributes: {
    email: {
      required: true,
      mutable: true,
    },
  },
  selfSignUpEnabled: true,
  userVerification: {
    emailSubject: 'Langfuse - Verify your new account',
  },
  removalPolicy: RemovalPolicy.DESTROY,
});
Enter fullscreen mode Exit fullscreen mode

Next, let's create an app client. It is mandatory to specify the Callback URL and generate a client secret.

this.userPoolclient = this.userPool.addClient('CognitoClient', {
  authFlows: {
    userPassword: true,
    userSrp: true,
  },
  oAuth: {
    flows: {
      authorizationCodeGrant: true,
    },
    scopes: [cognito.OAuthScope.OPENID, cognito.OAuthScope.EMAIL],
    callbackUrls: [`${cdnLoadBalancer.url}/api/auth/callback/cognito`],  // Specify the Callback URL
  },
  generateSecret: true,  // Generate the client secret
});
Enter fullscreen mode Exit fullscreen mode

Next, enable managed login for the Cognito authentication screen and set up a custom domain.

// Configure managed login
new cognito.CfnManagedLoginBranding(this, 'ManagedLoginBranding', {
  userPoolId: this.userPool.userPoolId,
  clientId: this.userPoolclient.userPoolClientId,
  useCognitoProvidedValues: true,
});

// Set up a custom domain for Cognito
const domain = this.userPool.addDomain('CognitoDomain', {
  customDomain: {
    domainName: `auth.${hostName}.${hostedZone.zoneName}`,
    certificate: certificateForCognito,
  },
  managedLoginVersion: cognito.ManagedLoginVersion.NEWER_MANAGED_LOGIN,
});

// **Wait for the A record of Langfuse to be generated**
domain.node.addDependency(cdnLoadBalancer.albARecord!);

// Generate a custom domain record
new route53.ARecord(this, 'CognitoARecord', {
  zone: hostedZone,
  recordName: `auth.${hostName}`,
  target: route53.RecordTarget.fromAlias(new targets.UserPoolDomainTarget(domain)),
});
Enter fullscreen mode Exit fullscreen mode

One crucial point to be aware of is the following:

// **Wait for the A record of Langfuse to be generated**
domain.node.addDependency(cdnLoadBalancer.albARecord!);
Enter fullscreen mode Exit fullscreen mode

Due to the specifications of Cognito's custom domains, a valid A record must exist for the parent domain. In this CDK implementation, the Cognito custom domain is designed to prepend auth. to the domain assigned to Langfuse.

  • Langfuse domain: langfuse.example.com (The domain can be configured in bin/app-config.ts)
  • Cognito custom domain: auth.langfuse.example.com

If a valid A record does not exist for the parent Langfuse domain when setting up the Cognito custom domain, the deployment will fail with an error.

Therefore, in the CDK implementation, we define a dependency to ensure that the A record for the Langfuse domain (specifically, the domain assigned to the ALB) is generated before setting up the Cognito custom domain.

This requirement is documented in the following AWS documentation:

A web domain that you own. Its parent domain must have a valid DNS A record. You can assign any value to this record. The parent may be the root of the domain, or a child domain that is one step up in the domain hierarchy. For example, if your custom domain is auth.xyz.example.com, Amazon Cognito must be able to resolve xyz.example.com to an IP address.

https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html#cognito-user-pools-add-custom-domain-prereq

3. Configuring Cognito-related Information in Langfuse Web Environment Variables

Finally, configure the Cognito-related information as environment variables in Langfuse Web.

AUTH_COGNITO_CLIENT_ID: cognitoAuth.userPoolclient.userPoolClientId,
AUTH_COGNITO_CLIENT_SECRET: cognitoAuth.userPoolclient.userPoolClientSecret.unsafeUnwrap(),
AUTH_COGNITO_ISSUER: `https://cognito-idp.${Stack.of(this).region}.amazonaws.com/${cognitoAuth.userPool.userPoolId}`,
Enter fullscreen mode Exit fullscreen mode

With this setup, authentication via Cognito is now enabled.

Verification

To enable Cognito authentication in Langfuse with AWS CDK, set enableCognitoAuth to true in bin/app-config.ts.

In the sample configuration in the repository, it is enabled for prod.

  prod: {
    // omit
    disableEmailPasswordAuth: true,  // Set to true to disable Langfuse's built-in authentication
    enableCognitoAuth: true,  // Enable Cognito authentication
  },
Enter fullscreen mode Exit fullscreen mode

After applying these settings, deploy to the target environment. Since enabling Cognito authentication results in two separate stacks, the --all flag is required.

npx cdk deploy --context env=prod --all
Enter fullscreen mode Exit fullscreen mode

Once the deployment is complete, access the Langfuse URL. You should see a Cognito login menu. Click on it.

Image description

With this implementation, users are redirected to Cognito's managed login authentication screen. It may take some time for the custom domain to propagate, so if you encounter an error, try again after a while. If authentication still fails, there may be a configuration issue.

Image description

Next, follow the user pool's settings to complete the signup process. In this implementation, after entering signup details, a verification code is sent to the specified email address, so complete the verification process.

Image description

Once signup is complete, users will be redirected to the Langfuse initial screen. From here on, authentication via Cognito will be available.

Image description

Conclusion

Introducing an external IdP can be a valuable way to enhance the security of a self-hosted Langfuse environment.
Give it a try!

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Best Practices for Running  Container WordPress on AWS (ECS, EFS, RDS, ELB) using CDK cover image

Best Practices for Running Container WordPress on AWS (ECS, EFS, RDS, ELB) using CDK

This post discusses the process of migrating a growing WordPress eShop business to AWS using AWS CDK for an easily scalable, high availability architecture. The detailed structure encompasses several pillars: Compute, Storage, Database, Cache, CDN, DNS, Security, and Backup.

Read full post