quavedev / apple-oauth

Sign in with Apple OAuth2 handler for Meteor.
MIT License
3 stars 15 forks source link

Question: How to use multiple native client IDs in practice #26

Open derwaldgeist opened 4 weeks ago

derwaldgeist commented 4 weeks ago

We have a white-label scenario where multiple different iOS apps have to connect to the same server backend. In your docs, you say: "We don't support multiple native ids yet.", but then provide a section that describes exactly that. So I assume that this feature was added later.

However, the section about multiple IDs does not state how the setup works in practice, i.e. how the sever decides which of the appIds will be used. Is this being done just by inspecting the token and trying to match the "audience" id to one of the nativeClientIds to find the right app (and then the secret based off the associated appId), or does the client have to send the "appId" when authenticating?

Also, I am wondering in which version this feature has been added. I had forked my production version from 1.4.2, but I am not using Meteor 3 yet. So I am wondering which version I have to upgrade to, to be compatible with Meteor 2.6.1 and use this white-label feature, if that's possible.

Thanks again for your great work!

renanccastro commented 4 weeks ago

Hi, @derwaldgeist. Yes, that was added afterward and the readme wasn't entirely updated, sorry. I will revisit this documentation over the weekend and update you here again. It's not mandatory to run with Meteor 3, and even the latest version has its code compatible with Meteor 2.x(4.0.0)

derwaldgeist commented 4 weeks ago

Thanks for the superfast response, highly appreciated!

How exactly does this new mechanism work? Is the "appId" just linking the two entries (secret and apps), or does it have another "deeper" meaning? (If you plan to describe this in the updated docs, that's also fine for me.)

Besides: I was trying to update my fork today, but I am struggling with one thing: The signature of the verifyAndParseIdentityToken() and getServiceDataFromTokens() methods has changed to include a "query" now. I used to call into these methods directly, to verify my tokens before logging in (and also for handling Apple's calls when the user removes Apple signin for the app). For this, I exposed these functions as a public API.

Do you happen to know what these new "query" parameters are, and how I can construct them from scratch or by calling some Meteor (internal) method? My current client only sends the token, as this is received from Apple when logging in.

(I am logging in with a Unity-based native client, not with Cordova, and also with a native Swift client.)

renanccastro commented 4 weeks ago

https://github.com/quavedev/apple-oauth/commit/768b7d8f93721ebd7dab4ec05b645f4a49d87347 This was added in this commit and it's this query param here.

It's needed for identifying which service id we are talking about in the login process.

derwaldgeist commented 4 weeks ago

Thanks. Where does this query come from, Meteor's own login mechanism?

I will try to send a "fake" query with { method: 'native-apple' } then, but I also saw that the new code uses query.email at some point, and it also tries to get some "state" from that query here:

https://github.com/quavedev/apple-oauth/blob/08a58eca443a56febe7857c00f2f9c0fa482c123/apple_server.js#L32

So I am wondering what this is about and what parameters this query needs to work.

derwaldgeist commented 4 weeks ago

Also, I tried to setup the config for the multi-app-id now, but somehow it does not work. If I do it as described in the docs, it seems as if it does not find the token at all. So I am awaiting your docs update, maybe it gets clearer then.

renanccastro commented 3 weeks ago

I still haven't had the time to update the docs. Sorry. But I managed to revisit the application I'm using the shards. The idea is that when I'm creating a new "client" I assign a new shard in my database and add the configuration in the settings - this is controlled manually by the application. When logging in, I do the following:

  Meteor.loginWithApple({
    requestPermissions: ['name', 'email'],
    ...getUrlData({ location }),
    shard: isWhiteLabel() && getMode().appleShard,
  });

The important part is shard, used when logging in to the web.

If you want to use multiple apps, you have to pass the appId bit which is your native app ID in the options, like:

  Meteor.loginWithApple({
    requestPermissions: ['name', 'email'],
    appId: "com.quave2"
  });

So this will use the corresponding 'appId' section in the services.apps array

renanccastro commented 3 weeks ago

Also, this is the "state" you are referring to, it's a URL param we pass that apple gives back to us in the callback: https://github.com/quavedev/apple-oauth/blob/master/apple_client.js#L66

derwaldgeist commented 3 weeks ago

Thanks for sharing these additional insights.

shard: isWhiteLabel() && getMode().appleShard,

I'm not sure if I get this right: according to the docs, the shard selects between different "clientIds". As far as I understand, these clientIds actually are the same as Apple's "Service IDs". These service IDs define the domains and the return URIs. What I do not understand is why you would need multiple of these IDs, given that a) you can define multiple domains and multiple return URLs for one service ID, b) your config format only allows one return URL per nativeClientId. It would make sense to me, if one could define multiple return URLs as well.

