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:
Additionally, it is possible to disable the default authentication mechanism and enforce authentication via Cognito.
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:
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 tohttps://cognito-idp.{region}.amazonaws.com/{PoolId}
. This value is also mentioned in the NextAuth.js documentation. -
AUTH_DISABLE_USERNAME_PASSWORD
: Setting this totrue
disables Langfuse’s built-in authentication. This is optional. -
AUTH_COGNITO_ALLOW_ACCOUNT_LINKING
: Setting this totrue
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.
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:
- A stack specifically for Cognito’s custom domain ACM certificate (
us-east-1
fixed) - 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:
- Create a stack for
us-east-1
- Create Cognito’s UserPool, UserPool Client, and Domain
- 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
});
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,
});
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
});
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)),
});
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!);
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.
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}`,
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
},
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
Once the deployment is complete, access the Langfuse URL. You should see a Cognito login menu. Click on it.
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.
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.
Once signup is complete, users will be redirected to the Langfuse initial screen. From here on, authentication via Cognito will be available.
Conclusion
Introducing an external IdP can be a valuable way to enhance the security of a self-hosted Langfuse environment.
Give it a try!
Top comments (0)