#Endpoint security

1 messages · Page 1 of 1 (latest)

shadow folio
#

It says that "Convex deployment endpoints are exposed to the open internet" in the docs, does that mean that anyone (not using my application) can also access them or is there a layer of auth with the CONVEX_DEPLOY_KEY that is used?

gritty marshBOT
#

Hi @shadow folio. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Yes, Convex deployment endpoints are exposed to the open internet, which means that anyone who knows your deployment’s public URL can attempt to access them. However, this does not mean they can freely access your data or functions—security depends on how you implement authentication and authorization in your Convex functions.

The CONVEX_DEPLOY_KEY is not used for authenticating end-user or client requests to your Convex backend. Instead, it is used for administrative purposes, such as deploying code from CI/CD pipelines or the CLI, and for authenticating streaming import/export API requests. It is not a mechanism for securing your public API endpoints from general access by users or clients of your application.

For user-level security, you must implement authentication and authorization checks in your public Convex functions (queries, mutations, actions). Convex recommends integrating with an authentication provider (like Clerk, Auth0, or your own OpenID Connect provider) and checking user credentials and permissions in every public function. Without these checks, anyone could call your public functions if they know the API and function names, potentially exposing or modifying your data. Internal functions, on the other hand, are only callable from within your Convex backend or by project admins via the CLI or dashboard, and are not exposed to the public internet in the same way.

In summary:

  • Anyone can access your public Convex endpoints if they know the URL, unless you implement proper authentication and authorization in your functions.
  • The CONVEX_DEPLOY_KEY is for deployment/admin purposes, not for securing public endpoints from general access.
  • You are responsible for securing your public functions by checking authentication and authorization in your code.
#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh What if I already have auth from next-auth and I am migrating to convex?

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

If you already have authentication set up with NextAuth (now called Auth.js) and are migrating to Convex, you can continue to use NextAuth for authentication and integrate it with Convex by following the Convex Adapter for Auth.js (NextAuth) setup. This allows you to use Convex as the database adapter for Auth.js and to authenticate Convex function calls using JWTs issued by your Next.js server.

Here’s a high-level summary of the migration steps:

  1. Set up the Convex Adapter for Auth.js

    • Copy the adapter code into your project as described in the Convex Adapter for Auth.js Setup Guide.
    • Update your Convex schema to include the tables required by Auth.js (users, accounts, sessions, etc.).
  2. Configure JWT Authentication

    • Generate a public/private key pair and configure your Next.js server to sign JWTs with the private key, and your Convex backend to verify them with the public key.
    • Expose the public key via a JWKS endpoint in your Convex backend, and update your Convex auth config to use it for JWT validation.
    • See the detailed steps in Authenticating Convex function calls.
  3. Pass the JWT to the Convex React Client

    • Use the ConvexProviderWithAuth and a custom useAuth hook to provide the JWT from the NextAuth session to Convex, so that all Convex queries and mutations are authenticated.
    • Example implementation is shown in Provide the JWT to the Convex React client.
  4. Update Your Convex Functions

    • In your Convex queries and mutations, use ctx.auth.getUserIdentity() to access the authenticated user’s identity and enforce authorization as needed.
#

By following these steps, you can migrate your app to use Convex for your backend/database while continuing to use NextAuth for authentication, with secure integration between the two systems.

For a working example and more details, you can refer to the convex-nextauth-template repository, which demonstrates this integration in practice.

If you need more details on any step, the Convex Adapter for Auth.js Setup Guide provides a comprehensive walkthrough.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh what is the current best practice of auth provider to choose when building convex? I don't want to use a third party

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

If you want to build authentication for your Convex app without using a third-party provider, the current best practice is to use Convex Auth. Convex Auth is a library that lets you implement authentication directly within your Convex backend, so you don’t need to rely on external services like Clerk or Auth0.

Convex Auth supports:

  • Magic links and OTPs (email-based sign-in)
  • OAuth (e.g., Google, GitHub, Apple, etc.)
  • Password-based authentication (with optional email verification and password reset)

It is designed for React web apps and React Native, and stores all user data directly in your Convex database, simplifying your architecture and avoiding the need to sync state with an external database. Note that Convex Auth is currently in beta, but it is the recommended approach for self-hosted authentication with Convex if you want to avoid third-party services Convex Auth docs Convex Auth introduction.

If you are not using React, Convex Auth currently only provides client libraries for React, but you can still use the backend part and implement your own frontend logic Convex Auth FAQ.

