MSAL ANDROID : MultiAccountMode issue with Logout - java

I have an android native application using MSAL library to authenticate. We are facing issues to logout from application after login. While logout, it displays a screen where the already logged in email displays, tapping on that allows the user to login to the application with out a password. The application is configured as MultiAccount mode. Below is the code for logout.
removeAccountButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (mMultipleAccountApp == null) {
return;
}
/**
* Removes the selected account and cached tokens from this app (or device, if the device is in shared mode).
*/
mMultipleAccountApp.removeAccount(accountList.get(accountListSpinner.getSelectedItemPosition()),
new IMultipleAccountPublicClientApplication.RemoveAccountCallback() {
#Override
public void onRemoved() {
Toast.makeText(getContext(), "Account removed.", Toast.LENGTH_SHORT)
.show();
/* Reload account asynchronously to get the up-to-date list. */
loadAccounts();
}
#Override
public void onError(#NonNull MsalException exception) {
displayError(exception);
}
});
}
});
It always display the toast "Account removed", but it is actually not. Any help is appreciated!

Edit 1 - 12/12/2022
#Tinjzz This Answer is almost your exact scenario.
Question Description
"accounts are removed successfully, but when signing in again and the microsoft sign in intent is opened, the accounts can just be clicked to sign in without password"
#Rutha answer "This is happening because MSAL automatically refreshes your token after expiration. When user opens your app it checks if that token is already present and valid."
"you need to remove the cache as well to remove the account from the cache, find the account that need to be removed and then call PublicClientApplication.removeAccount()"
In a later answer, #Rutha notes "On Android we basically don't have any control on the cookies" "If you want the user to enter the password again then you should do this: AcquireTokenInteractive(scopes).WithPrompt(Prompt.ForceLogin);
Old Response
From the code posted, it looks like you are using an approach similar to this site with a separate loadAccounts() method. "Step 5.2: Load accounts"
However, in the MS MSAL Single and Multi-Account page, it notes:
"If your app is configured to use a broker, and a broker is installed on the device, the account won't be removed from the broker when you call removeAccount. Only tokens associated with your client are removed."
MS specifically recommends using "Call getAccounts to get a list of accounts currently known to the app."
So, the current setup may be using loadAccounts() per the first link, yet MS actually recommends getAccounts that specifically addresses only the internal MSAL token system.

Related

Unique Firebase Instance-Id for every user account

I'm unsure how to handle different user accounts on the same device appropriately, as Firebase only creates one Instance-Id per device.
Therefore, I though that it would be possible to delete the Instance-Id when the user is logged out and to create a new one when a new user is logged in.
On login:
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> System.out.println(task.getResult().getToken()));
On logout:
FirebaseInstanceId.getInstance().deleteInstanceId()
Does Firebase ensure that the Instance-Id will be unique, even if it is generated multiple times on the same device?
The reasons why I prefer this approach are that it's simple to unsubscribe the user from all topics at once and moreover push notifications can be addressed to a specific user.
Does Firebase ensure that the Instance-Id will be unique, even if it is generated multiple times on the same device?
Regenerating an Instance ID will indeed always result in a unique value. As long as you ensure that you delete the Instance ID when the user logs out, you'll get a fresh token next time around.
To ensure your token registry (the place where you store tokens) doesn't accumulate too many outdated tokens, be sure to either remove the token when the user signs out, or when you find out a token is no longer valid when sending messages. See my answer to this question.
If you want to have Unique FCM Instance Id for each user on a device, you should call FirebaseInstanceId.getInstance().deleteInstanceId()
on each logout on an account.
Then in next FirebaseInstanceId.getInstance().instanceId
call, you'll get a new unique Intance Id
But there is a point that you need to know. Call deleteInstanceId() on a new Thread, not MainThread
The best way to handle your issue, is to use topic messaging.
From the docs:
Based on the publish/subscribe model, FCM topic messaging allows you to send a message to multiple devices that have opted in to a particular topic. You compose topic messages as needed, and FCM handles routing and delivering the message reliably to the right devices.
When the user logs in the app, you can subscribe to a certain topic, for example:
FirebaseMessaging.getInstance().subscribeToTopic("weather")
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
String msg = getString(R.string.msg_subscribed);
if (!task.isSuccessful()) {
msg = getString(R.string.msg_subscribe_failed);
}
Log.d(TAG, msg);
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
});
Then the user can receive notifications based on the topic name. Then when the user clicks the logs out button, you can call the following:
FirebaseMessaging.getInstance().unsubscribeFromTopic("weather");

