I am trying to enable Facebook authentication in Realm Sync, but keep getting an error on login.
I have been using these guides:
https://docs.realm.io/sync/v/3.x/using-synced-realms/user-authentication/additional-providers#facebook
https://docs.realm.io/sync/v/3.x/self-hosted/customize/authentication/included-third-party-auth-providers/facebook-authentication
I have the Access Token provided by the Facebook API/SDK enabling me to log in/sign up a user.
When I use Realm's libraries to log in a user with the Facebook Access Token, I get an error stating the 'provider' parameter is invalid, but this parameter is defined by Realm's own classes.
I have successfully authenticated a user with an email & password so do I need to set up something else on Facebook/Realm Sync? It seems Facebook authentication just doesn't work in Realm Sync and the above help files are pretty useless.
Authentication code
func authenticateWithFacebook(facebookToken: String, completion: #escaping (RealmAuthenticationResult) -> ()) {
let credentials = SyncCredentials.facebook(token: facebookToken)
print("------FACEBOOK LOGIN-------")
print("Token: \(facebookToken)")
login(credentials) { (result, userId) in
completion(result)
}
}
private func login(_ credentials: SyncCredentials, completion: #escaping (RealmAuthenticationResult, String?) -> ()) {
SyncUser.logIn(with: credentials, server: RealmConnection.AUTH_URL, onCompletion: { (user, err) in
if let _ = user {
print("User has logged in/signed up")
return completion(RealmAuthenticationResult.success(true), user?.identity)
} else if let error = err {
print(error.localizedDescription)
return completion(RealmAuthenticationResult.failure(error), user?.identity)
}
})
}
The error
Error Domain=io.realm.sync.auth Code=601 "Your request parameters did
not validate. provider: Invalid parameter 'provider'!;"
UserInfo={NSLocalizedDescription=Your request parameters did not
validate. provider: Invalid parameter 'provider'!;}
Other things I have tried
I have tried directly instantiating the base provider class 'RLMIdentityProvider' and creating SyncCredentials with that, but no dice.
A workaround is to get the account information from the Facebook API/SDK and use the account's email to login/signup with a username/password setup. However, it seems to make Facebook authentication redundant.
That Realm Documentation link is outdated. See the 3.16.0 Documentation (or later) as a lot was changed.
At the moment Password, JWT & Firebase are the only auth options with Firebase Authentication being a very solid solution. Integrating Firebase is also covered in the Realm Documentation in the Using Sync'd Realms -> Authentication section. I won't link it as the documentation is being frequently updated now days.
As stated by the Realm Team (several times) extensive auth options are not a priority as other companies (like Firebase) handle it very well.
There were a number of posts on the Realm forums speaking to this but Ian's response to this question is very succinct.
we have and will continue to prioritize synchronization features for
mobile
and then
This is why we recommend that a production app should outsource user
management and authentication to a company which specialized in these
features.
Related
I would like to force users that previously authenticated with Facebook to sign up using a new provider. The reason for this is that I would like to remove Facebook as an authentication provider. I would unlink the user once the user has been successfully linked with the new provider.
For example, the user is presented with new authentication options and the user selects to continue with email. I have the following code:
func createUserAndSignIn(
username: String,
email: String,
password: String
) async throws -> String {
let credential = EmailAuthProvider.credential(withEmail: email, password: password)
// if user is already logged in (in this case with Facebook)
if let user = Auth.auth().currentUser {
try await user.link(with: credential)
}
do {
let authDataResult = try await Auth.auth().createUser(withEmail: email, password: password)
return authDataResult.user.uid
} catch {
// throw error
}
}
The linking of accounts (user.link(with:)) fails with the following error:
Domain=FIRAuthErrorDomain Code=17014 "This operation is sensitive and requires recent authentication. Log in again before retrying this request." UserInfo={NSLocalizedDescription=This operation is sensitive and requires recent authentication. Log in again before retrying this request., FIRAuthErrorUserInfoNameKey=ERROR_REQUIRES_RECENT_LOGIN}
Would this be even be the correct approach for this?
You have to re-authenticate the user. Using the current credential
if
let user = Auth.auth().currentUser,
let currentAccessToken = AccessToken.current
{
// Prompt the user to re-provide their sign-in credentials
let result = try await user.reauthenticate(
with: FacebookAuthProvider.credential(withAccessToken: currentAccessToken.tokenString)
)
// Then link the user
try await user.link(with: newCredential)
// User can be unlinked from Facebook
try await Auth.auth().currentUser?.unlink(fromProvider: "facebook.com")
}
This is needed for several operations such as updating the user's email, password or deleting the user.
The approach you're taking is close. The error you're getting is because some operations in firebase require a recent authentication to have taken place:
FIRAuthErrorCodeRequiresRecentLogin: Updating a user’s email is a
security sensitive operation that requires a recent login from the
user. This error indicates the user has not signed in recently enough.
To resolve, reauthenticate the user by invoking
reauthenticateWithCredential:completion: on FIRUser. [1]
The steps you want to take are:
Authenticate the user with an existing auth method.
Prompt the user for their email and password
Use the email and password to create an AuthCredential object.
Pass that AuthCredential object to the user's linkWithCredential method.
There's a complete walkthrough for this in the Firebase docs: https://firebase.google.com/docs/auth/web/account-linking#link-email-address-and-password-credentials-to-a-user-account
But the key point is that you have to authenticate the user with an existing provider before you do this, even if they are technically "logged in".
Note that the steps are slightly different if you want to link the user to another Auth provider other than email (such as Google): https://firebase.google.com/docs/auth/web/account-linking#link-federated-auth-provider-credentials-to-a-user-account
After that, if you wish, you can use unlink to remove the Facebook authentication.
When I want to delete a firebase user account in my application, the operation passes normally if the user has recently logged but after a period of time if I try to delete the user I get this error
"This operation is sensitive and requires recent authentication. Log in again before retrying this request."
Normally the firebase refresh the user session automatically but I didn't find why he want the user to log again and even the value of Auth.auth().currentUser is not nil. Thank you for your help !
this is my code to delete the user account :
#objc func deleteAccountAction(){
self.showProgressView()
let user = Auth.auth().currentUser
let id=Auth.auth().currentUser?.uid
self.refProducts.child(id!).removeValue { error, _ in
if(error != nil){
print("firebase remove error")
print(error?.localizedDescription.description ?? nil)
self.dismissHUD(isAnimated: true)
}
else{
self.refUsers.child(id!).removeValue { error, _ in
if(error != nil){
print("firebase remove error")
print("error while deleting user from firebase: "+error!.localizedDescription)
self.dismissHUD(isAnimated: true)
}
else {
user?.delete { error in
if error != nil {
print("error while deleting user:" + error!.localizedDescription)
self.dismissHUD(isAnimated: true)
} else {
UserDefaults.standard.removeObject(forKey: "USER_UID")
UserDefaults.standard.synchronize
self.dismissHUD(isAnimated: true)
let nextVC = self.storyboard?.instantiateViewController(withIdentifier: "StartingViewController") as! StartingViewController
nextVC.isAccoundDeleted=true
GlobalVar.user=nil
self.navigationController?.pushViewController(nextVC, animated: true)
}
}
}
}
}
}
}
For certain sensitive operations (such as changing the user's password, or deleting the user account), Firebase requires that the user has recently signed in. If the user hasn't signed in recently when you try to perform such an operation, Firebase throws the exception you get.
When you get this exception, you should ask the user to re-enter their credentials, and retry the operation.
From the documentation on handling errors:
[Deleting a user account] is a security sensitive operation that requires a recent login from the user. This error indicates the user has not signed in recently enough. To resolve, reauthenticate the user by invoking reauthenticateWithCredential:completion: on FIRUser.
In addition to Frank van's answer, time span for that is 5 minutes. After 5 minutes of login you cannot do such operations.
you can refer FIRAuthErrorCode (check out error code 17014 : FIRAuthErrorCodeRequiresRecentLogin = 17014)
Here is a workaround:
Make a call to a cloud function that performs the user account deletion and sign out the client. You must ensure proper guards for this cloud function so that it is not maliciously used.
Show courtesy to your users. Offer them with a proper dialog showing that this is irreversible and all relevant user data will be deleted permanently. Only call the function if the user accepts the consequences.
PSA edit:
Although this approach will work, it will have an improper risk imposed on your user profile and data. The requirement to have the user sign in again for the delete account procedure serves a higher objective which is an additional layer of protection for the user account. By bypassing it as the solution suggests you are imposing a risk that another actor using the device will be able to delete the account without any additional checks. it is a procedure that is irreversible so if it was done by mistake then there is no way to go back.
What you can do (its probably the most easy way) is to save login and password of the user in the local storage when they log in first time (for example using AsyncStorage) and once they want to delete the account log them in again without needing them to reenter credentials shortly before you delete the account. They wont even noticed you logged them in again before deleting the account.
Im not a security expert but the password and email would be stored localy without access to the outside world so I do not see any concerns about security issues there.
Once you delete the app, the local storage is gone anyways, at least with AsyncStorage.
Once you logout a user and use another account, the new account credentials would overwrite the old ones from the local storage.
I have an app with an Uber login that gives access to restricted API calls (info on the current ride). I'd like to share the login token with the associated Today Widget so it can make similar calls.
I'm already sharing data with a UserDefaults suite, and I'm using the UberRides SDK. In digging into the RidesClient object it seems to try to use the keychain for storing/sharing the login token, and I set up a shared keychain to try to take advantage of this, but no luck. Restricted API calls from the widget return as unauthorized. Any suggestions?
Here's some code from the widget (note the user already authenticated in the main app):
let rc = RidesClient()
rc.fetchCurrentRide { ride, response in
if ride == nil { print("NO CURRENT RIDE") }
print(response.response)
print(response.error?.title)
if let ride = ride {
// do something
} else {
self.ride = nil
}
}
This returns an unauthorized response. I traced into the RidesClient (which is an object in the UberRides SDK), and see the code where the token is "supposed" to come from the keychain, but it doesn't.
I also tried generating my own URL request in the widget, using the login token passed through shared UserDefaults. This followed the standard HTTP access approach, putting the token in the Authorization header. But I got the same unauthorized response.
Here's some more details on the SDK approach:
Main app uses the LoginButton in native mode:
let scopes: [RidesScope] = [.Profile, .Places, .Request, .AllTrips]
let loginManager = LoginManager(accessTokenIdentifier: Configuration.getDefaultAccessTokenIdentifier(), keychainAccessGroup: "com.MYCOMPANY.MYAPP.share", loginType: .native)
let loginButton = LoginButton(frame: loginFrame, scopes: scopes, loginManager: loginManager)
loginButton.presentingViewController = self
loginButton.delegate = self
view.addSubview(loginButton)
The login button does the right thing and authorizes in the Uber app. I can see the token returned in the delegate callback didCompleteLoginWithToken. However, I can then check for the token:
let token = TokenManager.fetchToken(Configuration.getDefaultAccessTokenIdentifier(), accessGroup: "com.MYCOMPANY.MYAPP.share")
print(token)
The token is "nil". I don't think the SDK is saving the token into the access group keychain.
When I use the default keychain (not the keychainAccessGroup), the login in the app works fine and I can get the login token back and make restricted calls to the API. However, that doesn't help the widget, which needs the token from the access group keychain.
Solved!! After many hours of debugging, and searching. What was not clear in ANY documentation is the keychainAccessGroup MUST include the AppIndentifierPrefix. That's the 10 character identifier associated with the App ID. So, instead of using "com.MYCOMPANY.MYAPP.share", it's "APPID.com.MYCOMPANY.MYAPP.share" for the keychainAccessGroup.
So the error I am getting is:HTTP Status 401: Unauthorized, Response: {"errors":[{"errorType":"invalid_request","message":"Authorization header required.
The code I have to authorize Fitbit is :
func authorizeFitbit(completion: (result: Bool) -> Void ){
oauthswift.accessTokenBasicAuthentification = true
let state: String = generateStateWithLength(20) as String
oauthswift.authorizeWithCallbackURL( NSURL(string: "xxxxxxxxx")!, scope: "xxxxxxxxxxxxx", state: state, success: {
credential, response, parameters in
self.getUserProfile(){
(result: Bool) in
completion(result: result)
}
}, failure: { error in
print(error.localizedDescription)
completion(result: false)
})
}
I am able to grab profile information but when I am trying to grab more information from fitbit by clicking on an IBAction button, I am required to use this authorize function again to grab the number of steps.
The question may need more details to be properly answered.
What OAuth grant type are you using?
What is the oauthswift class you are using? Your own or third party?
You are stating that you are able to grab profile information. Should that be read as you been able to get the whole way through for whatever grant type you are using but get problems when you want to execute a new request to the resource server?
Some other OAuth implementations require you to set the Authorization HTTP-header with the actual token type and the actual access_token when you send your requests to the resource server. The following is an example for Microsoft Azure AD using Authorization Code flow:
GET /v1.0/me/messages
Host: https://graph.microsoft.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU...
Another possibility, if I compare to the "Authorization Code" grant type, the "invalid_request" error implies that you have not got pass the first stage in the request flow, that error you get before requesting the access token.
EDIT: Didn't see how old this question was. Well, I let the reply be here anyway, maybe it helps someone one day...
I've migrated from Azure Mobile Service to an App Service but I'm having difficulty working out how best to implement extended Facebook auth.
In my old implementation I inherited from FacebookLoginProvider and fetched the token from the claims. I then added the CustomFacebookLoginProvider to my login providers. I then use the token to fetch more information about the user (their date of birth, friends and gender). With this information I created a user object and saved it to my DB.
Does anyone have any suggestions on how best to recreate this in App Service as I can't find any documentation.
As far as how to set up Facebook authentication, you can find documentation here (and it sounds like you've already figured out this much):
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-how-to-configure-facebook-authentication/
Now that Facebook authentication is set up, you can refer to the following which shows how to obtain user information:
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-dotnet-backend-how-to-use-server-sdk/#user-info
// Get the credentials for the logged-in user.
var credentials =
await this.User
.GetAppServiceIdentityAsync<FacebookCredentials>(this.Request);
if (credentials.Provider == "Facebook")
{
// Create a query string with the Facebook access token.
var fbRequestUrl = "https://graph.facebook.com/me?access_token="
+ credentials.AccessToken;
// Create an HttpClient request.
using (var client = new System.Net.Http.HttpClient())
{
// Request the current user info from Facebook.
using (var resp = await client.GetAsync(fbRequestUrl))
{
resp.EnsureSuccessStatusCode();
// Do something here with the Facebook user information.
var fbInfo = await resp.Content.ReadAsStringAsync();
}
}
}
Note that you must add a using statement for System.Security.Principal to make the GetAppServiceIdentityAsync extension method work.
For more information on which Facebook user properties you can query, see the Facebook documentation here: https://developers.facebook.com/docs/graph-api/reference/user. Note that you may need to specify which user properties you want as an additional fields query string parameter on your call to the Facebook graph.
The only change I had to make when switching from Mobile Service to Mobile App was to change the end of the callback URL in the developer portal to use /.auth/login/facebook/callback instead of /signin-facebook and it worked exactly the same way as before.
Note that this is for a Windows app with a .NET backend; you didn't specify what you're using so your mileage may vary.
I've been using the following approach to obtain the Facebook Access Token in the iOS app.
App Services includes the Facebook Access Token in the request header, refer to https://azure.microsoft.com/en-in/documentation/articles/app-service-api-authentication/.
To get to the access token, create a Custom API in the Azure Portal, e.g. facebookUserInfo, with the following code:
module.exports = {
"get": function (request, response, next) {
response.send(200, { facebookAccessToken: request.headers['x-ms-token-facebook-access-token'] });
}};
In the iOS app, use the following code to query the custom API:
let client = self.table!.client
if client.currentUser != nil {
client.invokeAPI("facebookUserInfo", body: nil, HTTPMethod: "GET", parameters: nil, headers: nil, completion: { (result, response, error) -> Void in
if let resultDict = result {
if let facebookAccessToken = resultDict["facebookAccessToken"]! {
print(facebookAccessToken)
}
}
}
}
By Using the Easy Auth feature of Azure App Services, I dont need to worry about authentication.
I have a blogpost on this. I have explained on how we can use the FB GraphApi's to query FB data. Here is the link: https://blogs.msdn.microsoft.com/kaushal/2017/06/08/using-easy-auth-to-query-facebook-information-via-graph-api/
I have the sample code deployed on Github. Here is the link: https://github.com/kaushalp/Facebook-GraphApi-with-EasyAuth