Never get AccountManager.KEY_INTENT from getAuthToken request - android

On JellyBean device.
I'm following this to request an oauth2 token, e.g.
AccountManager am = AccountManager.get(getActivity());
am.invalidateAuthToken(MY_AUTH_TOKEN_TYPE, null);
am.getAuthToken(aGoogleAccount, MY_AUTH_TOKEN_TYPE, null, this,
new OnTokenAcquired(), new Handler(new OnError()));
and then make the check as per the later code sample:
private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
#Override
public void run(AccountManagerFuture<Bundle> result) {
Bundle bundle = result.getResult();
...
Intent launch = (Intent) bundle.get(AccountManager.KEY_INTENT);
if (launch != null) {
startActivityForResult(launch, 0);
return;
}
}
}
I never get a KEY_INTENT. I understand the following:
There may be many reasons for the authenticator to return an Intent. It may be the first time the user has logged in to this account. Perhaps the user's account has expired and they need to log in again, or perhaps their stored credentials are incorrect. Maybe the account requires two-factor authentication or it needs to activate the camera to do a retina scan. It doesn't really matter what the reason is. If you want a valid token, you're going to have to fire off the Intent to get it.
However, the getAuthToken always results in the permission screen, or login screen, appearing before the code hits the run method at which point the token is valid. I've tried:
Turning on 2 step authentication. Account login is requested before run so always have the token in run.
Changing the password on the server. Again account login is requested before run so always have the token in run.
Don't have the ability to try a retina scan so somewhat at a loss.
EDIT 1 The problem I have is that there may be a circumstance where I will get a KEY_INTENT and so I'd rather test this code path now rather when when it's out in the field.
Thanks in advance.
Peter.

Had a chance to do something similar on a project. This not the exactly the same as your code, and I still say that the callback docs have too many 'maybes' to be certain of how it should work, but if you use this method passing false for notifyAuthFailure, you will get an intent with the re-login screen if you change the password or enable 2FA. This is for ClientLogin, but should work similarly for OAuth 2 (not tested though). Something like:
// using Calendar ClientLogin for simplicity
Bundle authResult = am.getAuthToken(account, "cl", false, null, null).getResult();
if (authResult.containsKey(AccountManager.KEY_INTENT)) {
Intent authIntent = authResult.getParcelable(AccountManager.KEY_INTENT);
// start activity or show notification
}

I think you need to call getResult(), like this:
Intent launch = (Intent)result.getResult().get(AccountManager.KEY_INTENT);

You're using the version of getAuthToken which uses an Activity to invoke the access authorization prompt. That version of getAuthToken does not return an intent since the supplied activity is used to launch the corresponding intent. If you want to manually launch an intent, use the version of getAuthToken that was deprecated in API level 14. See the following for more information:
http://developer.android.com/reference/android/accounts/AccountManager.html#getAuthToken%28android.accounts.Account,%20java.lang.String,%20boolean,%20android.accounts.AccountManagerCallback%3Candroid.os.Bundle%3E,%20android.os.Handler%29

Related

Auth0 Android - How to renew id_token?

