React-Native Firebase: Token refresh not working - android

I have an issue with react-native-firebase (or firebase) in which my app does not receive a trigger after the auth token refreshes. It's pretty much the same issue as [1], but they never posted a solution.
So, what happens is that both on an Android phone and on the Android emulator (no idea about iOS), signing up, logging in and logging out works perfectly, meaning the listeners correctly see when I do a logout() etc. But the listeners never fire when the token refreshes.
My first question is: Am I correct to assume that the onIdTokenChanged-listener should automatically fire after 60 minutes without having to do anything else, e.g. call any firebase function, such that the app just sits there doing nothing for 60 minutes and then receiving the event and replacing the token?
My main component which contains the listeners looks like this:
class ReduxAppWrapper extends Component {
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
console.log('COMP DID MOUNT: AUTH STATE CHANGED! ' + JSON.stringify(user));
});
firebase.auth().onIdTokenChanged((user) => {
console.log('COMP DID MOUNT: TOKEN CHANGED! ' + JSON.stringify(user));
});
firebase.auth().onUserChanged((user) => {
console.log('COMP DID MOUNT: USER CHANGED! ' + JSON.stringify(user));
});
};
render() {
return (
<ReduxProvider store={store}>
<MenuProvider>
<PaperProvider>
<AppContainer />
</PaperProvider>
</MenuProvider>
</ReduxProvider>);
}
}
Normally inside the listener I have a function that dispatches a redux-action such that the authentication information is broadcast across my components. Inside those components I use the jwt token for http-requests to my backend.
Now the backend of course uses firebase to validate that token (and this is where the problem occurs after the 60 minutes since it retrieves an outdated jwt), but I think I am right to assume that the problem lies within the app since the refresh does not happen.
I'd be really glad if someone could point me to where to look, I also tried to find out in the firebase console whether a token refresh event was sent, but I could not find anything about that.
So basically:
1) Am I right to assume that the firebase.auth().onIdTokenChanged() function should be called without me doing anything else? Or is it not enough to define the listener once in the main component (also regarding the fact that other screens will be rendered on top of that due to the stack-nvigation).
2) If the code is fine, do you have any hints for where to look?
Thanks so much!
[1] https://github.com/invertase/react-native-firebase/issues/531

