Authentication and Extensions in iOS: best practices?

1,450 views
Skip to first unread message

Bartholomew Furrow

unread,
Sep 8, 2017, 10:48:10 AM9/8/17
to Firebase Google Group
Hi all,

I have an iOS app that uses Firebase authentication, and sadly its app extension doesn't get to share that authentication automagically. I have read this thread where Kato addressed this issue, though I have an important question about his response.

I can use NSUserDefaults to save information in the app and retrieve it from the extension. So what information should I send?

1. Follow Kato's solution, and sign my own tokens. I could create tokens with a simple Firebase Function as described here, authenticating the request for a token as described here. Unfortunately those tokens expire after an hour, so if nobody uses my sharing extension within an hour of signing in, it seems like the tokens will have expired and be useless. Is that correct?

2. Have the extension entirely use the REST API and Firebase Functions, passing the tokens needed for those operations to the extension as described here and here. I'm guessing those tokens expire too, however.

3. Ignore the main app, share no information, and allow the user to sign in independently in the extension. In addition to this plan being inconvenient for the user, I can't seem to assign a URL scheme to an extension, so I don't know how to make Google sign-in work.

4. Create my own non-expiring "tokens" for the user that I store a hash/salt of in the database. Authenticate user activity by checking against those.

Is there a better solution available? Is there a better solution on the horizon? I'd love to hear about either, and thoughts about my proposed solutions.

Thanks,
Bartholomew

Kato Richardson

unread,
Sep 8, 2017, 11:02:35 AM9/8/17
to Firebase Google Group
Hi Bartholomew, some comments inline.


1. ... if nobody uses my sharing extension within an hour of signing in, it seems like the tokens will have expired and be useless. Is that correct?

Yes. This is exactly what happens behind the scenes in Firebase Auth. Auth ID tokens also expire every hour and we have to renew them. But we do this automatically, so that shouldn't be a problem for you. Once you exchange the custom JWT for an Auth ID token, we'll manage it from there. 

If you're trying to send those tokens to a server, send the Auth ID token instead of the expired custom JWT. These do auto-refresh, but if it's close to the hour deadline, you'll want to use getIdTokenForcingRefreshCompletion, I believe, to avoid the edge case where it expires between the time you send it and the server processes it.


2. Have the extension entirely use the REST API and Firebase Functions, passing the tokens needed for those operations to the extension as described here and here. I'm guessing those tokens expire too, however.
Again, you only need the custom token long enough to obtain an access id. So this should be fine.

 
Is there a better solution available? Is there a better solution on the horizon? I'd love to hear about either, and thoughts about my proposed solutions.