Display Twitter user profile without logging in?

I would like to display a twitter user profile without having the app prompt the phone user for creating an account or login information.
public void openTwitter(View view){
try
{
// Check if the Twitter app is installed on the phone.
getPackageManager().getPackageInfo("com.twitter.android", 0);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClassName("com.twitter.android", "com.twitter.android.ProfileActivity");
intent.putExtra("user_id", 01234567L);
startActivity(intent);
}
catch (PackageManager.NameNotFoundException e)
{
// If Twitter app is not installed, start browser.
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://twitter.com/xxx")));
}
}
The code opens the twitter app and prompts the phone user for account creation before viewing profile xxx. I would like to simply view profile xxx without creating an account or logging in.
Welcome to StackOverflow. Please check out how to ask a good question first. Your question doesn't describe a specific problem and therefore can't be "solved". It also isn't specific enough for anyone to point you in the direction of methods to use to achieve your goal.
That being said, if you just want to display anyone's profile, implement the Twitter API in your app and make the right REST calls and you should get the information you want to display.
If you want the user's profile specifically, there's literally no way around the user logging into their account with the API, unless they previously define their username in your app.
If you just want to display the profile and don't care about designing the information yourself, you could use a WebView to open the link to the profile you want to open, or use UIApplication.shared.open to open a link outside your own app.

Getting the List of registered devices from fcm for my site

I am new to FCM and mobile coding. This is what I am trying to achieve:-
Develop an app to allow users to select some events.
I have a site that will loop through the events for users. When an event is close to its start time or some messages were created for
that event, I will send a FCM message to all the devices that
registered to that event.
I am confused on the implementation. This is what I am thinking:
When my app starts, I can register for push notification and it will return a devicetoken.
When user saves an event I can pass the devicetoken back to the server to re remember it.
In my site's code, I have some code to detect if an event is close and sends notification base on the devicetoken linked to
that event.
Is this about the right way to code? But if the user restarts the app or restarts the phone, isn't that I will get a new devicetoken? So I need to store some other identifier to identify a user (e.g. google plus user name)?
Is this about the right way to code? But if the user restarts the app or restarts the phone, isn't that I will get a new devicetoken? So I need to store some other identifier to identify a user (e.g. google plus user name)?
Yes it is right, and check the below for the questions:
You need to use FirebaseInstanceIdService it is used to handle the creation, rotation, and updating of registration tokens.
To retrieve the token of a device use this:
FirebaseInstanceId.getInstance().getToken()
The above token that you get may change in the following situations:
Instance ID is stable except when:
App deletes Instance ID
App is restored on a new device
User uninstalls/reinstall the app
User clears app data
Then using this method inside FirebaseInstanceIdService, it will refresh the token whenever any one of the situation happens:
#Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(refreshedToken);
}
After you've obtained the token, you can send it to your app server and store it using your preferred method.
more info here: https://firebase.google.com/docs/cloud-messaging/android/client

Android 8.0 Oreo - Accounts