For anyone with the same issue, I ended up asking firebase asking for the token everytime I needed it. I still think this should not be necessary but I did not want to spend any more time analyzing why the refresh did not work automatically. So what I am doing in the app is
firebase.auth().currentUser.getIdToken().then((token) => {
fetch(url, {
method: 'GET',
headers: { Authorization: token }
})
}
¯\_(ツ)_/¯

Apparently, with getIdToken, Firebase only makes a call to its server to get a new token if the current token has expired; it does not create unnecessary requests if it does not have to.
Quite a crucial detail which can be confusing if you are not aware of it and makes you (rightfully) assume that onIdTokenChanged is a listener which you would need to use to automatically update the token ...
https://firebase.google.com/docs/reference/js/firebase.User.html#getidtoken
Returns the current token if it has not expired. Otherwise, this will refresh the token and return a new one.

Related

Deleting a user from Azure Active Directory B2C Android/Java

I have an Android application in which I'm using Azure AD B2C to authenticate users. Users login and logout of the application as needed. I would like to give the user the option to delete their own account.
I understand that I need to use the Azure AD Graph API to delete the user. This is what I have so far:
According to this link, it looks like deleting a user from a personal account (which is what the B2C users are using) is not possible. Is that correct?
Here's my code snippet for the Graph API call. Feel free to ignore it if I'm off track and there is a better way to solve this.
I believe I need a separate access token than what my app currently has (as the graph API requires other API consent). So, I'm getting the access token as follows:
AcquireTokenParameters parameters = new AcquireTokenParameters.Builder()
.startAuthorizationFromActivity(getActivity())
.fromAuthority(B2CConfiguration.getAuthorityFromPolicyName(B2CConfiguration.Policies.get("SignUpSignIn")))
.withScopes(B2CConfiguration.getGraphAPIScopes())
.withPrompt(Prompt.CONSENT)
.withCallback(getGraphAPIAuthCallback())
.build();
taxApp.acquireToken(parameters);
In the getGraphAPIAuthCallback() method, I'm calling the Graph API using a separate thread (in the background):
boolean resp = new DeleteUser().execute(authenticationResult.getAccessToken()).get();
Finally, in my DeleterUser() AsyncTask, I'm doing the following:
#Override
protected Boolean doInBackground(String... aToken) {
final String asToken = aToken[0];
//this method will be running on background thread so don't update UI from here
//do your long running http tasks here,you dont want to pass argument and u can access the parent class' variable url over here
IAuthenticationProvider mAuthenticationProvider = new IAuthenticationProvider() {
#Override
public void authenticateRequest(final IHttpRequest request) {
request.addHeader("Authorization",
"Bearer " + asToken);
}
};
final IClientConfig mClientConfig = DefaultClientConfig
.createWithAuthenticationProvider(mAuthenticationProvider);
final IGraphServiceClient graphClient = new GraphServiceClient.Builder()
.fromConfig(mClientConfig)
.buildClient();
try {
graphClient.getMe().buildRequest().delete();
} catch (Exception e) {
Log.d(AccountSettingFragment.class.toString(), "Error deleting user. Error Details: " + e.getStackTrace());
}
return true;
}
Currently, my app fails when trying to get an access token with a null pointer exception:
com.microsoft.identity.client.exception.MsalClientException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
Any idea what I need to do to provide the user the option to users to delete their own account? Thank you!
Thanks for the help, #allen-wu. Due to his help, this azure feedback request and this azure doc, I was able to figure out how to get and delete users silently (without needing intervention).
As #allen-wu stated, you cannot have a user delete itself. So, I decided to have the mobile app call my server-side NodeJS API when the user clicks the 'Delete Account' button (as I do not want to store the client secret in the android app) and have the NodeJS API call the Azure AD endpoint to delete the user silently. The one caveat is that admin consent is needed the first time you try to auth. Also, I have only tested this for Graph API. I'm not a 100% sure if it works for other APIs as well.
Here are the steps:
Create your application in your AAD B2C tenant. Create a client secret and give it the following API permissions: Directory.ReadWrite.All ;
AuditLog.Read.All (I'm not a 100% sure if we need the AuditLog permission. I haven't tested without it yet).
In a browser, paste the following link:
GET https://login.microsoftonline.com/{tenant}/adminconsent?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&state=12345
&redirect_uri=http://localhost/myapp/permissions
Login using an existing admin account and provide the consent to the app.
Once you've given admin consent, you do not have to repeat steps 1-3 again. Next, make the following call to get an access token:
POST https://login.microsoftonline.com/{B2c_tenant_name}.onmicrosoft.com/oauth2/v2.0/token
In the body, include your client_id, client_secret, grant_type (the value for which should be client_credentials) and scope (value should be 'https://graph.microsoft.com/.default')
Finally, you can call the Graph API to manage your users, including deleting them:
DELETE https://graph.microsoft.com/v1.0/users/{upn}
Don't forget to include the access token in the header. I noticed that in Postman, the graph api had a bug and returned an error if I include the word 'Bearer' at the start of the Authorization header. Try without it and it works. I haven't tried it in my NodeJS API yet, so, can't comment on it so far.
#allen-wu also suggested using the ROPC flow, which I have not tried yet, so, cannot compare the two approaches.
I hope this helps!
There is a line of code: graphClient.getUsers("").buildRequest().delete();
It seems that you didn't put the user object id in it.
However, we can ignore this problem because Microsoft Graph doesn't allow a user to delete itself.
Here is the error when I try to do it.
{
"error": {
"code": "Request_BadRequest",
"message": "The principal performing this request cannot delete itself.",
"innerError": {
"request-id": "8f44118f-0e49-431f-a0a0-80bdd954a7f0",
"date": "2020-06-04T06:41:14"
}
}
}

Is there a way to identify platform.resume event was called by camera?

I have an issue with my ionic v2 app, I have subscribed to platform.resume on app.component.ts where I have an redirection, based on user status, every time a picture is taken and app returns from camera platform.resume is called and the redirection breaks my app flow. Is there a way to identify resume event called by cordova-plugin-camera?
Token validation, redirects back to login page
I suggest you use Events for triggering redirection based on login/logout
events.publish('token:received', token);
and in your app.component.ts
events.subscribe('token:received',(token)=>{
//redirect
});
An internal function like platform.resume may be used internally by any number of plugins or components.
If i got it right, you can use the returned promise and then redirect to another page.
Camera.getPicture(options).then((imageData) => {
let base64Image = 'data:image/jpeg;base64,' + imageData;
this.NavCtrl.push(TheRedirectPage, ParamsYouDLikeToPass);
// i think you can also call platform.resume here, if you want, but i'd stick with the promise return.
}, (err) => {
// Handle error
});
There's no need to check with the platform.resume, since when it returns from camera it's all ready for you to do your stuff.

How to wait for FirebaseAuth to finish initializing?

Integrated Firebase Authentication to my app. Everything seems to be fine, until I noticed the issue wherein I open the app, it shows me the login screen, even though there is already a user signed in.
Exiting the app and launching it again solves the problem (app no longer asks the user to sign in again), though it provides the users a very bad experience.
I've read the docs, and apparently, calling FirebaseAuth.getInstance().getCurrentUser() might return null if auth haven't finished initializing. I'm assuming that's the reason why the issue happens. So I wonder if there is a way to wait for FirebaseAuth to finish its initialization before I can call getCurrentUser()?
Using the modern API, simply wait for auth state to change:
firebase.auth().onAuthStateChanged(user => init_your_app);
You can create function which returns promise, and await where needed:
function getCurrentUser(auth) {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(user => {
unsubscribe();
resolve(user);
}, reject);
});
}
Check this - https://github.com/firebase/firebase-js-sdk/issues/462
For React users who are asking the same question :
react-firebase-hooks has a hook called useAuthState that can prove to be helpful for checking the state of firebase auth.
Following is a very common use-case I think.
import {useAuthState} from "react-firebase-hooks/auth";
import React from "react";
export default function AllowIfAuth() {
const [user, loading, error] = useAuthState(auth);
if (loading) {
return <div> Loading... </div>;
} else if (user) {
return <div> Some Content </div>;
} else if (error) {
return <div>There was an authentication error.</div>;
} else {
return <LoginPage/>;
}
}
Cannot believe that I have to do this (as there is no official API by Google). I have to use one additional boolean...
let isAuthReady = false
firebase.auth().onAuthStateChanged((user) => {
store.commit('setUser', user)
if (!isAuthReady) {
isAuthReady = true
init()
}
})
You need a listener that tell you when the authentication is done, this link might help you : FirebaseAuth.AuthStateListener
You can call getCurrentUser() to know, if, at this very moment in time, it's known that a user is logged in. If you need some bit of code to be triggered when the user logs in or out, you'll need to register a listener to be notified when there is a change. There is a method addAuthStateListener() that will trigger on that change. Be sure to read that javadoc to understand exactly the conditions when it will fire. In side the AuthStateListener that you add, you will be able to call getCurrentUser() to know if the user is logged in.