Be sure to consider phone number auth and anonymous auth. Another roll-your-own might be to sign tokens based on device id or similar (although I don't know how one would accomplish this yet).
 

Thanks,
Bartholomew

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-talk+unsubscribe@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/CAHaiWHME6CzE5j3_fyiKw7QR1AgLazxw%3Dwb0zRbYq99nQrSYYw%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.



--

Kato Richardson | Developer Programs Eng | kato...@google.com | 775-235-8398

Bartholomew Furrow

unread,
Sep 9, 2017, 10:39:29 AM9/9/17
to Firebase Google Group
Thanks for the very quick reply, Kato. Ask a question, go to sleep, see the answer. Questions inline:


1. ... if nobody uses my sharing extension within an hour of signing in, it seems like the tokens will have expired and be useless. Is that correct?

Yes. This is exactly what happens behind the scenes in Firebase Auth. Auth ID tokens also expire every hour and we have to renew them. But we do this automatically, so that shouldn't be a problem for you. Once you exchange the custom JWT for an Auth ID token, we'll manage it from there. 

If you're trying to send those tokens to a server, send the Auth ID token instead of the expired custom JWT. These do auto-refresh, but if it's close to the hour deadline, you'll want to use getIdTokenForcingRefreshCompletion, I believe, to avoid the edge case where it expires between the time you send it and the server processes it.

I'm not sure I understand this, so let me try to spell out how I think it could work based on what you've said:
1. User signs in to APP using google auth, email auth or whatever.
2. APP sends a request to a firebase function, requesting a custom "JWT" token.
3. APP then exchanges the custom JWT token for an Auth ID token (what function performs this exchange?)
4. APP saves the auth ID token to the app group's NSUserDefaults.
5. User closes APP.
6. Hours or days later, User opens APPEXT.
7. APPEXT loads the auth ID token from the app group's NSUserDefaults and starts using it (what function "starts using it"?)
8. Although the auth ID token has expired, the Firebase authentication library can automatically renew it.


2. Have the extension entirely use the REST API and Firebase Functions, passing the tokens needed for those operations to the extension as described here and here. I'm guessing those tokens expire too, however.
Again, you only need the custom token long enough to obtain an access id. So this should be fine.

Is the "access id" the thing that goes into the "authorization" header (for authenticating with Firebase Functions) and the "auth" param (for the REST API)? Does that not expire?

 
Be sure to consider phone number auth and anonymous auth. Another roll-your-own might be to sign tokens based on device id or similar (although I don't know how one would accomplish this yet).

Would phone number auth provide an alternative method for solving this problem, or is it simply a quick sign-in method that the user might not mind as much (and that works, unlike Google Sign In in an extension)?

Regarding your last thought, it appears that Apple have gone out of their way to make it impossible to obtain a unique device ID on an iOS device.


Thanks again as always for a thoughtful reply.
Bartholomew

Bartholomew Furrow

unread,
Sep 9, 2017, 10:39:29 AM9/9/17
to Firebase Google Group
Another option just occurred to me, which might make all this "expiry" stuff go away:

5. When APPEXT opens and doesn't have a logged-in user, have it use a custom url scheme to open up APP and have it dump a non-expired token into UserDefaults, then close itself.

Kato Richardson

unread,
Sep 12, 2017, 9:57:38 PM9/12/17
to Firebase Google Group
Hey, sorry for the wait. Been a busy week and this kept slipping down the todo list.

If you're trying to send those tokens to a server, send the Auth ID token instead of the expired custom JWT. These do auto-refresh, but if it's close to the hour deadline, you'll want to use getIdTokenForcingRefreshCompletion, I believe, to avoid the edge case where it expires between the time you send it and the server processes it.

I'm not sure I understand this, so let me try to spell out how I think it could work based on what you've said:

No, I assumed the goal here was to authenticate the extension by forwarding the auth state to some server process. I don't think that would work based on what you've described here.

I'm a bit hazy on the use case here, probably because I'm not be savvy enough at iOS to understand all the nuances here of what an extension represents and how that's separated from the app.  
Can it use the SDK and auth normally? Is the only issue here that it requires a reauth and you want this to be more automated?
When the app extension is opened some time later, is the app also accessed? Or are the two not necessarily connected?
 

Is the "access id" the thing that goes into the "authorization" header (for authenticating with Firebase Functions) and the "auth" param (for the REST API)? Does that not expire? 

Yes it is and yes it does. There are a few options for refreshing it though. If this feels like the right general approach, we can try to work down this route in a bit more detail.
 
 
Be sure to consider phone number auth and anonymous auth. Another roll-your-own might be to sign tokens based on device id or similar (although I don't know how one would accomplish this yet).

Would phone number auth provide an alternative method for solving this problem, or is it simply a quick sign-in method that the user might not mind as much (and that works, unlike Google Sign In in an extension)?

This is also probably a misunderstanding at my end about the relationship between the extension and the app and may not be applicable. 

I'm also not uber deep into phone auth yet. But my first thought was to check how frequently the phone auth verification code expires (if ever). Assuming it's long lived, maybe you'd be able to store that instead and use it to auth. I'll look into the expiration part anyway as it's a detail I should probably know.

As far as a general solution to long lived auth, probably just one they wouldn't mind as much. On Android, phone auth can actually occur fairly seamlessly. A lot here depends on how long the verification token lives for.
 

Regarding your last thought, it appears that Apple have gone out of their way to make it impossible to obtain a unique device ID on an iOS device.

Yeah, a unique device id for use with custom auth tokens might just be something you download from the server the first time the app connects (i.e. a uuid). But that has its own set of problems, since apple likes to delete localStorage now and again without letting us know : (
 
Let me dig in some more on these. In the mean time, if you can send me some light reading on app extensions, I can probably offer a bit more coherent help here.

Kato Richardson

unread,
Sep 12, 2017, 10:16:33 PM9/12/17
to Firebase Google Group
On Saturday, September 9, 2017 at 7:39:29 AM UTC-7, Bartholomew Furrow wrote:
Another option just occurred to me, which might make all this "expiry" stuff go away:

5. When APPEXT opens and doesn't have a logged-in user, have it use a custom url scheme to open up APP and have it dump a non-expired token into UserDefaults, then close itself.

That occurred to me too--one of the reasons I asked about whether the extension could talk to the app. Getting the Auth ID credential to the extension is easy enough. You can just have the app call getIdTokenForcingRefreshCompletion() to ensure you receive a non-expired auth token. This would work fine with REST api calls. 

But assuming you're using the SDK from the extension, I couldn't see an easy way to pass the Auth ID credential into the SDK and re-use it. You might be ahead to pass the Google Auth or similar provider token into the extension and call signInWithCredential(). But all of this felt a bit muddled. Might be a good approach; more to investigate here.

Bartholomew Furrow

unread,
Sep 12, 2017, 11:42:55 PM9/12/17
to Firebase Google Group
I'm currently working on implementing Kato's suggested solution, with my #5 to fill in the "what if the token has expired?" hole. I've hit a hitch, however:

In iOS, how can I tell whether or not the signed-in user has a custom token that I issued?

I'd like to let the user sign in with something like FUIAuth, then issue a token from the server and sign the user in with that. That way I can include "additional claims", as they're called in the documentation. Right now when I do that, the currentUser's providerId is "Firebase", and currentUser.providerData has one entry, with providerId "google.com".

In other words, when the user starts the app, I can't tell whether the user has a custom token or not. How can I check? Or am I going about this the wrong way?

Thanks!
Bartholomew

Bartholomew Furrow

unread,
Sep 13, 2017, 10:56:18 AM9/13/17
to Firebase Google Group
Thanks for adding so much, and pushing it up your todo list. I appreciate that it's tough to fit a big email into a working day.

Please ignore the followup question that looks like it was posted after your answers; I had sent it earlier, but it wasn't approved yet.


Very brief primer on App Extensions:

Giving your app an "app extension" is how you tell iOS, "I want my app to be accessible from other apps in one of a few specific ways." For example, if I open a photo in the Photos app and press the weird-looking "share" icon, I can send that photo to the "Share" extension of any installed app that has one. More examples in the link, but that's my use case.

Extensions are associated with an app, but run as a separate process in their own memory space, and are expected to be shut down as soon as the user is done with them. Their local storage isn't shared with the associated app, but combining the concepts of "App Groups" and "NSUserDefaults" gives you a shared hash table (see "Sharing Data with Your Containing App").

In summary, when you first code up an app extension, the user isn't signed in with Firebase. I want to know how to get the extension signed in, without user action. Any other questions I have, I can ask in another thread. Some features of iOS I should mention, because they've come up or they're otherwise relevant, are:

0. There's some functionality that's restricted in App Extensions, but Firebase appears to be generally unaffected. However:
1. Apparently one can't use Google authentication in an app extension, since that requires setting up a "URL scheme" which seems to be impossible in app extensions.
2. No way to find out your phone number automatically in iOS (surprisingly), and probably no way to read incoming texts, so phone authentication wouldn't be nearly as appealing as on Android.
3. No way to get a unique identifier for your phone, though of course you can generate one and share it between your app and extension with NSUserDefaults.
4. iOS is very strict about what can happen when an app isn't open. Basically if it isn't in the foreground, it can only do a few specific things (e.g. location tracking, uploading).
5. iOS does have a way, called custom URL schemes, to open up another app and have it do something based on a URL you pass.


At this point I see a couple of avenues of approach:

Avenue #1: "I hope this works". When the extension starts up, if there isn't a signed-in user with the right ID, use a custom URL scheme to talk to the app and say "Start up, call getIdTokenForcingRefresh, save the result and userId in NSUserDefaults and close." After it closes, find the saved token, use it to authenticate with a Firebase Function to ask for a custom token, and we're off to the races.

Avenue #2: "Bush-league security". Generate a non-expiring secret key for each authenticated user, and store it in the database. When you sign in with the app, store that key and your userId in NSUserDefaults. When the extension starts up, it says "Hi server! I'm userId, and here's my proof. Issue me a token!" The server validates that, makes a custom token, sends it to the user and the user is now signed in. Obviously it's possible to be slightly less bush-league with this.

I'm going to give avenue #1 a shot, and report back once I have some results.

Cheers!
Bartholomew


P.S. If you're looking for a way to make this authentication business easy on developers like me, a possible avenue of investigation is saving whatever it is that lets the user re-authenticate to NSUserDefaults for a specified App Group, in addition to where it's already saved. I'm 90% sure it isn't that simple, though, because the share extension has to have its own App ID.

--
You received this message because you are subscribed to a topic in the Google Groups "Firebase Google Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/firebase-talk/OtbDoPGm4zg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to firebase-tal...@googlegroups.com.

To post to this group, send email to fireba...@googlegroups.com.

Bartholomew Furrow

unread,
Sep 14, 2017, 10:25:21 AM9/14/17
to Firebase Google Group
Update: I now have something reasonable working, and it ended up being relatively simple. Here's what I did:

1. When a user logs in to the main app, I generate a random string of fixed length. I save it, with the userId, to UserDefaults. I save its sha256 hash to the Firebase database -- more specifically to /some/path/$userId/$instanceIdToken, so I don't have to worry about ending up with a zillion of these. If the user logs out, I delete the userId and the random string from UserDefaults.

2. When a user opens the app extension, I call an https-triggered firebase function. I tell it "I am user (userDefaults.string("userId")), and my secret code is (userDefaults.string("secretCode"))." It takes the sha256 hash of the secret code, verifies that it has that code for that user under /some/path/$userId/$anything, and sends back a server-generated token for that user.

3. I take the token and call Auth.auth().signIn with it, and I'm authenticated!

That was a lot easier than it seemed like it would be. Thanks for the help, Kato; hopefully my experience here has been helpful, and I'd love to hear if a simpler method becomes available.

Cheers,
Bartholomew

Shai Ben-Tovim

unread,
Dec 18, 2017, 10:21:53 AM12/18/17
to Firebase Google Group
Hi Bartholomew,

Have been trying to find an elegant solution for this exact problem for several days now with little success. Was thinking to go with: custom url scheme ->  main app refresh Auth token and saves to UserDefaults --> REST API with bearer token in extension.

Did you stick with your posted solution from Sep? Find anything better? 

Any insight would be much appreciated.

Shai

Bartholomew Furrow

unread,
Dec 18, 2017, 1:13:21 PM12/18/17
to fireba...@googlegroups.com

Shai,

I hit two problems with the strategy you described:

- Getting back from the main app. It turns out there's no "I'm done with the app; close it now" command in iOS.

- Unless it's a Today extension, you aren't supposed to have access to openUrl. There's a way around that, but it might not pass muster on the app store.

I ended up sticking with the solution I posted on September 13th. I found it relatively straightforward to implement, and it solved the problem pretty effectively. I'm guessing a security expert could tell me why it isn't a good idea to have a secret code that doesn't expire, though I suppose one could add an expiry time and refresh strategy to cope with that.

Good luck! Let me know if you get stuck or have any questions, and I'll see if I can help out.

Bartholomew

Shai Ben-Tovim

unread,
Dec 21, 2017, 11:02:35 AM12/21/17
to Firebase Google Group
Thanks Bartholomew.
Will follow your advise.
Firebase really need to solve this in a more elegant manner. I assume the "main app" token info is saved in the device keychain. Firebase should give an option to save it to an App Group shared keychain so any app extension can access it like the main app does.

Shai

Bartholomew Furrow

unread,
May 27, 2018, 6:30:32 PM5/27/18
to Firebase Google Group
Unfortunately, it seems like with Firebase 5.0 for iOS, we can't use Firebase in App Extensions at all anymore*. Is that accurate? Are there any plans to support extensions at least to the degree that they were possible to use before?

Thanks,
Bartholomew

* FirebaseAuth now uses UIApplication.sharedApplication, which isn't accessible from extensions.

Rick Terrill

unread,
May 29, 2018, 6:33:45 PM5/29/18
to Firebase Google Group
I just ran across the same thing FirebaseAuth appears to be unusable in iOS app extensions, due to using UIApplication.sharedApplication. That means you can't read/write Firestore, etc. from an extension.

Is there any plans to fix this, or is it by design?

Thanks!
Rick

Bassam

unread,
May 30, 2018, 12:59:58 PM5/30/18
to Firebase Google Group
Hey Bartholomew,
As far as I know, nothing should have changed in 5.0 in the iOS SDK to affect this.
If you want to persist a user in your app extension, currently the recommended way is the following.
1. sign in the user in your iOS app.
2. Save the signed in user's ID token in the NSUserDefaults or shared keychain (this is even better as it is encrypted) with same app group.
3. In your app extension, get the ID token from the shared NSUserDefaults/keychain
4. Send the ID token to your backend and check auth_time to make sure it is a recent login to minimize token leakage risks.
5. Mint a custom token with Firebase Admin SDk and return it.
6. In your app extension, signInWithCustomToken using the returned custom token.

So this will make it possible for a user to sign in to your app and still be signed in in your extension.

Best regards,
Bassam

Bartholomew Furrow

unread,
May 30, 2018, 1:01:53 PM5/30/18
to fireba...@googlegroups.com
Including FirebaseAuth is no longer possible.

--
You received this message because you are subscribed to a topic in the Google Groups "Firebase Google Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/firebase-talk/OtbDoPGm4zg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to firebase-tal...@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.

Bartholomew Furrow

unread,
May 30, 2018, 2:21:39 PM5/30/18
to fireba...@googlegroups.com
Bassam,

Sorry for the quick left-handed mid-gardening message. The trouble is that FirebaseAuth uses UIApplication.sharedApplication, which means App Extensions can't use it, and by extension can't use Firebase at all. I wonder if this is related to the fact that the code for that pod is now visible rather than pre-compiled? In any case, we've gone from "There are ways to make Firebase authentication work in App Extensions" in 4.x.y to "There is no way of using the Firebase iOS SDK with app extensions" in 5.x.y.

Cheers,
Bartholomew

Bassam

unread,
May 30, 2018, 2:36:32 PM5/30/18
to Firebase Google Group
Hey Bartholomew,
I have filed an issue in the GitHub repo. You can track this at: https://github.com/firebase/firebase-ios-sdk/issues/1357

Bassam

Bartholomew Furrow

unread,
May 30, 2018, 3:37:54 PM5/30/18
to fireba...@googlegroups.com
Thanks, Bassam! That's well-written.

Bartholomew Furrow

unread,
Jun 7, 2018, 10:58:02 PM6/7/18
to fireba...@googlegroups.com
The fix for this went live yesterday in version 5.2.0. Thanks, Bassam and team!

Bassam

unread,
Jun 7, 2018, 11:20:15 PM6/7/18
to Firebase Google Group
Thanks for the confirmation!
Reply all
Reply to author
Forward
0 new messages