In my app, I need to known if there is any Google account or any Samsung account.
Up to Android 7 it was easy to get this information with something like:
Account[] accounts = AccountManager.get(getContext())
.getAccountsByType("com.google")
But with the event of Oreo this does not work anymore.
EDIT: see official information on this subject:
In Android 8.0 (API level 26), apps can no longer get access to user accounts unless the authenticator owns the accounts or the user grants that access. The GET_ACCOUNTS permission is no longer sufficient. To be granted access to an account, apps should either use AccountManager.newChooseAccountIntent() or an authenticator-specific method. After getting access to accounts, an app can can call AccountManager.getAccounts() to access them.
Android 8.0 deprecates LOGIN_ACCOUNTS_CHANGED_ACTION. Apps should instead use addOnAccountsUpdatedListener() to get updates about accounts during runtime.
For information about new APIs and methods added for account access and discoverability, see Account Access and Discoverability in the New APIs section of this document
I spent half a day to find a solution to my need, without success.
I've found information claiming that now the only way to access to accounts is to use AccountPicker like this:
AccountPicker.newChooseAccountIntent(null, null, new String[]{"com.google"},true, null, null, null, null);
But this does respond to my problem. To be clear I only need to know if an account exists for a certain type (Google, Samsung...) I do not need to know how much if so and do not need accounts information.
Using "android.permission.READ_CONTACTS" permission, and
Account[] accounts = AccountManager.get(getContext())
.getAccountsByType("com.google")
working again in android Oreo
As you already said, there's no way to read other accounts if the user didn't give you the permission to do so. The permission now is provided not only with the run-time permission but even with the account picker, i.e. an account is visible to your app only if the user selected the account after you called the account picker. This new restriction is exactly to avoid what you are trying to do: read all user accounts. There's no solution to your problem, the only thing you can do is to present the picker to the user and let him select all the accounts, not the best user experience however.
Edit: starting from Google Play Services 11.6 there's now a new method requestGoogleAccountsAccess() to get all Google accounts.
To get the installed google accounts on a device running Oreo+ (8+) with this code
Account[] accounts = AccountManager.get(getContext()).getAccountsByType("com.google")
You need to first call
https://developers.google.com/android/reference/com/google/android/gms/auth/GoogleAuthUtil.html#requestGoogleAccountsAccess(android.content.Context)
Please add the following dependency first
com.google.android.gms:play-services-auth:16.0.0
The call requestGoogleAccountsAccess() throws an exception which you can cast (after checking) to UserRecoverableAuthException and get an intent from it to start with startActivityForResult
Here is some example code, working on Android Oreo
// call this on a background thread!
private void requestGoogleAccountAccess() throws Exception
{
googleAccountAccessGranted = GoogleAuthUtil.requestGoogleAccountsAccess(this);
Log.i(TAG, "googleAccountAccessGranted: " + googleAccountAccessGranted);
}
// exception handler after calling method above
private void handleAuthResult(Throwable e)
{
if (e instanceof UserRecoverableAuthException)
{
UserRecoverableAuthException authException = (UserRecoverableAuthException) e;
startActivityForResult(authException.getIntent(), AUTH_PERMISSION_REQUEST);
}
else
{
Log.e(TAG, "Cannot request Google Account Access", e);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == AUTH_PERMISSION_REQUEST)
{
Log.i(TAG, "Google Auth Permission Result");
if (resultCode == Activity.RESULT_CANCELED)
{
Log.w(TAG, "User Cancelled Play Services Auth Request.")
}
else if (resultCode == Activity.RESULT_OK)
{
Log.d(TAG, "User accepted Play Services Auth Request.");
// call the following line again on a background thread. the call now returns a boolean instead of throwing an exception
// googleAccountAccessGranted = GoogleAuthUtil.requestGoogleAccountsAccess(this);
}
}
}
It's a bit strange why Google decided themselves for this "architecture". Why not return a Task, etc.
But this is how you get it working.
Of course this code needs proper exception handling which I left out for readability.

GWT RequestFactory-based authentication