I have Auth0 and cognito all wired up. I can login to the app and everything works great. Until the id_token expires, then everything fails.
What is the ODIC Conformant way to refresh/renew id_tokens?
The following code only refreshes the access token for me.
Initial auth:
WebAuthProvider.login(auth0CredentialsManager.getAuth0Account())
.withScope("openid email profile offline_access") // is offline_access required?
.withResponseType(ResponseType.ID_TOKEN | ResponseType.CODE | ResponseType.TOKEN) // I'm not sure if this is necessary to specify...
.withParameters(params)
.withAudience(String.format("https://%s/userinfo", BuildConfig.AUTH0_DOMAIN))
.start(Auth0LoginActivity.this, new AuthCallback() {
#Override
public void onFailure(#NonNull Dialog dialog) {
// Show error Dialog to user
dialog.show();
onAuth0Failure(null);
}
#Override
public void onFailure(AuthenticationException exception) {
Bugsnag.notify(exception);
onAuth0Failure(exception);
// Show error to user
}
#Override
public void onSuccess(#NonNull Credentials credentials) {
handleSignIn(credentials); //this call saves credentials using SecureCredentialsManager. If you want to see it let me know
}
});
And when I need to get a fresh id_token, I'm trying this (but it only refreshes the access token):
// auth0CredentialsManager is SecureCredentialsManager
auth0CredentialsManager.getCredentials(new BaseCallback<Credentials, CredentialsManagerException>() {
#Override
public void onSuccess(Credentials credentials) {
auth0CredentialsManager.saveCredentials(credentials);
// do more stuff here... except the id_token is expired (access token is not).
}
Do I:
Need to request offline_access or is that only for access tokens? (in my testing, it appears to only refresh access tokens).
Research/Things I've tried:
https://auth0.com/learn/refresh-tokens/ seems to indicate I just set openid scope, but I'm doing that and only getting the intial id_token. Do I need to refresh the token with prompt=none parameters and make another login call? https://auth0.com/docs/api-auth/tutorials/silent-authentication seems to indicate silent login is only needed for single page applications though.
AuthenticationAPIClient.delegationWithRefreshToken looks like it would be the right call to make, but it always throws com.auth0.android.authentication.AuthenticationException: An error occurred when trying to authenticate with the server.
Okay, here's what I've learned.
As of version 1.18.0, the call to getCredentials does NOT consider id token expiration. It only checks if the access token is expired, and if it is, it will then refresh the id_token and access token. Unfortunately the access token expiry is locked in at 24 hours unless you do additional work.
Make sure you have setOIDCCompliant to true when you create your Auth0Account instance, or else the call to renew will hit the /delegation endpoint which is now deprecated and only works if your client id is setup to support non oidc compliant calls.
One other thing to be aware of that's somewhat off topic. The SecureCredentialsManager clears out credentials if anything appears to go wrong. This isn't acceptable for me, as simply being offline and being unable to make the call causes the credentials to be cleared.

AppAuth Relogin

After some back and forth I finally got this to work but I had to use version 0.2.0 because I followed the google guide presented in the Readme.
Anyway, Im struggling with handling what will happen when the oAuth token times out. Then it needs to open the browser again to log in or is there a background process available for this as it automatically redirects back to the app because the server remembers the user so there is no need for a new username/password input?
Im getting a refresh token like this :
if(mAuthService == null){
mAuthService = new AuthorizationService(context);
}
mAuthState.performActionWithFreshTokens(mAuthService, new AuthState.AuthStateAction() {
#Override public void execute(
String accessToken,
String idToken,
AuthorizationException ex) {
if (ex != null) {
return;
}
// Getting the access token...
}
});
Thats working fine but after the user is idle for some time it wont work. How to handle this properly?
Solution for my problem was this:
I changed to using offline_access for the token in the scope. Depending on the site/service you're login into if they accept it or not. For me it was accepted and will keep the user logged in for a long time and removes the need to re-login.

AzureAD for Android throws ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED

I have an app in which user authentificates in Office365 with AzureAD library for Android.
It works well, users can authentificate and work with the app. Unfortunately, after a while they start hitthing AuthenticationException with ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED as an error code.
I checked the source code of AzurelAD. The only place, which is throughing this issue is acquireTokenAfterValidation() method:
private AuthenticationResult acquireTokenAfterValidation(CallbackHandler callbackHandle,
final IWindowComponent activity, final boolean useDialog,
final AuthenticationRequest request) {
Logger.v(TAG, "Token request started");
// BROKER flow intercepts here
// cache and refresh call happens through the authenticator service
if (mBrokerProxy.canSwitchToBroker()
&& mBrokerProxy.verifyUser(request.getLoginHint(),
request.getUserId())) {
.......
Logger.v(TAG, "Token is not returned from backgroud call");
if (!request.isSilent() && callbackHandle.callback != null && activity != null) {
....
} else {
// User does not want to launch activity
String msg = "Prompt is not allowed and failed to get token:";
Logger.e(TAG, msg, "", ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED);
callbackHandle.onError(new AuthenticationException(
ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED, msg));
}
// It will start activity if callback is provided. Return null here.
return null;
} else {
return localFlow(callbackHandle, activity, useDialog, request);
}
}
My source code:
authenticator.getAccessTokenSilentSync(getMailService());
public class Authenticator {
..............
public String getAccessTokenSilentSync(ServiceInfo serviceInfo) {
throwIfNotInitialized();
return getAuthenticationResultSilentSync(serviceInfo).getAccessToken();
}
private AuthenticationResult getAuthenticationResultSilentSync(ServiceInfo serviceInfo) {
try {
return authenticationContext.acquireTokenSilentSync(
serviceInfo.ServiceResourceId,
Client.ID,
userIdentity.getAdUserId());
} catch (AuthenticationException ex) {
// HERE THE EXCEPTION IS HANDLED.
}
}
..............
}
Stacktrace I'm getting:
<package name>.data_access.error_handler.AuthenticationExceptionWithServiceInfo: Refresh token is failed and prompt is not allowed
at com.microsoft.aad.adal.AuthenticationContext.localFlow(AuthenticationContext.java:1294)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenAfterValidation(AuthenticationContext.java:1229)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenLocalCall(AuthenticationContext.java:1123)
at com.microsoft.aad.adal.AuthenticationContext.refreshToken(AuthenticationContext.java:1609)
at com.microsoft.aad.adal.AuthenticationContext.localFlow(AuthenticationContext.java:1261)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenAfterValidation(AuthenticationContext.java:1229)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenLocalCall(AuthenticationContext.java:1123)
at com.microsoft.aad.adal.AuthenticationContext.refreshToken(AuthenticationContext.java:1609)
at com.microsoft.aad.adal.AuthenticationContext.localFlow(AuthenticationContext.java:1261)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenAfterValidation(AuthenticationContext.java:1229)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenLocalCall(AuthenticationContext.java:1123)
at com.microsoft.aad.adal.AuthenticationContext.access$600(AuthenticationContext.java:58)
at com.microsoft.aad.adal.AuthenticationContext$4.call(AuthenticationContext.java:1072)
at com.microsoft.aad.adal.AuthenticationContext$4.call(AuthenticationContext.java:1067)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
Version of AzureAD library I'm using: 1.1.7 (to prevent blaming too old version - I've checked the changelist since from 1.1.7 to 1.1.11 and haven't found anything related to question)
Problem: Right now, I'm treating this error, as a signal to through the user to the login screen. In my opinion, it leads to a poor experience for the user. The fact that it happens very often and affects many users make it even worse.
Question: Is there anything I can do different to avoid this AuthenticationException or workaround it somehow (i.e. avoid user enters credentials once again).
Have you verified that AuthenticationContext.acquireTokenSilentSync() is truly the method that you wish to invoke?
The docs indicate that this method will explicitly not show a prompt. From the docs:
This is sync function. It will first look at the cache and automatically checks for the token expiration. Additionally, if no suitable access token is found in the cache, but refresh token is available, the function will use the refresh token automatically. This method will not show UI for the user. If prompt is needed, the method will return an exception.
The refresh token you are issued should last two weeks per this AAD book. After the refresh token expires users are expected to reauthenticate. Can you inspect net traffic with Fiddler or Charles and inspect the expiry of the tokens? If you can verify that the tokens are failing to refresh before their expiry it may indicate a bug in the AD library.
To clarify the difference in methods on AuthenticationContext - there are two categories of methods: "silent" methods (which will not present a dialog to user in the event that they need to reauthenticate), and non-silent. Non-silent methods will, in the event of requiring reauthentication (or consent) from the user, start a new Activity containing the AAD login. At that point the authentication flow is restarted.
Additionally, if you make changes to your application's registration in Azure such as adding new permission scopes your users will be required to re-grant consent for the application to continue to handle their data.
This is because you need to refresh your token and implement this in your code so the user won't be prompt to login every time the access token is expired. please check out how to implement refresh token here:
https://msdn.microsoft.com/en-us/library/azure/dn645538.aspx
Hope this helps.

SyncAdapter not getting called on "Network tickle"

Overview
I follwed Google's tutorial on using SyncAdapter without using ContentProvider, Authenticator..etc. It works perfectly when I call onPerformSync(...) when I need to do an "upload" to the server via de SyncAdapter.
Now, as you can imagine, I need to do downloads from the server as well (yes I understand that it would be better to use Google's Cloud Messaing system, but this is the set up I was given, and I can't change that). For that, instead of doing periodical syncs, I want to make use of the "Network tickle" Android system carries out when there is a network available. For that I state the following:
ContentResolver.setIsSyncable(accounts[0], AUTHORITY, 1);
ContentResolver.setSyncAutomatically(accounts[0], AUTHORITY, true);
But my SyncAdapter is just not getting called. Looking into other stackOverFlow questions, there seem to be a problem if targetting API 10 or below with SyncAdapter and that you must add an account explicitly before calling the before methods. So I ended up with this:
AccountManager accountManager = (AccountManager) context.getSystemService(ACCOUNT_SERVICE);
Account[] accounts = accountManager.getAccounts();
if(accounts.length == 0){ //ADD DUMMY ACCOUNT
Account newAccount = new Account(ACCOUNT, ACCOUNT_TYPE);
ContentResolver.setIsSyncable(accounts[0], AUTHORITY, 1);
ContentResolver.setSyncAutomatically(accounts[0], AUTHORITY, true);
accountManager.addAccountExplicitly(newAccount, null, null);
}else{
accounts = accountManager.getAccounts();
ContentResolver.setIsSyncable(accounts[0], AUTHORITY, 1);
ContentResolver.setSyncAutomatically(accounts[0], AUTHORITY, true);
}
Now this code gets executed when the user signs in, or if the application was killed and is started up again. I am wondering, should I call setIsSyncable and setSyncAutomatically only when I add the dummyAccount the very first time?
Also, part of the "goodiness" of the SyncAdapter is that it will keep on making the calls in case of an exception. But I don't quite understand how this goes about, so instead I have this:
private void profileUpdate(){
TableAccounts db = TableAccounts.getInstance(getContext());
boolean isRecordDirty = db.isRecordDirty(signedInUser);
if(isRecordDirty){
if(server.upDateUserProfile(signedInUser)){
db.cleanDirtyRecord(signedInUser);
turnOffPeriodicSync();
}else{
this.turnOnPeriodicSync(this.sync_bundle);
}
}else
turnOffPeriodicSync();
}
As you can see, depending on the result of my upload to the server, I turn on or off a periodic sync.
Since the accountManager.getAccounts[] return every account on the device, I think nothing guarantee that the account[0] is your app's account (aka, has the ACCOUNT_TYPE of your package name).
-- You could call addAccountExplicitly() in any case, if it is existed, then nothing happens.
Account account = new Account(ACCOUNT, ACCOUNT_TYPE);
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(account, null, null)
context.getContentResolver().setSyncAutomatically(account, AUTHORITY, true);
Disclaimer: I might be mistaken.
On top everything you did, you also have to call ContentResolver.requestSync() from within your app every time you need a sync to run. The sync will not run immediately though, because Android is trying to cluster network activity.
Or you can use Googles Cloud Messaging API to request a sync, but I don't know a whole lot about that.

Facebook API's method: isSessionValid() always returns true

I am using the official facebook SDK for Android and I am facing some issues regarding session management. Specifically, I don't know how to test whether the session is still valid or not.
First I initialize and authorize a connexion to facebook:
Facebook facebook = new Facebook(context.getString(R.string.facebook_app_id));
facebook.authorize(...); // Works ok
Then, I want to disconnect the user:
facebook.logout(context);
But once I have done that, the following still returns true:
facebook.isSessionValid();
Why does it return true?
Anything I'm doing wrong?
Thanks!
Maybe you encountered an older bug. Is your Facebook SDK up to date?
According to the source, logout calls setAccessToken(null);. And if the token is null, isSessionValid() should return false. Here is it's code for reference:
public boolean isSessionValid() {
return (getAccessToken() != null) &&
((getAccessExpires() == 0) ||
(System.currentTimeMillis() < getAccessExpires()));
}
The getter/setter doesn't do any null-checks either, so the call in logout() should have the desired effect.
Im not sure how you integrated facebook in your app but are you checking the access token from the shared preferences? If you are I would check to see that you are actually setting the new access token. For example, after the log out you would want to write out that new access token (null) so next time it would prompt the user to log in again.

Categories

Resources