#Convex Auth Credentials Provider signInViaProvider not taking additional args.

30 messages · Page 1 of 1 (latest)

dense tangle
#

Hi, I am using convex-auth credentials provider
This is my schema

const schema = defineSchema({
  ...authTables,
  users: defineTable({
    name: v.optional(v.string()),
    image: v.optional(v.string()),
    email: v.optional(v.string()),
    emailVerificationTime: v.optional(v.number()),
    username: v.string(),
  })
    .index("email", ["email"])
    .index("username", ["username"])
    .index("email_and_username", ["email", "username"]),
});

export default schema;

This is where I am trying to pass username and name to ResendOTP Provider

      const authRes = await signInViaProvider<DataModel>(ctx, ResendOTP, {
        accountId: account._id,
        params: {
          code: data.code, // Works
          email: user.email, // Works
          username: user.username, // Not Passing to sendVerificationRequest
          name: user.name, // Not Passing to sendVerificationRequest
        },
      });
plucky sirenBOT
#

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

#

Based on the knowledge sources, it appears that the issue you're experiencing is a known limitation in Convex Auth. The params object you're passing to signInViaProvider with additional fields like username and name is not being forwarded to the sendVerificationRequest function in your ResendOTP provider.

This limitation is specifically mentioned in one of the Discord support threads:

As far as I can tell, this simply isn't supported. Here's where the params object would be passed in if it were: https://github.com/get-convex/convex-auth/blob/2f77702b0e42fa705dfe2af623494682e6a21b49/src/server/implementation/signIn.ts#L151-L169
Discord support thread: Send params from signIn to sendVerificationRequest using ResendOTP

The Convex team member further explained:

The main issue here is Convex Auth defers a lot to Auth.js, to the point of utilizing types and type docs linking directly to that library. This parity is a good thing for users, so I don't know if diverging from it in an Auth.js method like sendVerificationRequest() is very palatable.

A workaround suggested by the Convex team is to create separate providers for different use cases:

Hmm maybe you can register a separate provider per language? Obviously reusing pretty much everything except the email template. But you would have distinct ids for each provider eg., resend-otp-en and resend-otp-es, and whatever others. Then you can pick the provider you call straight from the client based on language.

This approach was confirmed to work by another user in the same thread:

Having different providers is a good option to move forward with this. I've already tried it and it works perfectly.

#

So if you need to pass different parameters to customize your verification emails, the recommended approach is to create separate provider instances with different IDs, each configured for your specific use case.

I hope this helps with your implementation!

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

#

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

dense tangle
#
import { Email } from "@convex-dev/auth/providers/Email";
import { alphabet, generateRandomString } from "oslo/crypto";
import type { DataModel } from "~/convex/_generated/dataModel";

export const ResendOTP = Email<DataModel>({
  id: "resend-otp",
  apiKey: process.env.AUTH_RESEND_KEY,
  maxAge: 60 * 15, // 15 minutes
  generateVerificationToken() {
    return generateRandomString(6, alphabet("0-9"));
  },
  async sendVerificationRequest(params) {
    console.log(params);
    // TODO: implement resend
  },
});

This is my ResendOTP provider

#

I am using this for 2FA

#
authorize: async (params, ctx) => {
    const parsedParams = ProviderSchema.safeParse(params);
    if (!parsedParams.success) {
      throw new ConvexError("Invalid Credentials");
    }
    const data = parsedParams.data;
    if (data.flow === "sign-up") {
      if (data.password !== data.confirmPassword) {
        throw new ConvexError("Password and Confirm Password do not match");
      }
      const res = await createAccount<DataModel>(ctx, {
        provider,
        account: {
          id: data.username,
          secret: data.password,
        },
        profile: {
          email: data.email,
          username: data.username,
          name: data.name,
        },
        shouldLinkViaEmail: true,
        shouldLinkViaPhone: false,
      });
      return { userId: res.user._id };
    } else if (data.flow === "send-otp") {
      const res = await retrieveAccount<DataModel>(ctx, {
        provider,
        account: { id: data.username, secret: data.password },
      });
      const { account, user } = res;
      return await signInViaProvider<DataModel>(ctx, ResendOTP, {
        accountId: account._id,
        params: {
          email: user.username, // Works but IDK why
        },
      });
    } else if (data.flow === "sign-in") {
      const res = await retrieveAccount<DataModel>(ctx, {
        provider,
        account: { id: data.username, secret: data.password },
      });
      const { account, user } = res;
      const authRes = await signInViaProvider<DataModel>(ctx, ResendOTP, {
        accountId: account._id,
        params: {
          code: data.code, // Works
          email: user.email, // Works
          username: user.username, // Not Passing to sendVerificationRequest
          name: user.name, // Not Passing to sendVerificationRequest
        },
      });
      return authRes;
    }
    return null;
  },