I am experimenting with GWT RequestFactory (RF) for the first time and am trying to implement a simple sign-in screen and authentication system (not using anything fancy, just fiddling around with the basics here). The basic user experience I'm looking to achieve is pretty par for the course:
The user will be presented with a sign-in screen (email and password and "Sign In" button). When they click the button, I want to use RF to send their credentials to the server (using ValueProxy since these are not entities) and authenticate them. If the credentials were correct, they are now "signed in" to the system, and the GWT app will download a whole new module and they'll be redirected to their account's main menu. If the credentials were incorrect, I want to send back a String explaining that the email or password was incorrect, and they are still "signed out" of the app.
Regarding this question that I posted yesterday, I have now figured out how to use RF to expose a SignInOutService which has a signIn(SignIn) method for attempting to sign the user in, and a signOut(SignOut) method for signing the user out of the system. But now I'm actuallly trying to implement that service, and here's what I have so far:
public class DefaultSignInOutService {
// Try to sign the user into the system.
public String signIn(SignIn signIn) {
// The SignIn object contains the email/hashed password the user tried
// signing-in with, as well as other metadata I'm looking to store for
// security purposes (IP address, user agent, etc.).
String email = signIn.getEmail();
String hashedPassword = signIn.getHashedPassword();
// This will be set to a non-null value if the sign-in attempt fails.
// Otherwise (on successful sign-in) it will stay NULL. The client-side
// handler will know what to do with the UI based on this value.
String failReason = null;
// For this simple example, the password is "12345" and below is it's MD5 hash.
// Hey! That's the combination on my luggage!
if(!"skroob#spaceballs.example.com".equals(email) || !"827ccb0eea8a706c4c34a16891f84e7b".equals(hashedPassword))
failReason = "Login failed; incorrect email or password.";
else {
// Log the user into the system...
// TODO: How?
}
return failReason;
}
// Sign the user out of the system.
public void signOut(SignOut signOut) {
// The SignOut object should reference the user attempting to sign out, as well as a reason
// for why the sign out is occurring: the user manually requested to be signed out, or they
// "expired" due to inactivity or navigating the browser away from the app, and so the system
// auto-signed them out, etc.
// TODO: How?
return;
}
}
So now, I've implemented my super-simple email/password check, and I'm ready to write the code that somehow signs the user into the app (so that they're not presented with a login screen over and over again). And I'm choking on what to do next.
Issues I'm trying to find solutions for:
Is GWT RF somehow session- or token-based? If so, under the commented line "Log the user into the system...", what code can I write that says "this user is now authenticated, set some cookie or session variable to make it so!"? I ask this because once they sign in and are routed to the new module and main menu, GWT will need a way to authenticate every subsequent RF request thereafter.
What does the signOut() method need to reset/clear/nullify in order to clear these cookies/session vars? In other words, how do I actually sign the user out, so if they try to go to the URL for their main menu (which again is only accessible if they're signed in), they'll be redirected to the sign-in screen?
How could I implement a 15-min inactivity timeout, where the user is automatically signed out of the app after a certain length of time? I think this answer will become more obvious once I see how questions #1 and #2 above work.
I was told that I may need to have two servlets and/or filters: one for handling unauthenticated RF requests (while a user is signed out or has not yet signed in), and one for handling authenticated RF requests (once the user is actively signed in). But I can't see how they fit into the overall picture here.
The easiest way is to store your authentication details in session.
public String signIn(SignIn signIn) {
...
if(!"skroob#spaceballs.example.com".equals(email) || !"827ccb0eea8a706c4c34a16891f84e7b".equals(hashedPassword))
failReason = "Login failed; incorrect email or password.";
else {
RequestFactoryServlet.getThreadLocalRequest().getSession().setAttribute("auth", signIn);
}
return failReason;
}
public void signOut(SignOut signOut) {
RequestFactoryServlet.getThreadLocalRequest().getSession().removeAttribute("auth");
return;
}
On every request you can check if SignIn object is still present in session:
SignIn signIn = null;
final Object userObject = RequestFactoryServlet.getThreadLocalRequest().getSession().getAttribute("auth");
if (userObject != null && userObject instanceof SignIn) {
signIn = (SignIn) userObject;
}
In case of absence of this object you should cancel the request and redirect user to login page.

Categories