Summary:
Use Convex Auth for a first-party, self-hosted authentication solution with Convex. It is the best practice if you want to avoid third-party providers.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh would about better-auth?

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Yes, Better Auth is another option for authentication with Convex, and it is currently available as a Convex Component called "Convex Better Auth." This integration allows you to use the Better Auth library directly with Convex, providing a comprehensive and secure authentication solution without relying on third-party providers like Clerk or Auth0.

Key points about Better Auth with Convex:

  • It is a Convex Component that integrates Better Auth with your Convex backend.
  • It is in early alpha development, so you may encounter breaking changes or missing features, but active support is available via GitHub and Discord.
  • Better Auth manages its own user table within Convex, and you are expected to create your own application-specific users table as well.
  • It supports a variety of authentication flows, including email/password, OAuth, and more, similar to Convex Auth.
  • The integration is designed to be comprehensive and secure, and you can find migration guides, setup instructions, and working examples in the official documentation and GitHub repo.

Summary:
Better Auth is a viable, self-hosted authentication solution for Convex, especially if you want to avoid third-party providers. However, it is still in alpha, so you should be prepared for potential changes and keep an eye on updates from the maintainers Convex Better Auth docs.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh Is there any statement or information that convex is moving more towards better-auth? also do both convex auth and better auth support react-native?

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

There is no official statement in the provided knowledge sources that Convex is definitively moving towards Better Auth as the standard authentication solution. However, there are hints in community discussions that active development focus is currently on the Better Auth integration, and it is possible that it could become the new standard in the future. If that happens, migration paths will be documented, and Convex Auth is not expected to disappear in the near term. Both solutions are currently supported, and Convex Auth is considered stable enough for production use despite its beta label (Discord support thread: Convex Auth Reliability & Support).

React Native support:

  • Convex Auth: Officially supports React Native mobile apps (Convex Auth docs).
  • Better Auth: The documentation and examples focus on React, Next.js, and TanStack Start. There is no explicit mention of React Native support for Better Auth in the provided sources, so its compatibility with React Native is unclear based on the current documentation (Convex Better Auth docs).

Summary:

  • No official statement that Convex is moving exclusively to Better Auth, but there is increased focus on it.
  • Convex Auth supports React Native; Better Auth’s React Native support is not clearly documented in the provided sources.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh Is there a way to globally set that it is always required to be logged in, instead of validating ctx.auth.getUserIdentity on each function?

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Convex does not provide a built-in, global setting to require authentication for all functions. Instead, the recommended approach is to enforce authentication at the function level by checking ctx.auth.getUserIdentity() in each public function (query, mutation, action) Understanding Best Practices.

However, to avoid repeating this check in every function, you can use custom wrappers or helper functions. For example, you can create a wrapper (like withUser, authQuery, or customQuery) that automatically checks authentication and use it to define your functions. This pattern allows you to enforce authentication consistently without duplicating code:

export const authQuery = customQuery(
  query,
  customCtx(async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) {
      throw new Error('Not authenticated!');
    }
    return {};
  }),
);

You would then use authQuery instead of query for all functions that require authentication Authentication Best Practices: Custom Hooks and Utility Functions.

Summary:
There is no global switch, but you can achieve similar behavior by consistently using custom wrappers for your functions. This is the current best practice in Convex.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh how would one implement roles in convex auth? can the role be added into the token claims?

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

To implement roles in Convex Auth, the common approach is to store the user's role in your Convex database, typically as a field in the users table. When a user is created or updated (for example, in the createOrUpdateUser callback), you can assign a role to the user record. Here’s an example pattern from the community:

// In your Convex Auth setup
callbacks: {
  async createOrUpdateUser(ctx, args) {
    // ...find or create user logic
    return await ctx.db.insert("users", {
      email: args.profile.email,
      roles: ["admin"], // or dynamically assign based on your logic
      // other fields...
    });
  },
}

See example in Discord support thread

Can the role be added into the token claims?
Currently, Convex Auth does not support adding custom claims (like roles) directly into the JWT token at the time of token creation. The knowledge sources indicate that while this is a requested feature, it is not yet available in Convex Auth. The recommended approach is to look up the user's role from the database after authenticating the user in your Convex functions, rather than relying on the token to carry this information Discord support thread: Custom Claims to Convex Auth Token.

Summary:

  • Store roles in your users table and look them up in your backend functions after authentication.
  • Adding roles as custom claims in Convex Auth tokens is not currently supported, but may be considered for future updates.

If you need to access roles in your functions, always fetch the user record after verifying authentication.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh what about for better-auth?

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

For Better Auth with Convex, you can implement roles by adding a roles field to your application's own users table. Better Auth manages its own internal user table, but you are expected to create and manage your own application-specific user table, where you can store any custom fields such as roles or permissions.

When a user is created, you can assign roles in the onCreateUser hook, and update them in the onUpdateUser hook if needed. Here’s an example pattern:

export const { createUser, updateUser } = betterAuthComponent.createAuthFunctions({
  onCreateUser: async (ctx, user) => {
    // Assign a role when creating the user
    return await ctx.db.insert("users", {
      email: user.email,
      roles: ["admin"], // or assign dynamically
    });
  },
  onUpdateUser: async (ctx, user) => {
    // Update roles if needed
    await ctx.db.patch(user.userId as Id<"users">, {
      roles: user.roles, // or your own logic
    });
  },
});

See Better Auth users table guide

Can the role be added into the token claims?
The knowledge sources do not explicitly state whether you can add custom fields like roles directly into the JWT claims with Better Auth. The documentation focuses on syncing user metadata to your own users table and using hooks for user creation and updates, but does not mention custom claims in tokens. Therefore, you should plan to look up roles from your users table in your backend functions after authentication.

Summary:

  • Store roles in your own users table and manage them via Better Auth hooks.
  • There is no clear documentation that roles can be added to JWT claims in Better Auth; you should fetch roles from the database as needed.
#

If you need roles in your backend logic, always fetch the user record after authentication using the user ID provided by Better Auth.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh how about if I wan to use the next-auth adapter and add a role to the userschema and session and jwt

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

If you are using the NextAuth adapter with Convex and want to add a role to the user schema, session, and JWT, here’s how you can approach it based on the knowledge sources:

1. Add role to the Convex user schema

You can extend the users table in your Convex schema to include a role field:

import { defineSchema, defineTable } from "convex/server";
import { authTables } from "@convex-dev/auth/server";
import { v } from "convex/values";

const schema = defineSchema({
  ...authTables,
  users: defineTable({
    name: v.optional(v.string()),
    email: v.optional(v.string()),
    // ... other fields ...
    role: v.optional(v.string()), // Add your custom role field
  }).index("email", ["email"]),
  // Your other tables...
});

export default schema;

This pattern is confirmed in community discussions and is the standard way to add custom fields to the user schema (Discord support thread: Convex Auth: How to add custom data to signIn() (or up)).

2. Add role to the session and JWT

In your auth.ts (NextAuth config), you can add custom fields to the session and JWT in the callbacks:

export const { handlers, signIn, signOut, auth } = NextAuth({
  // ... other config ...
  callbacks: {
    async session({ session, token }) {
      // Add role from token to session
      session.role = token.role;
      return session;
    },
    async jwt({ token, user }) {
      // Add role from user to token on sign in
      if (user) {
        token.role = user.role;
      }
      return token;
    },
  },
});

You will also need to extend the NextAuth types to include your custom field:

#
declare module "next-auth" {
  interface Session {
    role?: string;
  }
}

This approach is standard for NextAuth and is compatible with the Convex adapter (Stack > Convex Adapter for Auth.js (NextAuth) Setup Guide).

3. Storing and retrieving the role

When a user signs up or is updated, ensure the role field is set in the Convex database. You can then access the role in your Convex functions by fetching the user document using the user ID from the session or JWT.


Summary:

  • Add role to your Convex users table schema.
  • Pass the role through the NextAuth jwt and session callbacks.
  • Extend the NextAuth types to include role.
  • Fetch the user’s role from the database in your Convex functions as needed.

This pattern is supported and aligns with the best practices shown in the knowledge sources.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh when i follow the guide for creating a next-auth adapter and at async jwt the user object only has an id and emailVerified, I am missing the rest of the user object like email, name, role etc

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

When using the Convex Adapter for NextAuth (Auth.js), it is expected that the user object passed to the jwt callback during sign-in only contains a subset of fields, typically id and emailVerified. This is because, by default, the adapter only returns these fields after user creation or update. The rest of the user fields (like email, name, role, etc.) are not automatically included in the user object passed to the jwt callback.

This behavior is consistent with the standard Auth.js adapter interface, where the user object in the jwt callback is often minimal. If you need additional fields (such as role), you will need to fetch the full user object from your database using the user ID within the jwt callback.

The knowledge sources do not provide a built-in way to automatically include all user fields in the user object in the jwt callback when using the Convex adapter. The recommended approach is to perform a database lookup for the user by ID inside the jwt callback if you need more information:

async jwt({ token, user }) {
  if (user) {
    // Fetch the full user object from Convex using user.id
    // and add the fields you need to the token
  }
  return token;
}

This pattern is common with custom adapters and is not specific to Convex. The Convex Adapter for Auth.js Setup Guide and related sources do not mention a way to change this default behavior.

#

