Open britannio opened 4 years ago
I'm using Firebase Auth not a custom server.
Hey,
the above is only needed when using a custom server, which is meant to mean that you should copy over the POST body (that Apple posts to your server) to that query parameter.
For Firebase we don't have documentation, but anecdotally we know that it works 😉
It depends on the type of integration you want to do, but maybe one of the earlier issues around this helps get you started: https://github.com/aboutyou/dart_packages/issues?q=is%3Aissue+firebase
We'd definitely be grateful for a write-up on how to integrate this with Firebase.
I will close this for now. But feel free to reopen if we can help you any further.
The flow should be similar, I expect to use SignInWithApple.getAppleIDCredential
to get a AuthorizationCredentialAppleID
containing a crucial IdToken
which I then supply to Firebase.
FirebaseAuth.instance.signInWithCredential(
PlatformOAuthCredential(
providerId: 'apple.com',
idToken: credential.identityToken,
rawNonce: nonce,
),
);
Inspired by this snippet. https://github.com/FirebaseExtended/flutterfire/blob/master/packages/firebase_auth/firebase_auth_platform_interface/lib/src/types.dart#L162
On that note, should the AuthorizationCredentialAppleID
also contain an accessToken?
@britannio I think the accessToken
is named authorizationCode
in sign in with apple
@britannio I think the
accessToken
is namedauthorizationCode
in sign in with apple
ah, thanks!
@tp Can we keep this open until a complete solution is found? Firebase provides a call-back URL but I think it's for web purposes and besides that, only the url scheme we define will open the app.
@britannio Sure, seems like this is going somewhere :) Then let's use this to get a codesample documenting the flow with Firebase.
@britannio but what's the hold up now? I think the integration with firebase auth should just work as described in the Firebase Auth iOS Apple docs (also on Android it should).
You get the credentials from this plugin and then just give them to firebase so it can create the user in the firebase backend. That should also work for Android, you just need this small server part to redirect back into the app on android.
@HenriBeck Not knowing what to provide to the redirectUri
of WebAuthenticationOptions
.
It's of this form:
intent://callback?${PARAMETERS FROM CALLBACK BODY}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end
but I'm unaware of what I should replace ${PARAMETERS FROM CALLBACK BODY}
with
@britannio The redirectUri
shouldn't be intent://callback?${PARAMETERS FROM CALLBACK BODY}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end
.
It should be the URL to small server that is needed for android, in the example in the readme it is https://flutter-sign-in-with-apple-example.glitch.me/callbacks/sign_in_with_apple
.
This intent://
URL is defined on the server here in line 25 of server.js
@HenriBeck So is the redirectUri
expected to accept requests from Apple's servers and redirect them to the intent://
url where they're intercepted by the app?
yes exactly, so when you call getAppleIDCredential
we redirect to the apple website and send along the redirectUri
.
Once the user has finished authenticating, Apple will redirect to the redirectUri
with a POST request. Note that the redirectUri
needs to be an https
URI, that's why we can't simply directly link into the app.
Then on the server part, the user data is received, and you are being redirected to the intent://
URI which in turn is intercepted by the app. The app then returns the data which was in the intent://
URI
Hello. I was checking out your project and it seems to work with firebase auth. This is my example code for someone who needs this for future reference
Future<FirebaseUser> signInWithApple() async {
var redirectURL = "https://SERVER_AS_PER_THE_DOCS.glitch.me/callbacks/sign_in_with_apple";
var clientID = "AS_PER_THE_DOCS";
final appleIdCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
webAuthenticationOptions: WebAuthenticationOptions(
clientId: clientID,
redirectUri: Uri.parse(
redirectURL)));
final oAuthProvider = OAuthProvider(providerId: 'apple.com');
final credential = oAuthProvider.getCredential(
idToken: appleIdCredential.identityToken,
accessToken: appleIdCredential.authorizationCode,
);
final authResult =
await SignInUtil.firebaseAuth.signInWithCredential(credential);
return authResult.user;
}
As a note I got it working with iOS and Android through firebase.
@DavidCorrado That is great to hear. I will mark this ticket then as documentation
so we can write some document where the full integration is explained.
Hello. I was checking out your project and it seems to work with firebase auth. This is my example code for someone who needs this for future reference
Future<FirebaseUser> signInWithApple() async { var redirectURL = "https://SERVER_AS_PER_THE_DOCS.glitch.me/callbacks/sign_in_with_apple"; var clientID = "AS_PER_THE_DOCS"; final appleIdCredential = await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, ], webAuthenticationOptions: WebAuthenticationOptions( clientId: clientID, redirectUri: Uri.parse( redirectURL))); final oAuthProvider = OAuthProvider(providerId: 'apple.com'); final credential = oAuthProvider.getCredential( idToken: appleIdCredential.identityToken, accessToken: appleIdCredential.authorizationCode, ); final authResult = await SignInUtil.firebaseAuth.signInWithCredential(credential); return authResult.user; }
As a note I got it working with iOS and Android through firebase.
Hello, could you please specify how you did manage to authenticate via Firebase on Android?
The issue here is that the redirect URI provided by Firebase apparently cannot be extended with the "intent://..." part required by Android; however, without the "intent://..." part, the authentication tab remains open on Android, thus stalling the authentication flow.
Note that on iOS, where no "intent://..." is required, authentication via Firebase appears to work perfectly every time.
Thank you, Dario
@adario you will need this small server as described in the README here: https://github.com/aboutyou/dart_packages/tree/master/packages/sign_in_with_apple#server
When you then get back the credentials from our plugin, you can create the user the same way you already do on iOS.
@adario you will need this small server as described in the README here: https://github.com/aboutyou/dart_packages/tree/master/packages/sign_in_with_apple#server
When you then get back the credentials from our plugin, you can create the user the same way you already do on iOS.
@HenriBeck I've created the server as described in the README, and I'm passing it in 'redirectUri' — the authentication tab is now dismissed as expected, but authentication fails on Android with this error:
AppleSignIn: Error = PlatformException(ERROR_INVALID_CREDENTIAL, The supplied auth credential is malformed or has expired. [ The audience in ID Token [*.*****.*****] does not match the expected audience. ], null)
(The token above has been redacted.)
@adario I think you put in the wrong identifier when setting up Sign in with Apple on the Firebase website. My guess is that you put in your normal bundle identifier into Firebase Auth and not the correct Service ID you have configured on Apple's side. That's why you get a mismatch.
For more information see this issue: https://github.com/aboutyou/dart_packages/issues/94
@HenriBeck Unfortunately that's not the problem, otherwise I wouldn't have written here... Firebase Auth is configured with the service ID in the console, not the bundle ID — I'm only using the bundle ID on iOS, never on Android.
@HenriBeck After doing a flutter clean
, and without touching anything, it's now working on Android too — I guess a Hot Reload was not enough... ;-)
Any plans to remove the need for the extra server on Android?
Thanks, Dario
@adario There are no plans to remove the extra server as there is no other way to launch the App on Android without it.
Seems weird that a hot reload fixed that particular issue, but it's always required to do a full restart for packages that have native Android/iOS code, which our package has.
@HenriBeck Actually, I wrote that a hot reload was not enough — I had to do a flutter clean
to get it working.
Thanks for the clarification about Android.
I'm using Firebase Auth not a custom server.
Hey,
the above is only needed when using a custom server, which is meant to mean that you should copy over the POST body (that Apple posts to your server) to that query parameter.
For Firebase we don't have documentation, but anecdotally we know that it works 😉
It depends on the type of integration you want to do, but maybe one of the earlier issues around this helps get you started: https://github.com/aboutyou/dart_packages/issues?q=is%3Aissue+firebase
We'd definitely be grateful for a write-up on how to integrate this with Firebase.
I will close this for now. But feel free to reopen if we can help you any further.
Can you please elaborate a bit about this, where your say: POST body (that Apple posts to your server) to that query parameter
Can you give me some links or some guidance so I can get this package working on Android with firebase.
Thank you
POST body (that Apple posts to your server) to that query parameter
@gerryau The above text refers to the custom server which is needed for Android support as described in the here in the readme.
You will then need to provide the URL to your own server in getAppleIDCredential
via the webAuthenticationOptions
@HenriBeck Okay thanks,
Do I need to use glitch for this or can I use firebase hosting? Are there any guides for doing this with fire base
@HenriBeck I've setup the server now as per the docs, what's the next step?
intent://callback?${PARAMETERS FROM CALLBACK BODY}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end
What do I put in the ${PARAMATERS FROM CALLBACK BODY} part?
Do I need to use glitch for this or can I use firebase hosting? Are there any guides for doing this with firebase
You don't have to use glitch of course, in the end, it's just a plain HTTP server. But you can't use the Firebase Auth server.
intent://callback?${PARAMETERS FROM CALLBACK BODY}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end
This is part of the server code you can see on the glitch example.
On Android I'm now using the Android Firebase SDK to handle this Plugin: https://github.com/britannio/android_firebase_siwa I just followed https://firebase.google.com/docs/auth/android/apple#handle_the_sign-in_flow_with_the_firebase_sdk
I'd rather use the alternative approach the docs describe and potentially show a web view instead of leaving the app but step 2 requires initiating the auth process yourself and I don't think this package can do that and redirect back into the app to access the tokens without a http server.
@britannio so what's the question/issue? For our package, you will always need this HTTP server to redirect back into the app again.
Hi guys,
has anyone tried using Sign In with Apple with Firebase anonymous accounts? I'm getting an error saying Duplicate credential received. Please try again with a new credential.
when trying to link an anonymous account with Apple credentials I used to sign in to my app before. I then need to regenerate credentials in order for this to work. The problem is that the Apple sign in flow will need to be shown again. Here's my code:
Future<AuthCredential> _generateAppleCredential(
{String baseUrl, String clientId}) async {
final redirectURL = "https://$baseUrl/signInWithApple";
final rawNonce = AuthenticationHelper.randomNonceString();
final appleIdCredential = await SignInWithApple.getAppleIDCredential(
scopes: [AppleIDAuthorizationScopes.email],
nonce: AuthenticationHelper.toSha256(rawNonce),
webAuthenticationOptions: WebAuthenticationOptions(
clientId: clientId, redirectUri: Uri.parse(redirectURL)),
);
final oAuthProvider = OAuthProvider(providerId: "apple.com");
return oAuthProvider.getCredential(
idToken: appleIdCredential.identityToken,
accessToken: appleIdCredential.authorizationCode,
rawNonce: rawNonce,
);
}
@override
Future<User> signInWithApple({String baseUrl, String clientId}) async {
final currentUser = await _auth.currentUser();
if (currentUser == null) return null;
final credential = await _generateAppleCredential(
baseUrl: baseUrl,
clientId: clientId,
);
try {
final result = await currentUser.linkWithCredential(credential);
final user = result.user?.user;
_streamController.sink.add(user);
return user;
} on PlatformException catch (e) {
if (e.code == "ERROR_CREDENTIAL_ALREADY_IN_USE") {
final result = await _auth.signInWithCredential(credential); // throws an error here
return result.user?.user;
} else {
throw UserError.Unknown;
}
}
}
The way to accomplish this on iOS is that the error returns updated credentials under AuthErrorUserInfoUpdatedCredentialKey
which then can be used to sign in without reauthenticating with Apple. Unfortunately, there is nothing similar in Flutter in PlatformException under details (it's null).
In the server.js, in this line- intent://callback?${PARAMETERS FROM CALLBACK BODY}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end
need to change only the package? or if {PARAMETERS FROM CALLBACK BODY} also need to be changed, could you tell me what to be added here?
@neha-madhini you only need to change your package identifier
For anyone else who was getting the following error:
supplied auth credential is malformed, has expired or is not currently supported
Firebase have changed how they do the OAuth authentication with the latest Flutter libraries. The following code contains both the old and new way of creating the credential:
// This is the section that decodes the session response to retrieve the access and id tokens
// We can use this to generate an OAuthCredential that can be used with FireBase.
Map<String, dynamic> appleValidationReponse = jsonDecode(session.body);
// This no longer works with the latest Firebase Flutter libraries
// OAuthCredential authCredential = OAuthCredential(
// providerId: "apple.com",
// signInMethod: "",
// idToken: appleValidationReponse['idToken'],
// accessToken: appleValidationReponse['accessToken'],
// rawNonce: nonce.toString(),
// );
final oAuthProvider = OAuthProvider('apple.com');
final providerCredential = oAuthProvider.credential(
idToken: appleValidationReponse['idToken'],
accessToken: appleValidationReponse['accessToken'],
rawNonce: nonce.toString(),
);
// Authenticate with firebase
UserCredential authResult = await _auth.signInWithCredential(providerCredential);
Full working code example.
Hello. I was checking out your project and it seems to work with firebase auth. This is my example code for someone who needs this for future reference
Future<FirebaseUser> signInWithApple() async { var redirectURL = "https://SERVER_AS_PER_THE_DOCS.glitch.me/callbacks/sign_in_with_apple"; var clientID = "AS_PER_THE_DOCS"; final appleIdCredential = await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, ], webAuthenticationOptions: WebAuthenticationOptions( clientId: clientID, redirectUri: Uri.parse( redirectURL))); final oAuthProvider = OAuthProvider(providerId: 'apple.com'); final credential = oAuthProvider.getCredential( idToken: appleIdCredential.identityToken, accessToken: appleIdCredential.authorizationCode, ); final authResult = await SignInUtil.firebaseAuth.signInWithCredential(credential); return authResult.user; }
As a note I got it working with iOS and Android through firebase.
Is it valid for the newest version of firebase plugin? I just need the functionality for ios. Isn't there any way to use it without Glitch?
@MeshkaniMohammad for iOS you don't need the Glitch server, it is only required for the Android integration.
@HenriBeck So I just need to register my call back URL
as redirectUrl
and register it as a returnUrl
in this link?
There is no need for a return url on iOS. Just follow the detailed guide in the readme, skip the android and Glitch server part, and it should work.
Hello. I was checking out your project and it seems to work with firebase auth. This is my example code for someone who needs this for future reference
Future<FirebaseUser> signInWithApple() async { var redirectURL = "https://SERVER_AS_PER_THE_DOCS.glitch.me/callbacks/sign_in_with_apple"; var clientID = "AS_PER_THE_DOCS"; final appleIdCredential = await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, ], webAuthenticationOptions: WebAuthenticationOptions( clientId: clientID, redirectUri: Uri.parse( redirectURL))); final oAuthProvider = OAuthProvider(providerId: 'apple.com'); final credential = oAuthProvider.getCredential( idToken: appleIdCredential.identityToken, accessToken: appleIdCredential.authorizationCode, ); final authResult = await SignInUtil.firebaseAuth.signInWithCredential(credential); return authResult.user; }
As a note I got it working with iOS and Android through firebase.
But you are not using Firebase Hosting backend to catch the callback. ("https://xxxproject.web.app/__/auth/handler")
I'm having trouble using Firebase Hosting as handler server, getting the error "The requested action is invalid.".
Does anyone got the same problem? Any help is welcome.
@houdayec it is not supported to use the firebase hosted backend as the callback, see our README for instructions, and an example server which you need to set up to use Android.
@HenriBeck Thanks for your answer. What is blocking to use Firebase Hosting auth handler?
I have the same question as @houdayec. Setting up a server seems unnecessary.
@houdayec easy: Not everyone is using Firebase. So we need to have an implementation which can be done without requiring a special provider/service.
While you might be using Firebase, plenty of consumers/companies might have other providers or own systems, so they can't/don't want to switch to Firebase.
@houdayec easy: Not everyone is using Firebase. So we need to have an implementation which can be done without requiring a special provider/service.
While you might be using Firebase, plenty of consumers/companies might have other providers or own systems, so they can't/don't want to switch to Firebase.
Of course not everyone is using Firebase. However, this is the most common / used MBaaS and to me, it should be an option as many people are facing an issue with it. It is not a matter of only providing a solution for Firebase, but also for Firebase. No need to switch for Firebase, it does not make sense. But your packages users might enjoy this feature.
@houdayec in our opinion the Firebase Auth package should do the full Firebase integration for Sign in with Apple on Android. For iOS, they could still refer to our package.
@houdayec you can also use the Firebase Cloud Function to implement the server, so this is technically not a blocker when already using Firebase.
Hello @DavidCorrado, all,
Future<FirebaseUser> signInWithApple() async { var redirectURL = "https://SERVER_AS_PER_THE_DOCS.glitch.me/callbacks/sign_in_with_apple"; var clientID = "AS_PER_THE_DOCS"; final appleIdCredential = await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, ], webAuthenticationOptions: WebAuthenticationOptions( clientId: clientID, redirectUri: Uri.parse( redirectURL))); final oAuthProvider = OAuthProvider(providerId: 'apple.com'); final credential = oAuthProvider.getCredential( idToken: appleIdCredential.identityToken, accessToken: appleIdCredential.authorizationCode, ); final authResult = await SignInUtil.firebaseAuth.signInWithCredential(credential); return authResult.user; }
As a note I got it working with iOS and Android through firebase.
I copied the code as is and added it to sign_in_with_apple/example/lib/main.dart.
I set the redirectURL
and clientID
to the correct values.
I verified that a simple CURL request returns correctly:
$ curl -X POST https://PROJECT.glitch.me/callbacks/sign_in_with_apple
returns with:
< HTTP/2 307 < date: Sun, 18 Oct 2020 18:36:16 GMT < content-type: text/plain; charset=utf-8 < content-length: 103 < location: intent://callback?#Intent;package=BUNDLE_ID;scheme=signinwithapple;end < x-powered-by: Express < vary: Accept
I also verified the return URL is correctly configured in the service identifier on Apple's end.
When I invoke signInWithApple()
, from Android, I get to the Apple login screen (https://appleid.apple.com/auth..), I am successfully authenticated and asked if I'd like to continue. Upon clicking on Continue
(on the Apple page), I briefly see the Android logo and am then navigated to a blank screen with a throbber.
I put a debug print right after the SignInWithApple.getAppleIDCredential()
call. I am not getting there.
In the console, I see:
D/SurfaceView(20638): onWindowVisibilityChanged(4) false io.flutter.embedding.android.FlutterSurfaceView{7955dfd V.E...... ........ 0,0-720,1436} of ViewRootImpl@981fa54[MainActivity] D/ViewRootImpl@981fa54MainActivity: Relayout returned: old=[0,0][720,1520] new=[0,0][720,1520] result=0x1 surface={false 0} changed=false D/ViewRootImpl@981fa54MainActivity: stopped(false) old=true D/SurfaceView(20638): windowStopped(false) false io.flutter.embedding.android.FlutterSurfaceView{7955dfd V.E...... ........ 0,0-720,1436} of ViewRootImpl@981fa54[MainActivity] D/ViewRootImpl@981fa54MainActivity: stopped(false) old=false
Until I finally give up and get:
E/flutter (20638): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: SignInWithAppleAuthorizationError(AuthorizationErrorCode.canceled, The user closed the Custom Tab)
Any help would be most appreciated.
Cheers,
@jessp01 is your bundle identifier really plate21
, normally it is something like com.company.app
Something weird is going on with the redirect for sure, so I would also suggest logging the redirect uri on the server and see if that looks alright
Hi @HenriBeck,
Thanks for your reply. Indeed, my bundle ID is different (that was just a test to see whether I'll get a different response with it). Anyway, I'll add some debug prints in the NodeJS code. I'll update when I have additional findings/arrive at a resolution so that others may also benefit from it.
Thanks again,
For anyone using Firebase and not wanting to setup a glitch project to process the callback, you can implement the same functionality of the glitch project using Firebase hosting and cloud functions as follows (using typescript):
in index.ts
import * as functions from 'firebase-functions';
import express = require('express');
import { URLSearchParams } from 'url';
const app = express();
app.use(express.urlencoded({ extended: false }));
process.env.ANDROID_PACKAGE_IDENTIFIER = '<your_android_app_package_id>';
app.post("*/app/callbacks/sign_in_with_apple", (request, response) => {
const redirect = `intent://callback?${new URLSearchParams(
request.body
).toString()}#Intent;package=${process.env.ANDROID_PACKAGE_IDENTIFIER
};scheme=signinwithapple;end`;
console.log(`Redirecting to ${redirect}`);
response.redirect(307, redirect);
});
exports.app = functions.https.onRequest(app);
in your firebase.json, in hosting section, setup url rewrite to call the function:
...
"hosting": {
"public": "public",
"rewrites": [
{
"source": "/app/**",
"function": "app"
}
you can then use the following as a callback from apple-sign-in: https://[firebase project id].web.app/app/callbacks/sign_in_with_apple
you can test with:
curl -X POST -H "Content-Type:application/json" 'https://[firebase project id].web.app/app/callbacks/sign_in_with_apple' -d '{"test":"something"}'
if all is working you should see the following test results:
Temporary Redirect. Redirecting to intent://callback?test=something#Intent;package=[your_android_app_package_id];scheme=signinwithapple;end
When the callback is called from apple, and with your intent properly setup in your AndroidManifest.xml according to package docs: https://pub.dev/packages/sign_in_with_apple, your app's call to SignInWithApple.getAppleIDCredential will get a AuthorizationCredentialAppleID returned with token and auth code that you can use to create a Firebase Auth Credential and sign-in:
final appleUser = await SignInWithApple.getAppleIDCredential(
scopes: [AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName],
webAuthenticationOptions: WebAuthenticationOptions(
clientId: '[your Service ID from Apple Dev setup]',
redirectUri: Uri.parse('https://[firebase_project_id].web.app/app/callbacks/sign_in_with_apple')),
);
final oAuthProvider = firebase_auth.OAuthProvider('apple.com');
final credential = oAuthProvider.credential(
accessToken: appleUser.authorizationCode,
idToken: appleUser.identityToken);
_firebaseAuth.signInWithCredential(credential);
*Be sure to add the callback URL and domain in your Apple Dev account within the Web Authentication Configuration in your Service ID Sign-in-with-Apple config setup).
Thanks, @mpiparo that works. Maybe include these steps along with the steps on glitch? With how common it is to use Firebase with Flutter this would make sense. I am still hoping the Flutterfire team is going to make apple auth on android work out of the box, though.
I'm just trying to use Apple sign-in on iOS, along with Firebase. It was only through reading through this issue that it's become clear that I don't need to pay any attention to the instructions about setting up a server - the README does not mention anything about that step being specific to Android. I think it would be helpful if it did.
Hi @jessp01, I'm curious if you found the solution as I'm facing exactly the same issue.
When I invoke
signInWithApple()
, from Android, I get to the Apple login screen (https://appleid.apple.com/auth..), I am successfully authenticated and asked if I'd like to continue. Upon clicking onContinue
(on the Apple page)
I received the exception from SignInWithApple.getAppleIDCredential(...
SignInWithAppleAuthorizationError(AuthorizationErrorCode.canceled, The user closed the Custom Tab)
The same exception is thrown when I close the Apple login screen manually.
I implemented the firebase function provided by @mpiparo. In the log, I can see the intent link was generated with the user's details.
intent://callback?${PARAMETERS FROM CALLBACK BODY}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end
Where do I find
${PARAMETERS FROM CALLBACK BODY}
? I'm using Firebase Auth not a custom server.