next-auth icon indicating copy to clipboard operation
next-auth copied to clipboard

feat(providers): return `user` object in Profile of Apple provider

Open vinhnguyen1211 opened this issue 3 years ago • 6 comments

☕️ Reasoning

In Apple callback, there's user object but it's not able to retrieve it using Apple provider image However, this user object only serve for initial request, subsequent request will not get this from Apple

🧢 Checklist

  • [ ] Documentation
  • [X] Tests
  • [X] Ready to be merged

🎫 Affected issues

Not found issue related yet

📌 Resources

vinhnguyen1211 avatar May 18 '22 11:05 vinhnguyen1211

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Ignored Deployment
Name Status Preview Updated
next-auth ⬜️ Ignored (Inspect) May 18, 2022 at 11:30AM (UTC)

vercel[bot] avatar May 18 '22 11:05 vercel[bot]

I'm very hesitant to add provider specific code to the core.

A workaround is using advanced initialization https://next-auth.js.org/configuration/initialization#advanced-initialization

balazsorban44 avatar May 19 '22 09:05 balazsorban44

I'm very hesitant to add provider specific code to the core.

A workaround is using advanced initialization next-auth.js.org/configuration/initialization#advanced-initialization

Thanks for your reference I find that advanced initialization could be workaround for this case but it will lack of generic handler in callbacks: { jwt, session } which share the same flow of other providers.

Do you think is there any other way to achieve this without putting specific code of a provider? Like another option in Apple provider somehow could take & process the callback. It would be great if we have this user object in OAuthProfile.

vinhnguyen1211 avatar May 19 '22 10:05 vinhnguyen1211

This was the case before, but we moved away from it on purpose. Our core should not care about specific providers if they implement the spec correctly. This is unfortunately on Apple. :shrug:

What I am willing to do is to pass the req here (or rather a subset to begin with, like {body, query}):

https://github.com/nextauthjs/next-auth/blob/ba6d7df3bf38bdcc5b342229babf46e45be2523e/packages/next-auth/src/core/lib/oauth/callback.ts#L125-L132

So a custom userinfo.request method could be added to Apple's configuration.

balazsorban44 avatar May 31 '22 16:05 balazsorban44

Thanks for your suggestion! Regardless of using userinfo.request, isn't it will not execute else if (provider.idToken) which is Apple in this case? https://github.com/nextauthjs/next-auth/blob/ba6d7df3bf38bdcc5b342229babf46e45be2523e/packages/next-auth/src/core/lib/oauth/callback.ts#L125-L133 https://github.com/nextauthjs/next-auth/blob/ba6d7df3bf38bdcc5b342229babf46e45be2523e/packages/next-auth/src/providers/apple.ts#L102-L110 Sorry, I'm confusing to the hint.

vinhnguyen1211 avatar Jun 01 '22 08:06 vinhnguyen1211

@vinhnguyen1211 Hi, if userinfo.request is defined, it will take priority over the else if (provider.idToken) block So I think what @balazsorban44 is suggesting is something like:

if (provider.userinfo?.request) {
      // @ts-expect-error
      profile = await provider.userinfo.request({
        provider,
        tokens,
        client,
        requestInfo: { body, query },
      })
    } else if (provider.idToken) {

And in apple.ts you could define the method like:

userinfo: {
      request({ provider, tokens, requestInfo: { body } }) {
        const profile = tokens.claims()
        if (body?.user) {
        try {
          profile.user = typeof body.user === 'string' ? JSON.parse(body.user) : body.user

          return profile
        } catch (error) {
          profile.user = body.user
          logger.debug("ERR_PARSING_BODY_USER_OBJECT_APPLE", {
            error: error as Error,
            providerId: provider.id,
          })
        } 
      }
      },
    },

ThangHuuVu avatar Sep 03 '22 02:09 ThangHuuVu

Thanks @ThangHuuVu that solved it for me!

I just added an else to the if statement for when users log in a second time. On second login user is omitted from body.

  userinfo: {
    request({ provider, tokens, requestInfo: { body } }) {
      const profile = tokens.claims()
      if (body?.user) {
      try {
        profile.user = typeof body.user === 'string' ? JSON.parse(body.user) : body.user

        return profile
      } catch (error) {
        profile.user = body.user
        logger.debug("ERR_PARSING_BODY_USER_OBJECT_APPLE", {
           error: error as Error,
           providerId: provider.id,
        })
      } 
    } else {
      return profile
    }
    },
  },  

Digital-E avatar Nov 08 '22 14:11 Digital-E

Hi @Digital-E, Could you please share more details on how you fixed it? Did you make changes as suggested, in advanced initialisation? Can you please share the code you added?


export default async function auth(req: NextApiRequest, res: NextApiResponse) {
 // What did you add here, if anything?
 return await NextAuth(req, res, {
   ...
  // I recon the above snippet was added within the apple provider config object?
 })

Appreciate and thanks in advance. :+1:

JosephJustus avatar Nov 22 '22 05:11 JosephJustus

Hello @balazsorban44,

Do you think you could add the requestInfo: { body, query }, parameter in the request so we can resolve this issue (maybe fix directly on the apple provider).

We use nextauth to register/login in our mobile apps and we can't publish anymore because Apple does not validate our latest update since we can't fill the user name

bmichotte avatar Jan 06 '23 09:01 bmichotte

Until this PR is merged i solved it by doing this:

src/app/api/auth/[...nextauth]/route.ts

const authOptionsFunc = (userObject) => ({
  ...,
  providers: [
    AppleProvider({
      clientId: APPLE_ID,
      clientSecret: APPLE_SECRET,
      profile(profile) {
        if (userObject) {
          profile.name = `${userObject.name.firstName} ${userObject.name.lastName}`;
        }

        return {
          id: profile.sub,
          name: profile.name,
          email: profile.email,
          image: null,
        };
      },
    }),
  ],
});

export const authOptions = authOptionsFunc();

async function auth(req: NextApiRequest, res: NextApiResponse) {
  let userObject;

  if (req?.url?.includes("callback/apple") && req?.method === "POST") {
    const formData = await req.clone().formData();
    const user = formData.get("user");
    userObject = JSON.parse(user);
  }

  return await NextAuth(req, res, authOptionsFunc(userObject));
}

export { auth as GET, auth as POST };

d0ri0 avatar Jun 24 '23 11:06 d0ri0

Closing this PR, there are workarounds in the user land and we don't want to add provider-specific handling in the core, as @balazsorban44 mentioned in https://github.com/nextauthjs/next-auth/pull/4579#issuecomment-1131473311

ThangHuuVu avatar Jul 10 '23 16:07 ThangHuuVu