Summary:
It is normal for the user object in the jwt callback to be minimal. To access additional fields like email, name, or role, you need to fetch the full user record from your Convex database using the user ID inside the jwt callback. The knowledge sources do not describe a way to automatically include all user fields in the user object at this stage.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh followed the next-auth and next-auth adapter guide, getting "Failed to authenticate: "Could not parse as OIDC ID token. Token might not be an OIDC-compliant JWT.", check your server auth config"

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

The error message:

"Failed to authenticate: 'Could not parse as OIDC ID token. Token might not be an OIDC-compliant JWT.', check your server auth config"

indicates that the JWT being sent to Convex is either not formatted as a valid OIDC ID token or is missing required fields, or that your Convex backend is not configured to accept the token as an OIDC provider.

Based on the knowledge sources, here are the most common causes and solutions:


1. Token Format and Claims

Convex expects an OIDC-compliant JWT, which must include at least the following claims:

  • sub
  • iss
  • aud
  • exp
  • iat (expected by Convex clients for token refreshing)

If your JWT is missing any of these, Convex will reject it. When following the NextAuth adapter guide, ensure your JWT is created with all required claims:

const convexToken = await new SignJWT({
  sub: session.userId,
})
  .setProtectedHeader({ alg: "RS256" })
  .setIssuedAt()
  .setIssuer(CONVEX_SITE_URL)
  .setAudience("convex")
  .setExpirationTime("1h")
  .sign(privateKey);

See: Issue the JWT


2. Convex Auth Config

Your convex/auth.config.ts must be set up to accept tokens with the correct iss (issuer) and aud (audience) fields. For example:

export default {
  providers: [
    {
      domain: process.env.CONVEX_SITE_URL,
      applicationID: "convex",
    },
  ],
};
  • domain must match the iss field in your JWT.
  • applicationID must match the aud field in your JWT.

See: Modify the Convex auth config


3. Expose JWKS and OIDC Endpoints

#

Convex will try to fetch the public key for JWT verification from your deployment’s OIDC endpoints. Make sure you have the following endpoints set up in your Convex http.ts:

  • /.well-known/openid-configuration
  • /.well-known/jwks.json

See: Expose the public key


4. Debugging Steps

  • Use jwt.io to inspect your JWT and confirm it has all required claims and is properly signed.
  • Double-check that the iss and aud fields in the token match your Convex auth config.
  • Review the Convex authentication debugging guide for step-by-step troubleshooting.

Summary:
This error almost always means the JWT is missing required OIDC claims or your Convex auth config does not match the token’s iss and aud fields. Double-check your JWT creation and your Convex auth config to ensure they are aligned as described above. If you continue to have issues, inspect the JWT and config values directly and compare them to the requirements in the guides.

If you need more help, please provide the decoded JWT (with sensitive info redacted) and your auth.config.ts (with secrets removed) for more targeted troubleshooting.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

shadow folio
#

@gritty marsh I seem to be missing the sub field

gritty marshBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

If you are missing the sub field in your JWT, Convex will not accept the token as a valid OIDC ID token. The sub (subject) claim is required and represents the unique identifier for the user. According to the Convex documentation, the JWT must include at least the following claims: sub, iss, aud, exp, and iat Custom JWT Provider.

When issuing the JWT in your NextAuth setup, make sure you explicitly set the sub field. For example, in your auth.ts:

const convexToken = await new SignJWT({
  sub: session.userId, // Make sure this is set!
})
  .setProtectedHeader({ alg: "RS256" })
  .setIssuedAt()
  .setIssuer(CONVEX_SITE_URL)
  .setAudience("convex")
  .setExpirationTime("1h")
  .sign(privateKey);

See: Issue the JWT

If session.userId is undefined, you need to ensure that your session object includes the user ID. Double-check your session and user creation logic to confirm that the user ID is available and correctly passed into the JWT.

Summary:
The sub field is required in your JWT for Convex authentication. Make sure you set it when creating the JWT, as shown in the guide. If you are still missing it, check your session object to ensure it contains the user ID.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@shadow folio Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sullen harbor
shadow folio
#

@sullen harbor I did but I cannot remember what it was
However looking at the diff I can see that I have session.convexToken = convexToken; which they dont have in their example, so maybe it was that

    async session({ session, token }) {
      const privateKey = await importPKCS8(
        process.env.CONVEX_AUTH_PRIVATE_KEY!,
        "RS256"
      );
      const convexToken = await new SignJWT({
        sub: token.id,
      })
        .setProtectedHeader({ alg: "RS256" })
        .setIssuedAt()
        .setIssuer(CONVEX_SITE_URL)
        .setAudience("convex")
        .setExpirationTime("1h")
        .sign(privateKey);
      session.convexToken = convexToken;
      return session;