derwaldgeist commented 3 weeks ago

appId: "com.quave2"

Ok, so that's the missing part I was looking for. Now the appIds make sense.

Unfortunately, I still can't test it out because of (the second issue mentioned in) #27.

derwaldgeist commented 3 weeks ago

Also, this is the "state" you are referring to

Ok, so this is basically some data that is handed over to the Apple API and later transferred back to the app (transparently), correct? So, if I want to mimick this in the mobile app call, I would have to re-construct this data somehow. Will try to do this, but in this case the login is not initiated by Meteor, but by some native "SignInWithApple" code. Will look how far I can come with this.

renanccastro commented 3 weeks ago

Yeah, that's only possible in the web call. On mobile the handoff is different.

a) you can define multiple domains and multiple return URLs for one service ID, b) your config format only allows one return URL per nativeClientId. It would make sense to me, if one could define multiple return URLs as well.

Our use case was that we had a LOT of different domains, one per client, so there is a limit of only 10 redirect uris that apple let you add in the service id. That's why we needed more than one, and that 's why we are sharding. It's possible to override the redirect URI when calling the login too: https://github.com/quavedev/apple-oauth/blob/08a58eca443a56febe7857c00f2f9c0fa482c123/apple_server.js#L161. the definition there is only a "default" redirect.

For meteor 2.x you can use the version: '3.1.1'.

derwaldgeist commented 3 weeks ago

Sooo, I was able to use the 3.1.1 version and log-in using the "multi-app" configuration, at least using the web frontend. I had to add this config to AccountsReact to make it possible:

AccountsReact.configure({
  ...,
  oauth: {
    apple: {
      appId: '<appId>'
    }
  }
});
derwaldgeist commented 3 weeks ago

For the native login, I learned that the library does not seem to support this scenario yet. To make native logins work, I used to call getServiceDataFromTokens() directly, since I had found no other way to do it. This method calls verifyAndParseIdentityToken(), yet this method is not looking for the nativeClientId in other places than Apple.config.nativeClientId (yet):

https://github.com/quavedev/apple-oauth/blob/08a58eca443a56febe7857c00f2f9c0fa482c123/apple_server.js#L35

I was able to workaround this by patching this line to use the already existing getServiceConfiguration() in utils.js

https://github.com/quavedev/apple-oauth/blob/08a58eca443a56febe7857c00f2f9c0fa482c123/utils.js#L16

in case "isNative" is set to true:

const clientId = isNative
      ? getServiceConfiguration({ appId: query.appId }).nativeClientId
      : getClientIdFromOptions(state, Apple.config);

When calling, I set the query.appId accordingly, like this:

const loginData = Apple.getServiceDataFromTokens({
        query: { appId: 'marble' },
        tokens,
        isNative: true,
        isBeingCalledFromLoginHandler: true
      });

This worked!

Maybe you'd like to change this line in your library, too:

https://github.com/quavedev/apple-oauth/blob/08a58eca443a56febe7857c00f2f9c0fa482c123/apple_server.js#L35

BTW: I also allow my users to link an existing account (e.g. created using email and password) to Sign In With Apple. To make this work, I call verifyAndParseIdentityToken() directly and then Accounts.LinkUserFromExternalService(). This is the reason why I had exposed both getServiceDataFromTokens() and verifyAndParseIdentityToken() in my fork, by adding them to the global Apple object.

If there's a better way to support native logins, please let me know.

In case you're interested, you can find my fork here:

https://github.com/marblear/apple-oauth/blob/merge/quakedev-master-3.1.1/apple_server.js

It also includes a couple of minor patches.