[GH-ISSUE #5907] FEAT: Privy #10374

Open
opened 2026-04-13 06:28:35 -05:00 by GiteaMirror · 12 comments
Owner

Originally created by @austinm911 on GitHub (Nov 11, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5907

Is this suited for github?

  • Yes, this is suited for github

Is your feature request related to a problem? Please describe.

Privy is a crypto wallet infrastructure stack that was recently acquired by Stripe.
https://www.privy.io/
https://docs.privy.io/api-reference/introduction

Describe the solution you'd like

Integration with their SDK

Describe alternatives you've considered

Not sure what alternatives there are

Additional context

No response

Originally created by @austinm911 on GitHub (Nov 11, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5907 ### Is this suited for github? - [ ] Yes, this is suited for github ### Is your feature request related to a problem? Please describe. Privy is a crypto wallet infrastructure stack that was recently acquired by Stripe. https://www.privy.io/ https://docs.privy.io/api-reference/introduction ### Describe the solution you'd like Integration with their SDK ### Describe alternatives you've considered Not sure what alternatives there are ### Additional context _No response_
GiteaMirror added the oauth label 2026-04-13 06:28:35 -05:00
Author
Owner

@shrkawy commented on GitHub (Nov 12, 2025):

Privy doesn’t offer custom authentication in its Free or Pro plans, only in the Enterprise tier, which is quite expensive.
In my opinion, Web3Auth is definitely a better choice in that regard. I tried integrating Privy in one of my projects but eventually switched to Web3Auth + Better Auth, and they work together seamlessly with the jwt plugin.
The developer experience was much smoother, and the cost was significantly lower.

<!-- gh-comment-id:3522911334 --> @shrkawy commented on GitHub (Nov 12, 2025): `Privy` doesn’t offer custom authentication in its Free or Pro plans, only in the Enterprise tier, which is quite expensive. In my opinion, `Web3Auth` is definitely a better choice in that regard. I tried integrating Privy in one of my projects but eventually switched to `Web3Auth` + `Better Auth`, and they work together seamlessly with the `jwt` plugin. The developer experience was much smoother, and the cost was significantly lower.
Author
Owner

@austinm911 commented on GitHub (Nov 12, 2025):

@shrkawy good to know, I am not very familiar with each, so I appreciate the insight!

<!-- gh-comment-id:3523482778 --> @austinm911 commented on GitHub (Nov 12, 2025): @shrkawy good to know, I am not very familiar with each, so I appreciate the insight!
Author
Owner

@denyncrawford commented on GitHub (Dec 20, 2025):

@shrkawy here is a documentation of how to authenticate using JWT https://docs.privy.io/authentication/user-authentication/jwt-based-auth/overview is this only for Enterprise tier?

<!-- gh-comment-id:3677973871 --> @denyncrawford commented on GitHub (Dec 20, 2025): @shrkawy here is a documentation of how to authenticate using JWT https://docs.privy.io/authentication/user-authentication/jwt-based-auth/overview is this only for Enterprise tier?
Author
Owner

@itxtoledo commented on GitHub (Jan 12, 2026):

The JWT from better-auth doesn't work with web3auth because the generated JWT doesn't have the "iat" attribute, and web3auth looks for this property to validate the token.

<!-- gh-comment-id:3740339825 --> @itxtoledo commented on GitHub (Jan 12, 2026): The JWT from better-auth doesn't work with web3auth because the generated JWT doesn't have the "iat" attribute, and web3auth looks for this property to validate the token.
Author
Owner

@denyncrawford commented on GitHub (Jan 12, 2026):

@itxtoledo i was able to work with privy perfectly, just take in mind that you need at least the scale plan to do it.

<!-- gh-comment-id:3740347796 --> @denyncrawford commented on GitHub (Jan 12, 2026): @itxtoledo i was able to work with privy perfectly, just take in mind that you need at least the scale plan to do it.
Author
Owner

@itxtoledo commented on GitHub (Jan 12, 2026):

Thanks @denyncrawford! Just to clarify, we’re currently using Web3Auth (not Privy) and are on a paid plan. I wanted to share this as an observation because @shrkawy had recommended Web3Auth, but unfortunately it doesn’t work with BetterAuth’s JWT. Also, BetterAuth doesn’t allow modifying the JWT claims, even when using the definePayload property.

<!-- gh-comment-id:3740359472 --> @itxtoledo commented on GitHub (Jan 12, 2026): Thanks @denyncrawford! Just to clarify, we’re currently using Web3Auth (not Privy) and are on a paid plan. I wanted to share this as an observation because @shrkawy had recommended Web3Auth, but unfortunately it doesn’t work with BetterAuth’s JWT. Also, BetterAuth doesn’t allow modifying the JWT claims, even when using the definePayload property.
Author
Owner
<!-- gh-comment-id:3740393234 --> @denyncrawford commented on GitHub (Jan 12, 2026): @itxtoledo what about custom signing? https://www.better-auth.com/docs/plugins/jwt#custom-signing https://docs.metamask.io/embedded-wallets/authentication/custom-connections/custom-jwt/#generate-a-jwt
Author
Owner

@shrkawy commented on GitHub (Jan 13, 2026):

@denyncrawford

@shrkawy here is a documentation of how to authenticate using JWT https://docs.privy.io/authentication/user-authentication/jwt-based-auth/overview is this only for Enterprise tier?

When I tried to integrate it with Privy, it was on the Enterprise plan, but I'm not sure if they've changed that or if it's still part of the Enterprise plan.

<!-- gh-comment-id:3744492973 --> @shrkawy commented on GitHub (Jan 13, 2026): @denyncrawford > [@shrkawy](https://github.com/shrkawy) here is a documentation of how to authenticate using JWT https://docs.privy.io/authentication/user-authentication/jwt-based-auth/overview is this only for Enterprise tier? When I tried to integrate it with Privy, it was on the Enterprise plan, but I'm not sure if they've changed that or if it's still part of the Enterprise plan.
Author
Owner

@shrkawy commented on GitHub (Jan 13, 2026):

@itxtoledo

The JWT from better-auth doesn't work with web3auth because the generated JWT doesn't have the "iat" attribute, and web3auth looks for this property to validate the token.

I already have a working app that uses both Web3Auth and Better Auth with JWT, and everything is running smoothly. If you can share a code snippet, I’ll be happy to help you figure out what’s going wrong.

This is a snippet of the Better Auth Backend JWT plugin config

jwt({
      jwks: {
        keyPairConfig: {
          alg: 'RS256', // Web3Auth prefers RS256
        },
        disablePrivateKeyEncryption: false,
      },
      jwt: {
        issuer: 'my_app_name',
        audience: 'my_app_aud',
        expirationTime: '1h',
        definePayload: ({ user, session }) => {
          const now = Math.floor(Date.now() / 1000);
          return {
            sub: user.id,
            name: user.name || '',
            email: user.email,
            iss: 'my_app_name',
            aud: 'my_app_aud',
            iat: now,
            exp: now + 3600,
            sessionId: session.id,
          };
        },
      },
    })

Frontend

const connectToWeb3Auth = async () => {
    try {
      const jwt = await authClient.token();
      if (!jwt.data?.token) {
        throw new Error("No idToken received from Better Auth");
      }

      await connectTo(WALLET_CONNECTORS.AUTH, {
        authConnection: AUTH_CONNECTION.CUSTOM,
        authConnectionId: "auth_connection_id", // you get this from Web3Auth Dashboard
        idToken: jwt.data.token,
      });
    } catch (err) {
      authClient.signOut();
      const errorMessage =
        err instanceof Error ? err.message : "Error logging into Web3Auth";
      console.error("Error logging into Web3Auth:", err);
    }
  };

I hope that can help

<!-- gh-comment-id:3744505802 --> @shrkawy commented on GitHub (Jan 13, 2026): @itxtoledo > The JWT from better-auth doesn't work with web3auth because the generated JWT doesn't have the "iat" attribute, and web3auth looks for this property to validate the token. I already have a working app that uses both Web3Auth and Better Auth with JWT, and everything is running smoothly. If you can share a code snippet, I’ll be happy to help you figure out what’s going wrong. This is a snippet of the Better Auth Backend JWT plugin config ```ts jwt({ jwks: { keyPairConfig: { alg: 'RS256', // Web3Auth prefers RS256 }, disablePrivateKeyEncryption: false, }, jwt: { issuer: 'my_app_name', audience: 'my_app_aud', expirationTime: '1h', definePayload: ({ user, session }) => { const now = Math.floor(Date.now() / 1000); return { sub: user.id, name: user.name || '', email: user.email, iss: 'my_app_name', aud: 'my_app_aud', iat: now, exp: now + 3600, sessionId: session.id, }; }, }, }) ``` Frontend ```ts const connectToWeb3Auth = async () => { try { const jwt = await authClient.token(); if (!jwt.data?.token) { throw new Error("No idToken received from Better Auth"); } await connectTo(WALLET_CONNECTORS.AUTH, { authConnection: AUTH_CONNECTION.CUSTOM, authConnectionId: "auth_connection_id", // you get this from Web3Auth Dashboard idToken: jwt.data.token, }); } catch (err) { authClient.signOut(); const errorMessage = err instanceof Error ? err.message : "Error logging into Web3Auth"; console.error("Error logging into Web3Auth:", err); } }; ``` I hope that can help
Author
Owner

@itxtoledo commented on GitHub (Jan 13, 2026):

@itxtoledo

The JWT from better-auth doesn't work with web3auth because the generated JWT doesn't have the "iat" attribute, and web3auth looks for this property to validate the token.

I already have a working app that uses both Web3Auth and Better Auth with JWT, and everything is running smoothly. If you can share a code snippet, I’ll be happy to help you figure out what’s going wrong.

This is a snippet of the Better Auth Backend JWT plugin config

jwt({
jwks: {
keyPairConfig: {
alg: 'RS256', // Web3Auth prefers RS256
},
disablePrivateKeyEncryption: false,
},
jwt: {
issuer: 'my_app_name',
audience: 'my_app_aud',
expirationTime: '1h',
definePayload: ({ user, session }) => {
const now = Math.floor(Date.now() / 1000);
return {
sub: user.id,
name: user.name || '',
email: user.email,
iss: 'my_app_name',
aud: 'my_app_aud',
iat: now,
exp: now + 3600,
sessionId: session.id,
};
},
},
})
Frontend

const connectToWeb3Auth = async () => {
try {
const jwt = await authClient.token();
if (!jwt.data?.token) {
throw new Error("No idToken received from Better Auth");
}

  await connectTo(WALLET_CONNECTORS.AUTH, {
    authConnection: AUTH_CONNECTION.CUSTOM,
    authConnectionId: "auth_connection_id", // you get this from Web3Auth Dashboard
    idToken: jwt.data.token,
  });
} catch (err) {
  authClient.signOut();
  const errorMessage =
    err instanceof Error ? err.message : "Error logging into Web3Auth";
  console.error("Error logging into Web3Auth:", err);
}

};
I hope that can help

Which version of better-auth are you using? I'm using "better-auth": "^1.4.7" and any additional properties I add to the token aren't returned in the JWT to the frontend, but I solved it by signing in a custom way as @denyncrawford showed.

<!-- gh-comment-id:3744778513 --> @itxtoledo commented on GitHub (Jan 13, 2026): > [@itxtoledo](https://github.com/itxtoledo) > > > The JWT from better-auth doesn't work with web3auth because the generated JWT doesn't have the "iat" attribute, and web3auth looks for this property to validate the token. > > I already have a working app that uses both Web3Auth and Better Auth with JWT, and everything is running smoothly. If you can share a code snippet, I’ll be happy to help you figure out what’s going wrong. > > This is a snippet of the Better Auth Backend JWT plugin config > > jwt({ > jwks: { > keyPairConfig: { > alg: 'RS256', // Web3Auth prefers RS256 > }, > disablePrivateKeyEncryption: false, > }, > jwt: { > issuer: 'my_app_name', > audience: 'my_app_aud', > expirationTime: '1h', > definePayload: ({ user, session }) => { > const now = Math.floor(Date.now() / 1000); > return { > sub: user.id, > name: user.name || '', > email: user.email, > iss: 'my_app_name', > aud: 'my_app_aud', > iat: now, > exp: now + 3600, > sessionId: session.id, > }; > }, > }, > }) > Frontend > > const connectToWeb3Auth = async () => { > try { > const jwt = await authClient.token(); > if (!jwt.data?.token) { > throw new Error("No idToken received from Better Auth"); > } > > await connectTo(WALLET_CONNECTORS.AUTH, { > authConnection: AUTH_CONNECTION.CUSTOM, > authConnectionId: "auth_connection_id", // you get this from Web3Auth Dashboard > idToken: jwt.data.token, > }); > } catch (err) { > authClient.signOut(); > const errorMessage = > err instanceof Error ? err.message : "Error logging into Web3Auth"; > console.error("Error logging into Web3Auth:", err); > } > }; > I hope that can help Which version of better-auth are you using? I'm using `"better-auth": "^1.4.7"` and any additional properties I add to the token aren't returned in the JWT to the frontend, but I solved it by signing in a custom way as @denyncrawford showed.
Author
Owner

@shrkawy commented on GitHub (Jan 13, 2026):

@itxtoledo I use the latest version, it was working on the previous versions as well.

returned data from /auth/jwks should match this

{
  "keys": [
    {
      "alg": "RS256",
      "kty": "RSA",
      "n": "",
      "e": "",
      "kid": ""
    }
  ]
}

Also, you can verify the additional properties by getting the token from this endpoint auth/token, it should be a normal JWT token

{
  "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6......"
}

Use this website to decode it, and you should have the additional fields in the token
https://www.jwt.io/

In my case, the decoded value matches the defined payload

{
  "iat": 1768316381,
  "sub": "FQ08P7YuDDxAYtLGloHd0uSscBeT6wDJ",
  "name": "Name",
  "email": "email@gmail.com",
  "iss": "ISS_VALUE",
  "aud": "AUD_VALUE",
  "exp": 1768319981,
  "sessionId": "qqOXxs9xrnXaT0xkHXnvVbw2SiGIvhv6"
}
<!-- gh-comment-id:3744886353 --> @shrkawy commented on GitHub (Jan 13, 2026): @itxtoledo I use the latest version, it was working on the previous versions as well. returned data from `/auth/jwks` should match this ```json { "keys": [ { "alg": "RS256", "kty": "RSA", "n": "", "e": "", "kid": "" } ] } ``` Also, you can verify the additional properties by getting the token from this endpoint `auth/token`, it should be a normal JWT token ```json { "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6......" } ``` Use this website to decode it, and you should have the additional fields in the token https://www.jwt.io/ In my case, the decoded value matches the defined payload ```json { "iat": 1768316381, "sub": "FQ08P7YuDDxAYtLGloHd0uSscBeT6wDJ", "name": "Name", "email": "email@gmail.com", "iss": "ISS_VALUE", "aud": "AUD_VALUE", "exp": 1768319981, "sessionId": "qqOXxs9xrnXaT0xkHXnvVbw2SiGIvhv6" } ```
Author
Owner

@denyncrawford commented on GitHub (Jan 13, 2026):

@shrkawy With privy enterprice plan should be very stright forward, this is literally my configuration:


 plugins: [
    expo(),
    jwt({}),
    ....
]

You only need to activate the jwt authentication on the admin pannel of your app and use client side authentication:

JWKS endpoint: https://api.yourapi.com/api/auth/jwks (or ngrok, but i recommend to you just pushing your code to a dev environment it is not going to fail and it is a one time setup)
JWT user ID claim: sub

and that's it.

For client side, create a privy auth provider and request the token of the session using the auth client:

export const PrivyAuthProvider = ({ children }: { children: React.ReactNode }) => {

  const { data: session, isPending } = authClient.useSession();

  const getCustomToken = useCallback(async () => {
    try {
      const { data: tokenData } = await authClient.token();
      const token = tokenData?.token;
      return token;
    } catch (error) {
      // If there's an error, the user is likely not authenticated
      return undefined;
    }

  }, [session, isPending]);
  return (
    <PrivyProvider
      appId="...."
      clientId="client-...."
      config={{
        embedded: {
          ethereum: {
            createOnLogin: "users-without-wallets",
          }
        },
        customAuth: {
          enabled: true,
          // Indicates if your auth provider is currently updating auth state
          isLoading: isPending,
          // Callback to get the user's JWT token
          getCustomAccessToken: getCustomToken,
        }
      }}
    >
      {children}
    </PrivyProvider>
  );
}

this is for a react native example, but pretty sure react client is almost same.

@denyncrawford

@shrkawy here is a documentation of how to authenticate using JWT https://docs.privy.io/authentication/user-authentication/jwt-based-auth/overview is this only for Enterprise tier?

When I tried to integrate it with Privy, it was on the Enterprise plan, but I'm not sure if they've changed that or if it's still part of the Enterprise plan.

<!-- gh-comment-id:3745563311 --> @denyncrawford commented on GitHub (Jan 13, 2026): @shrkawy With privy enterprice plan should be very stright forward, this is literally my configuration: ```ts plugins: [ expo(), jwt({}), .... ] ``` You only need to activate the jwt authentication on the admin pannel of your app and use client side authentication: JWKS endpoint: https://api.yourapi.com/api/auth/jwks (or ngrok, but i recommend to you just pushing your code to a dev environment it is not going to fail and it is a one time setup) JWT user ID claim: sub and that's it. For client side, create a privy auth provider and request the token of the session using the auth client: ```ts export const PrivyAuthProvider = ({ children }: { children: React.ReactNode }) => { const { data: session, isPending } = authClient.useSession(); const getCustomToken = useCallback(async () => { try { const { data: tokenData } = await authClient.token(); const token = tokenData?.token; return token; } catch (error) { // If there's an error, the user is likely not authenticated return undefined; } }, [session, isPending]); return ( <PrivyProvider appId="...." clientId="client-...." config={{ embedded: { ethereum: { createOnLogin: "users-without-wallets", } }, customAuth: { enabled: true, // Indicates if your auth provider is currently updating auth state isLoading: isPending, // Callback to get the user's JWT token getCustomAccessToken: getCustomToken, } }} > {children} </PrivyProvider> ); } ``` this is for a react native example, but pretty sure react client is almost same. > [@denyncrawford](https://github.com/denyncrawford) > > > [@shrkawy](https://github.com/shrkawy) here is a documentation of how to authenticate using JWT https://docs.privy.io/authentication/user-authentication/jwt-based-auth/overview is this only for Enterprise tier? > > When I tried to integrate it with Privy, it was on the Enterprise plan, but I'm not sure if they've changed that or if it's still part of the Enterprise plan.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10374