Closed lee-hodg closed 1 week ago
Any chance you figured anything out here?
For anyone still wondering: you need to have client ID for web listed first before the one for native.
Web always picks the first one, but for native all of them are checked against the aud
field in the id_token
.
@blablacio What do you mean "native all of them are checked against the aud field in the id_token"? We have that issue in our project but the endpoint for web and in our case react-native apps is exactly the same. After signup/login by Apple we are sending request to Django server. And always there is an error of 2 different ID's because react-native is using a different one and web different as well.
I've changed that as you suggested, Web client ID is on first place: com.xxx.login, com.xxx
. But now works only web, and mobile is crashing :/ If I change place of these ID's mobile is working, web is crashing :D
Well, hard to explain why it is like that as it's hard to debug this locally, but it seems to me that the native call to the endpoint goes through get_verified_identity_data
and get_client_id
methods on the AppleOAuth2Adapter
, while web call goes through get_client_id
on the AppleOAuth2Client
.
Don't quote me on that, that's just my gut feeling because get_verified_identity_data
and get_client_id
on the AppleOAuth2Adapter
checks against all the client IDs, while get_client_id
on the AppleOAuth2Client
just takes the first client ID.
My current configuration:
SOCIALACCOUNT_PROVIDERS = {
'apple': {
'APP': {
'client_id': 'com.app.web,com.app.native',
'key': 'key',
'secret': 'secret',
'certificate_key': os.getenv('APPLE_LOGIN_CERTIFICATE')
}
}
}
Hmm that's very interesting. But you have created only one endpoint for both platforms, right? Because I've got same configuration basically, and this is my endpoint view for apple login, and it's not working.
class AppleLoginView(SocialLoginView):
adapter_class = AppleOAuth2Adapter
client_class = AppleOAuth2Client
serializer_class = SocialLoginSerializer
I also have only one endpoint handling both web and native.
Here's the view:
class AppleLoginView(BaseLoginView):
adapter_class = AppleOAuth2Adapter
client_class = AppleOAuth2Client
callback_url = f'https://{settings.APP_DOMAIN}/signup/'
I have some extra stuff on the BaseLoginView
as well as the serializer defined there. I had to define callback_url
though as I was getting errors without it.
Ok, so maybe the case is your custom BaseLoginView
or Serialiser. Because on that point, all is the same, and for me, it's not working.
Not much going on in BaseLoginView
-- just handling some custom data passed. It inherits from SocialLoginView
and overrides post
method:
def post(self, request, *args, **kwargs):
self.serializer = self.get_serializer(data=self.request.data)
self.serializer.is_valid(raise_exception=True)
# Login user
self.login()
return self.get_response()
as opposed to the original SocialLoginView->LoginView:post
:
def post(self, request, *args, **kwargs):
self.request = request
self.serializer = self.get_serializer(data=self.request.data)
self.serializer.is_valid(raise_exception=True)
self.login()
return self.get_response()
By the way, I'm using dj-rest-auth
, maybe that's also something worth mentioning.
Yeah, I've got dj-rest-auth
as well. I don't know what's going on here 😄 I'm definitely missing something.
What is the error you're getting? Might be able to help if you post more details.
[OAuth2Error]
Error retrieving access token: b'{"error":"invalid_grant","error_description":"client_id mismatch. The code was not issued to com.app.test.login."}'
That is the situation where com.app.test.login
is SERVICE_ID
and I want to login via mobile app, which should use com.app.test
ID. Both of ID's looks like this in settings:
com.app.test.login, com.app.test
Web first, mobile second. If I reverse order, then I can login on mobile but not on web.
[OAuth2Error] Error retrieving access token: b'{"error":"invalid_grant","error_description":"client_id mismatch. The code was not issued to com.app.test.login."}'
That is the situation where
com.app.test.login
isSERVICE_ID
and I want to login via mobile app, which should usecom.app.test
ID. Both of ID's looks like this in settings:com.app.test.login, com.app.test
Web first, mobile second. If I reverse order, then I can login on mobile but not on web.
I (am) was facing the exact same issue! This is how I solved it:
I tried to override AppleOAuth2Adapter, but the get_client_id
I override never gets called.
However, if I override the AppleOAuth2Client.get_client_id()
instead, it gets called.
This is how I did it
class CustomAppleOAuth2Client(AppleOAuth2Client):
def get_client_id(self):
mobile_client_id = os.environ.get('APPLE_CLIENT_ID').split(",")[1].strip()
return mobile_client_id
class ApiAppleLoginView(SocialLoginView):
adapter_class = AppleOAuth2Adapter
client_class = CustomAppleOAuth2Client
callback_url = f'{settings.APP_SITE_HOST}/accounts/apple/login/callback/'
This way you can use both client_ids in your env file (or settings), just keep the web before the mobile, like you have already.
Weird, still using the default client and adapter.
In any case, seems like your solution should work @yandiro.
There is a new issue now :man_facepalming:, users that sign in with the mobile app are not recognised by the web app. I think it may be because they are using different client_id
s, since the web app uses a service ID
and the mobile app uses an App ID
. How are you dealing with this, @blablacio ? Would you mind sharing?
Cheers!
@yandiro That's very weird behavior. I have a very similar solution to yours - custom AppleOAuth2Client
and AppleOAuth2Adapter
. Everything is working great now.
class WebAppleOAuth2Client(AppleOAuth2Client):
def get_client_id(self):
return settings.APPLE_CLIENT_ID
class WebAppleOAuth2Adapter(AppleOAuth2Adapter):
def get_client_id(self, provider):
return settings.APPLE_CLIENT_ID
Are you sure that on developer.apple.com in Web Authentication Configuration you have selected the correct Primary App ID
?
Thanks, @brychter-merix!
There is only one issue remaining, when the user is created using the mobile app, the name doesn't seem to come. The mobile team say they are sending the correct scope, though.
So the remaining question is, what does my SocialLoginView Class expect to receive in the request? The mobile app is only sending this to the backend:
{
"code": <authorizationCode returned by Apple>
}
Do they need to send the name too?
On the mobile app, on the request, we are including beside code
- idToken
which is nonce
variable coming from Apple Native response object. But we are using React Native and react-native-apple-authentication
package for that.
@yandiro and @brychter-merix I was thinking that the issue might be the data you supply from native and/or web.
So here's what we're doing:
react-apple-signin-auth
on web and passing code
and id_token
to backend endpointreact-native-apple-authentication
on native and passing access_token
(coming as authorizationCode
from library) and id_token
(coming as identityToken
from library) to backend endpointHope that helps you to figure it out.
Is the latest advice here working stably for everyone?
Please guys... I have a question. Must I open a membership account at apple developer site to obtain client secret and all the necessary IDs to implement apple sign-in in my django api project ? They are asking me to pay the sum of $99 to able to register a developer account. Is there a way I can override this payment ?
@JohnsonMasino https://developer.apple.com/support/compare-memberships/ looks like it's required. Apple is a trash monopolist company sadly.
@aehlke Yes it is ! I made my research and figured I must enroll. Its fine.
For those using dj-rest-auth, you can work around this and configure separate apps, as follows:
# settings.py
SOCIALACCOUNT_PROVIDERS = {
"apple": {
"APPS": [
{"client_id": "client1", ...},
{"client_id": "client2", ...},
]
}
}
APPLE_MOBILE_CLIENT_ID = "client1"
APPLE_WEB_CLIENT_ID = "client2"
# Custom Adapter
from allauth.core import context
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def list_apps(self, request, provider=None, client_id=None):
if not request:
request = context.request
apps = super().list_apps(request=request, provider=provider, client_id=client_id)
if provider == "apple":
if request.path.startswith("/api"):
apps = [app for app in apps if app.client_id == settings.APPLE_MOBILE_CLIENT_ID]
else:
apps = [app for app in apps if not app.client_id == settings.APPLE_WEB_CLIENT_ID]
return apps
As for using allauth headless API, the client_id
is required to be passed to the token endpoint, so it is always clear what app is used, avoiding the need for the above workaround.
By what mechanism is allauth intending to cope with these 2 disparate IDs?
allauth.headless
, the client_id
is passed to the endpoint, removing any ambiguity. list_apps()
workaround above, it becomes ambiguous what app to pick. This is still to be fixed.Closed via 38021aea
For the Apple SSO, there are 2 Client IDs, a Bundle ID and a Services ID. When the flow is started from a mobile iOS device the app would use the Bundle ID as the Client ID, but when it is started from a web app such as react the authorization flow would be started with the Service ID as the Client ID.
How does the django-allauth Apple provider handle these 2 different Client ID?
There are some comments in the PR thread https://github.com/pennersr/django-allauth/pull/2424 that suggest setting up
SOCIALPROVIDERS
using a comma-delimited string of the 2 client IDs, likeClient id = <APPLE_SERVICE_ID>, <APPLE_APP_ID>
(https://github.com/pennersr/django-allauth/pull/2424#issuecomment-670597679) however looking atallauth/socialaccount/providers/apple/client.py
shows that always the first client ID in the string would be used anywayThis means if the Service ID is the first in the settings, then it would ALWAYS be the one used.
If iOS initiated the auth flow therefore with the Bundle ID as the client ID, then the backend (allauth) tries to use the Service ID, it will fail with
invalid_id
because of the mismatch. If however the Bundle ID was first in the settings string, then things would work for iOS, but would fail for flows started by web (react) because web would use the Service ID, but backend would use the Bundle ID.By what mechanism is allauth intending to cope with these 2 disparate IDs?
Maybe the identity token from the auth response should be used to derive the
sub
(subject claim) so it always matches the requesting client id, rather than just taking the first client id in the settings list?