Do i need to "didReceiveRegistrationToken"? - ios

I want to send notifications, but only to authenticated users. This means, if my token updates (potential reasons: app is restored on a new device, user uninstalls/reinstall the app or user clears app data) my user will also not be authenticated.
Thus, whenever my user tries to login i update the token, which i receive via this method, for my backend:
Messaging.messaging().token { token, error in
// ...
}
When the user logs out i delete the token, so why would i implement this method here:
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
}
However, if i do not implement it, i receive this:
9.5.0 - [FirebaseMessaging][I-FCM002023] The object <Name.Delegate: 0x2827c0650> does not respond to -messaging:didReceiveRegistrationToken:. Please implement -messaging:didReceiveRegistrationToken: to be provided with an FCM token.

In this function you must Save your device "Token" or "refresh token" as Local:
"didReceiveRegistrationToken"
When the user logged in you must register the push-token in the backend
When the user logged out you must clear the push-token in the backend
You must after login set a key value in "KeyChain" such as:
"userId" : "x"
"For the users that before login, cleared or uninstalled the app":
In each launch you must check your app user is login or not?
if "NO" (doesn't have the token):
You must clear the push-token with "userId -> x" that you saved on keychain. (request it to the backend)
Albeit the new push-token is different with the previouse push-token.
if "YES" (user had backend token)
you must save the new "push-token" or "refresh-push-token" from below function:
"didReceiveRegistrationToken"

Related

iOS Google SignIn refreshed idToken has missing profile info in backend authentication

I use GoogleSignIn for iOS (GoogleSignIn-iOS), v6.1.0, in my iOS app.
All calls to my backend have the idToken in the request header.
The id token is verified in the backend. Here I also need to retrieve the users email and name.
(see also: https://developers.google.com/identity/sign-in/ios/backend-auth)
After a new SignIn with GIDSignIn.sharedInstance.signIn everything works fine.
GIDSignIn.sharedInstance.currentUser.profile contains email and name.
When sending the idToken to the backend, the Verifier gives me name and email in its payload, too.
Before I do a backend request, I get a valid (=not expired) idToken, with the following code:
private static func refreshToken(_ authentication: GIDAuthentication) async throws -> GIDAuthentication {
try await withCheckedThrowingContinuation { continuation in
authentication.do { authentication, error in
if let authentication = authentication {
continuation.resume(returning: authentication)
} else if let error = error {
Log.warn("Google SignIn refreshToken failed with -> \(error)")
continuation.resume(throwing: error)
}
}
}
}
I use the following code to get the idToken, before I create the request for my URLSession.
func idToken() async -> String {
do {
guard let user = GIDSignIn.sharedInstance.currentUser else {
Log.error("No GID user to get idToken from")
return ""
}
currentAuth = try await Self.refreshToken(user.authentication) //currentAuth is a class variable
return currentAuth?.idToken ?? ""
} catch {
print("Error during Google SignIn idToken retrieval \(error)")
return ""
}
}
And now my problem comes:
The idToken is refreshed properly. It is valid for another hour, and the verifier in my backend accepts it.
But I can't get the users name from the verified payload data in the backend, the name field is null.
Same happens when I use GIDSignIn.sharedInstance.restorePreviousSignIn (which I call on every app re-start, to do the silent sign in. (But in the app, the values are there in the updated users object profile)
It seems to me, that when the idToken gets refreshed, that it looses the profile scope.
I hope someone can help me with this, or at least explain the behaviour to me.
Thank in advance :)
Update
I checked the idTokens on https://jwt.io.
They are valid, but after the refresh, the jwt payload definitely is missing the profile data, like the users name.
I waited one day and tried again. Now the silent signin after app start gives me a complete idToken with jwt payload including name, but only once. After an hour, when the idToken gets refreshed, the idToken is again missing the profile information
Unfortunately I got no hint here, so I solved my problem as follows.
I hope this approach can save time for some others in the future.
I only require the profile data, when the user logs in to the backend the first time and a new user record is created in the backend.
In all other calls, where I need the JWT for authentication, I only rely on the basic information (ID, email) and handle all other values as optional values.
So I check the users name, if it is available. Otherwise the ID and a valid token is of course sufficent for authentication.

How to get Firebase FCM token outside the Swift AppDelegate

I have a SwiftUI app and inside it's custom AppDelagte I get the FCM token and save it to my database. Which works.
But when the user registers and gets put to the main screen of the applcation, their FCM token isn't uploaded to the database until they restart the app and the AppDelagte messaging function is run again. So basically, when the user registers, the app will not function correctly until they restart the app.
I have tried to get and then send the FCM token as soon as they successfully register via:
Messaging.messaging().fcmToken
which returns an empty string, meaning it is only made after they re run the app delegate.
How do I get around this, and send the FCM token to their database profile as soon as they register?
You could store the fcmToken on a static property of the AppDelegate as soon as you get it and then access it whenever the user registers.
Add a static property to the app delegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
static var fcmToken: String?
// when you get the token set it to the static property
Self.fcmToken = fcmToken
}
Then when you need the token simply access:
AppDelegate.fcmToken
You can use this method at any time to access the token instead of storing it.
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM registration token: \(error)")
} else if let token = token {
print("FCM registration token: \(token)")
self.fcmRegTokenMessage.text = "Remote FCM registration token: \(token)"
}
}
https://firebase.google.com/docs/cloud-messaging/ios/client