This is my authorize function

#

@plucky siren can you tell me how can I do 2FA?

plucky sirenBOT
#

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

#

Based on the knowledge sources, there are a few approaches to implementing two-factor authentication (2FA) with Convex:

Using Third-Party Authentication Platforms

The most comprehensive way to implement 2FA with Convex is to use a third-party authentication platform that Convex integrates with:

  1. Clerk - Provides multi-factor authentication capabilities and is recommended for better Next.js and React Native support
    Authentication

  2. Auth0 - A more established platform with "more bells and whistles" including 2FA options
    Authentication

Using Convex Auth with Custom Implementation

If you prefer to implement 2FA directly within Convex, you can use Convex Auth (which is in beta) with a custom implementation:

  1. You could use the ConvexCredentials provider to handle custom authentication flows including 2FA:

    const config = ConvexCredentials({
      // Your custom 2FA implementation
    });
    

    ConvexCredentials()

  2. For SMS-based 2FA, you could implement a solution using Twilio as shown in the example repo:
    Phone providers

One Discord thread mentions a user implementing a custom JWT-based authentication system with WhatsApp Auth and OTP verification using Twilio, which could be adapted for 2FA:

"Right now, I've got the OTP flow working (using Twilio), and I'm able to generate JWTs after OTP validation."

The Convex team member suggested looking at their examples:

"I suggest looking at our ResendOTP example + potentially the Twilio OTP example"

#

Note that Convex Auth is currently in beta, so for a production application requiring robust 2FA, using a third-party authentication platform like Clerk or Auth0 would likely be more reliable and feature-complete.

Would you like more specific details on implementing any of these approaches?

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

#

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

dense tangle
#

@plucky siren I am also getting error Uncaught (in promise) TypeError: Cannot read properties of null (reading 'redirect')
at AuthProvider.useCallback[signIn] (client.tsx:203:18)
at async onSubmit (SignUpForm.tsx:44:5)
When I try to create user that already exists

 const user = await retrieveAccount<DataModel>(ctx, {
        provider,
        account: { id: data.username },
});
if (user) {
        throw new ConvexError("User already exists");
}

shouldn't error be User already exists?

plucky sirenBOT
#

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

#

Based on the knowledge sources, the error you're experiencing is a known issue with Convex Auth. When you throw a ConvexError in your authentication flow, it's not properly propagating back to the client.

Several users have reported similar issues in Discord support threads:

"When I am throwing ConvexError within the createOrUpdate callback, the error don't propagate back to the user." Catching error when creating a user

The error message you're seeing (Cannot read properties of null (reading 'redirect')) is a common symptom when authentication errors occur but aren't properly handled:

"For example, the error on client side when entering wrong user/pass is 'Cannot read properties of null (reading 'redirect')'" User feedback on signIn/signUp

Workaround Solution

A Convex team member suggested using ConvexError for errors that should make it back to the client, but it appears this doesn't work properly in authentication callbacks.

One community member shared a workaround by overriding the default signIn action:

export const { auth, signIn: authSignIn, signOut, store, isAuthenticated } = convexAuth({ ... })

export const signIn = actionGeneric({
    args: v.any(),
    async handler(ctx, args) {
        try {
            return await ctx.runAction(api.auth.authSignIn, args);
        } catch (err) {
            if (err instanceof ConvexError) {
                return { redirect: `${process.env.PUBLIC_URL}/signin?error=${encodeURI(err.data ?? "Convex error")}` }
            }
            return { redirect: `${process.env.PUBLIC_URL}/signin?error=${encodeURI("Something went wrong")}` }
        }
    }
})
#

This approach allows you to catch errors and handle them by redirecting with an error parameter that you can then display in your UI.

The Convex team acknowledged this is a good approach and mentioned it should be documented better. There are open issues related to this problem: #165 and #124.

So to answer your question directly: Yes, the error should be "User already exists", but due to how errors are currently handled in Convex Auth, it's not propagating correctly to the client.

I recommend implementing the workaround above or filing an issue on the Convex Auth GitHub repository.

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

#

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

dense tangle
#

@plucky siren is there a way to pass custom params to ResendOTP by custom implementation?

plucky sirenBOT
#

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

#

Based on the knowledge sources, there are a few approaches to pass custom parameters to ResendOTP for email verification in Convex Auth.

Current Limitation

The knowledge sources indicate that passing custom parameters directly from signIn to sendVerificationRequest is not currently supported in Convex Auth:

"As far as I can tell, this simply isn't supported. Here's where the params object would be passed in if it were: https://github.com/get-convex/convex-auth/blob/2f77702b0e42fa705dfe2af623494682e6a21b49/src/server/implementation/signIn.ts#L151-L169"
Discord support thread: Send params from signIn to sendVerificationRequest using ResendOTP

Workaround Options

1. Create Multiple Providers

The most recommended approach is to create separate providers for different use cases:

// For English emails
export const ResendOTPEnglish = Email({
  id: "resend-otp-en",
  // Configuration for English emails
});

// For Spanish emails
export const ResendOTPSpanish = Email({
  id: "resend-otp-es",
  // Configuration for Spanish emails
});

Then register both in your auth configuration:

export const { auth, signIn, signOut, store } = convexAuth({
  providers: [ResendOTPEnglish, ResendOTPSpanish],
});

This approach was confirmed to work:

"Having different providers is a good option to move forward with this. I've already tried it and it works perfectly."
Discord support thread: Send params from signIn to sendVerificationRequest using ResendOTP

2. Use Callbacks

Another approach is to use the createOrUpdateUser callback to handle custom user data:

#
callbacks: {
  createOrUpdateUser: async (ctx, args) => {
    const params = args.profile;
    // Custom logic here
  }
}

However, note that this callback is for user creation/updating and not directly for customizing the OTP email content.

3. Feature Request

This limitation has been acknowledged by the Convex team, and there's a suggestion to open a feature request:

"Welcome! Makes sense. I would open this feature request as an issue on the open source repo, that way anyone can take a shot at implementing if they like: https://github.com/get-convex/convex-auth/issues"
Discord support thread: Additional parameters in Email config

For now, the multiple provider approach is your best option for customizing email content based on parameters like language or other user preferences.

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

#

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

dense tangle
#

@plucky siren No, I am not working with Languages. I am working with usernames so names

plucky sirenBOT
#

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

dense tangle
#

anything?

dense tangle
#

@plucky siren how can i customise auth:store functions?

plucky sirenBOT
#

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

#

Based on the knowledge sources, I can provide information about customizing the auth:store function in Convex Auth.

Understanding auth:store

The auth:store function is an internal mutation used by the Convex Auth library to read and write to the database during signin and signout processes. It's one of the components returned by the convexAuth() configuration function:

export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
  providers: [],
});

Customizing the Schema

One way to customize how auth data is stored is by modifying the schema for the users table:

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

const schema = defineSchema({
  ...authTables,
  users: defineTable({
    name: v.optional(v.string()),
    image: v.optional(v.string()),
    email: v.optional(v.string()),
    emailVerificationTime: v.optional(v.number()),
    // Add your custom fields here
    username: v.optional(v.string()),
  }).index("email", ["email"]),
});

export default schema;

This approach is documented in Customizing Schema.

Taking Complete Control

For more advanced customization, you can implement the createOrUpdateUser callback to take complete control over user creation and updates:

export const { auth, signIn, signOut, store } = convexAuth({
  providers: [...],
  callbacks: {
    createOrUpdateUser: async (ctx, args) => {
      // Custom logic for creating or updating users
      // Return the user ID
    }
  }
});

This allows you to completely control how user data is stored and updated during authentication processes.

Error Handling

#

If you're experiencing issues with error handling in the auth store functions, you might need to create a custom wrapper around the default signIn action:

export const { auth, signIn: authSignIn, signOut, store } = convexAuth({ ... })

export const signIn = actionGeneric({
    args: v.any(),
    async handler(ctx, args) {
        try {
            return await ctx.runAction(api.auth.authSignIn, args);
        } catch (err) {
            // Custom error handling
            if (err instanceof ConvexError) {
                return { redirect: `${process.env.PUBLIC_URL}/signin?error=${encodeURI(err.data ?? "Convex error")}` }
            }
            return { redirect: `${process.env.PUBLIC_URL}/signin?error=${encodeURI("Something went wrong")}` }
        }
    }
})

This approach allows you to catch and handle errors from the authentication process in a more customized way.

Custom Functions Approach

For more comprehensive customization, you can use the custom functions approach from the convex-helpers package:

import { customQuery, customCtx } from "convex-helpers/server/customFunctions";

// Create custom versions of Convex functions with additional behavior
const authQuery = customQuery(
  query,
  customCtx(async (ctx) => {
    // Custom authentication logic
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("Authentication required");
    }
    // Return modified context
    return { user: identity };
  })
);

This approach is documented in Customizing serverless functions without middleware.

Would you like more specific details on any of these customization approaches?

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