Phonegap- Facebook connect. Api calls doesn't fire any callback

I'm using Phonegap Build.
First of all, I need to say that this error doesn't happen if I try it with an administrator account of the app.
It only happen if a normal user tries to login in my app.
This is my code so far.
var facebookPermissions = ['public_profile', 'email', 'user_about_me', 'user_website'];
$(document).on('click', '#btnFacebook', function() { //click
facebookConnectPlugin.login(facebookPermissions, onFacebookLoginSuccess, onFacebookLoginError)
});
function onFacebookLoginSuccess(userData) {
alert("userData: " + JSON.stringify(userData));
facebookConnectPlugin.api('me', facebookPermissions,
function(result) {
alert("Result: " + JSON.stringify(result));
});
};
I'm the app's administrator and every work as expected... no trouble at all.
But if another user tries to login, the login works well, but there is no response for the api request.
Only the first alert is displayed
I also tried with this parameters
facebookConnectPlugin.api("/?fields=id,email",facebookPermissions,
facebookConnectPlugin.api("",facebookPermissions,
But nothing changes
I'm kinda lost here, and I dont know what else to try.
Your App may be in sandbox mode, activate it in the "Settings & Review" section
user_website needs to get reviewed. Without Login Review, it will only work for users with a role in the App.

Sinatra + omniauth + Android, advice sought

I'm developing a Sinatra app for which I'd like to use OmniAuth. So far, I have something similar to this for the web app:
http://codebiff.com/omniauth-with-sinatra
I'd like the web app to be usable via Android phones which would use an API, authenticating by means of a token. The development of an API seems to be covered nicely here:
Sinatra - API - Authentication
What is not clear is now I might arrange the login procedure. Presumably it would be along these lines:
User selects what service to use, e.g. Twitter, FaceBook &c., by means of an in-app button on the Android device.
The Android app opens a webview to log in to the web app.
A token is somehow created, stored in the web app's database, and returned to the Android app so that it can be stored and used for subsequent API requests.
I'm not very clear on how point 3 might be managed - does anyone have any suggestions?
As no-one seems to have any suggestions, here's what I've come up with so far. I don't think it's very good, though.
I've added an API key to the user model, which is created when the user is first authenticated:
class User
include DataMapper::Resource
property :id, Serial, :key => true
property :uid, String
property :name, String
property :nickname, String
property :created_at, DateTime
property :api_key, String, :key => true
end
....
get '/auth/:name/callback' do
auth = request.env["omniauth.auth"]
user = User.first_or_create({ :uid => auth["uid"]},
{ :uid => auth["uid"],
:nickname => auth["info"]["nickname"],
:name => auth["info"]["name"],
:api_key => SecureRandom.hex(20),
:created_at => Time.now })
session[:user_id] = user.id
session[:api_key] = user.api_key
flash[:info] = "Welcome, #{user.name}"
redirect "/success/#{user.id}/#{user.api_key}"
end
If the authorisation works then the api_key is supplied to the Android app, which will presumably store it on the device somewhere:
get '/success/:id/:api_key', :check => :valid_key? do
user = User.get(params[:id],params[:api_key])
if user.api_key == params[:api_key]
{'api_key' => user.api_key}.to_json
else
error 401
end
end
All API calls are protected as in the link in my original post:
register do
def check (name)
condition do
error 401 unless send(name) == true
end
end
end
helpers do
def valid_key?
user = User.first(:api_key => params[:api_key])
if !user.nil?
return true
end
return false
end
end
For public use I'll only allow SSL connections to the server. Any suggestions for improvement would be welcome.

Categories

Resources