Some registrations don't seem to be registered in Notification Hub

I'm trying to integrate my Android and iOS native apps with Notification Hub.
For example, on iOS, as soon as I receive the deviceToken and if my user is already authenticated, I register directly with NotificationHub:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
log.debug("Received device token \(deviceToken.hexEncodedString()). Saving device token to user defaults.", "AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken")
let defaults = UserDefaults.standard
defaults.set(deviceToken, forKey: UserNotificationDeviceTokenKey)
defaults.synchronize()
if let authenticatedUser = Session.shared.authenticatedUser {
log.debug("Trying to register device token \(deviceToken.hexEncodedString()) to user \(authenticatedUser.id) on notification hub with hub name \(String(describing: hubName)) and connection string \(String(describing: connectionString)). Notification hub null? \(notificationHub == nil)")
self.notificationHub?.registerNative(withDeviceToken: deviceToken, tags: Set<String>([authenticatedUser.id]), completion: { error in
if (error != nil) {
log.error("Error registering for notifications: \(error.debugDescription)", "AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken");
} else {
log.debug("Successfully registered device token \(deviceToken.hexEncodedString()) for user with ID \(authenticatedUser.id) and email \(authenticatedUser.emailAddress)", "AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken")
}
})
}
}
And whatever happens, I start by saving the device token to the user defaults so that when a user does log in, he can retrieve the deviceToken from user defaults and call the same registerNative method to create a registration associating this deviceToken with a tag that is the user's identifier:
func userDidSignIn(user: User) {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let deviceToken = UserDefaults.standard.data(forKey: UserNotificationDeviceTokenKey){
log.debug("Trying to register device token \(deviceToken.hexEncodedString()) to user \(user.id) on notification hub with hub name \(String(describing: appDelegate.hubName)) and connection string \(String(describing: appDelegate.connectionString)). Notification hub null? \(appDelegate.notificationHub == nil)")
appDelegate.notificationHub?.registerNative(withDeviceToken: deviceToken, tags: Set<String>([user.id]), completion: { error in
if (error != nil) {
log.error("Error registering for notifications: \(error.debugDescription)");
} else {
log.debug("Successfully registered device token \(deviceToken.hexEncodedString()) for user with ID \(user.id) (\(user.emailAddress)) who just logged in", context: "HTRootViewController.userDidSignIn")
}
})
} else {
log.warning("No device token found in settings.", "HTRootViewController.userDidSignIn")
}
}
I have all my logs remotely sent to Bugfender, which lets me check the logs for all the devices using this app installed in TestFlight, so I can see that all the registerNative's are successful:
Trying to register device token
0fea9a4d99ec37dc4f3ac252c35fa4e1617004fd14740973d80a7dfdaeacc857 to
user 77bfb1c6-b05a-440b-a7a0-71ae5a91bbb2 on notification hub with hub
name Optional("[my notification hub name]") and connection string
Optional("[my notification hub connection string]").
Notification hub null? false
Successfully registered device
token 0fea9a4d99ec37dc4f3ac252c35fa4e1617004fd14740973d80a7dfdaeacc857
for user with ID 77bfb1c6-b05a-440b-a7a0-71ae5a91bbb2
([my user's email]) who just logged in
But then, when I load the list of all the registrations using this .NET C# code:
public async Task<List<RegistrationDescription>> GetRegistrations()
{
var hub = NotificationHubClient.CreateClientFromConnectionString([my notification hub connection string], [my notification hub name]);
var result = await hub.GetAllRegistrationsAsync(1000);
return result.ToList();
}
I can see that some registrations are there, but others are nowhere to be found, even though they succeeded according to my mobile logs.
Does anyone know what else could be going on? What else can I check? Why else would registrations not be saved even though the call to registerNative seems to succeed?
EDIT: As a matter of fact, and after checking more precisely what is happening, the registration first appears in the list of registrations returned by Notification Hub, but as soon as we try to send a notification, it's as if Notification Hub could not reach the corresponding device token and deleted the registration altogether. Is there a way to see the logs of Notification Hub to see what is going on and why it decides to delete this registration?
I figured it out. It turns out it was working on my device because I ran the beta in debug mode, so it was getting its device token from the Sandbox APS environment, which matched the Sandbox endpoint configured in Notification Hub, but other users were using the Production APS environment configured in the archive build, so they got device tokens for a different environment and notification hub correctly identified as incorrect device tokens. Once we switched the Notification Hub to the production APNS endpoint, all the TestFlight users were able to receive notifications because their registrations stayed in Notification Hub. Now that does mean that my debug builds trying to use that environments won't receive notifications, but that's OK, that's why we have a different environment and Notification Hub for development purposes. So if anyone sees something similar, don't forget to double check your Notification Hub configuration matches the type of build your users are using, and don't forget that TestFlight builds are now production-type builds, same as App Store builds.

Firebase verifying email while being logged in

I have the following logic in my iOS app:
User registers
Firebase sends an email confirmation
Returns to login screen
Now if the user logs in, without verifying the email, then we have a user session and isEmailVerified is false.
I only need to check the isEmailVerified in a certain point in the app.
Also I think signing the user in, checking the field and signing the user out would be bad practise.
I'd need to reauthenticate the user, what is the best way of doing this? How can I, after the user has logged in, switch the status of isEmailVerified?
Thanks
First, you need to have the email and password to create a credential. Your user already provided this on the login page... So the email and password to persistent storage on iOS. In Android, the equivalent would be SharedPreferences.
I do not code in iOS, but this will give you the idea for the logic.
Then, when you get to that point in your app where email verified is called:
if (user.isEmailVerified) == true {
// you do not need to hold the email and password in persistent storage anymore.
// go into your persistent storage and delete the data.
} else {
// get the email and password the user saved in persistent storage.
String email = persistentStorage.getEmail();
String password = persistentStorage.getPassword();
var user = firebase.auth().currentUser;
var credentials = firebase.auth.EmailAuthProvider.credential(email, password);
user.reauthenticate(credentials);
// then, when finished reauthenticating, check whether isEmailVerified() == true;
}

Firebase Auth (with Custom Token, for Linkedin) returns a user with no email and no data (only uid)

I'm trying to login with Linkedin and Firebase. I generate on my server the custom token, I have my private key, I use RS256 and this is my payload:
"iss" : service_account_email
"sub" : service_account_email
"aud", "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
"iat", Date().timeIntervalSince1970
"exp", Date().timeIntervalSince1970.advanced(by: 3600)
"uid", String.randomString(length:28)
I create the token, send it back to the app and from this I do:
Auth.auth().signIn(withCustomToken: token!, completion: { (user, error) in
I receive no error and a user back, so the token is valid. The problem is that the user has no values (no email, no displayName etc). the only thing is the uid which is the one that I set with: String.randomString(length:28)
How can I retrieve the user email and other info? In my linkedin account I have a displayname, email, picture etc. But here nothing.
Thanks
You have to set that manually. Firebase Auth has APIs to update profile (photo URL and display name) as well as email. You can send this LinkedIn data along with the custom token to your app, sign in with custom token and then update profile and email on that signed in custom token user.
By the way, set the LinkedIn ID as the UID for that user. Do not use a random string. Otherwise the next time you sign in with LinkedIn, the same user will map to another one.

Resources