I am trying to sign in to my android application using Microsoft Single Sign on, using MSAL implemenation as provided here.
In onCreate
mApp = new PublicClientApplication(this.getApplicationContext(), API.CLIENT_ID, API.AUTHORITY);
When the user presses "Sign in with Microsoft" option, I call the method to acquire token as
mApp.acquireToken(this, getResources().getStringArray(R.array.msal_scopes), getAuthInteractiveCallback());
After handling redirect request in onActivityResult, I grab the authentication response at the callback as
private AuthenticationCallback getAuthInteractiveCallback() {
return new AuthenticationCallback() {
#Override
public void onSuccess(AuthenticationResult authenticationResult) {
/* Successfully got a token, use it to call a protected resource */
accessToken = authenticationResult.getAccessToken();
Log.d("AuthSuccess"," "+accessToken);
}
#Override
public void onError(MsalException exception) {
/* Failed to acquireToken */
Log.d("AuthFail"," "+exception.getMessage());
if (exception instanceof MsalClientException) {
/* Exception inside MSAL, more info inside MsalError.java */
} else if (exception instanceof MsalServiceException) {
/* Exception when communicating with the STS, likely config issue */
}
}
#Override
public void onCancel() {
/* User canceled the authentication */
}
};
}
The problem is, AuthenticationResult object gives the access token, but not the refresh token. The object simply does not have refresh token as one of it's parameter. Do I need to further call another method to grab the refresh token as well? How does one get both access and refresh token from microsoft single sign on using MSAL?!
Currently, the library is not exposing the refresh token:
https://github.com/AzureAD/microsoft-authentication-library-for-android/issues/202
Related
I was integrating azure adb2c on my native android app using MSAL. My token expiry is set to 60minutes in the portal. Currently I'm calling the acquireTokenSilentAsync each time the app launches in order to make sure access token is not expired. But is there any way to avoid calling acquireTokenSilentAsync each time and make the call happens only when the access token expires? This is to make the app load much faster,by avoid calling acquireTokenSilentAsync every time.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.microsoft_azure);
context = MicrosoftAzureActivity.this;
initializeUI();
// Creates a PublicClientApplication object with res/raw/auth_config_single_account.json
PublicClientApplication.createSingleAccountPublicClientApplication(MicrosoftAzureActivity.this,
R.raw.auth_config_single_account,
new IPublicClientApplication.ISingleAccountApplicationCreatedListener() {
#Override
public void onCreated(ISingleAccountPublicClientApplication application) {
/**
* This test app assumes that the app is only going to support one account.
* This requires "account_mode" : "SINGLE" in the config json file.
**/
loadAccount();
}
#Override
public void onError(MsalException exception) {
displayError(exception);
}
});
}
Interactively fetching Token:
mSingleAccountApp.signIn(MicrosoftAzureActivity.this, null, getScopes(), getAuthInteractiveCallback());
Load Account when already token is fetched Interactively and account is already Loaded:
private void loadAccount() {
if (mSingleAccountApp == null) {
Log.d("SKT","Account Not Signed In");
return;
}
Log.d("SKT","Account Not Signed In#1");
mSingleAccountApp.getCurrentAccountAsync(new ISingleAccountPublicClientApplication.CurrentAccountCallback() {
#Override
public void onAccountLoaded(#Nullable IAccount activeAccount) {
// You can use the account data to update your UI or your app database.
mAccount = activeAccount;
if (activeAccount != null) {
Log.d("SKT","Account Already Signed In");
mSingleAccountApp.acquireTokenSilentAsync(getScopes(), B2CConfiguration.getAuthorityFromPolicyName("B2C_1_SignInSignUp"), getAuthSilentCallback());
}
}
#Override
public void onAccountChanged(#Nullable IAccount priorAccount, #Nullable IAccount currentAccount) {
if (currentAccount == null) {
// Perform a cleanup task as the signed-in account changed.
showToastOnSignOut();
}
}
#Override
public void onError(#NonNull MsalException exception) {
displayError(exception);
}
});
}
No, you must call acquireTokenAsync for this, it evaluates whether the token in cache is expired or for a different scope than being requested. If neither is true, MSAL returns the tokens from the cache, it doesn’t make any network calls and should be almost instant. You wouldn’t get any perf advantage by doing anything different as that is the minimum.
I'm trying to create a SyncAdapter for Microsoft calendars and the first step is Authentication. i'm using com.microsoft.aad:adal:2.0.4-alphaand using this code for first authentication:
getAuthenticationContext().acquireToken(
mContextActivity,
Constants.SCOPES.split(" "),
null,
Constants.CLIENT_ID,
Constants.REDIRECT_URI,
PromptBehavior.Auto,
new AuthenticationCallback<AuthenticationResult>() {
#Override
public void onSuccess(final AuthenticationResult authenticationResult) {
if (authenticationResult != null && authenticationResult.getStatus() ==
AuthenticationResult.AuthenticationStatus.Succeeded) {
dependencyResolver = new ADALDependencyResolver(
getAuthenticationContext(),
resourceId,
Constants.CLIENT_ID);
token = authenticationResult.getToken();
UserInfo userInfo = authenticationResult.getUserInfo();
if (userInfo != null) {
userIdentifier = new UserIdentifier(userInfo.getUniqueId(),
UserIdentifier.UserIdentifierType.UniqueId);
}
}
}
#Override
public void onError(Exception t) {
Log.e("initialize", "onError : " + t.getMessage());
result.setException(t);
}
}
);
this works perfectly and after entering username and password i can get token.
BUT this is for sync adapter and at some point i need to get token silently. so i used this code:
public void getTokenSilent() {
getAuthenticationContext()
.acquireTokenSilent(Constants.SCOPES.split(" "),
Constants.CLIENT_ID,
userIdentifier,
new AuthenticationCallback<AuthenticationResult>() {
#Override
public void onSuccess(
AuthenticationResult authenticationResult) {
UserInfo userInfo = authenticationResult.getUserInfo();
}
#Override
public void onError(Exception e) {
Log.e("getTokenSilent", "onError : " + e.getMessage());
}
});
}
After executing this code i got the error:
AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED Prompt is not allowed and failed to get token: ver:2.0.4-alpha
onError : Refresh token is failed and prompt is not allowed
how can i resolve this error and get or refresh token silently?
tnx in advance.
If you want to get the token silently, there are two ways for using Azure AD V2.0 endpoint.
First, acquire the access token and refresh token interactively, then get the access token in the cache or renew the access token using refresh token via acquireTokenSilent method.
Second is that the Azure AD V2.0 endpoint also support the Client Credentials flow(refer here) which normally used for the service daemon application. However the MSAL for android doesn't support this flow at present. You need to implement it yourself. You can follow this article about detail of this flow. And this flow only works for the Azure AD account.
I am creating an android app with azure mobile service. I have a service that runs always (with startForeground()) and is monitoring some of the user activities. The service sometimes needs to query an azure database invoking APIs, stored in the azure cloud, this way:
mClient.invokeApi("APIname", null, "GET", parameters);
//mClient is the MobileServiceClient instance
At the beginning the user logins by using a LoginActivity and everything works fine. After some time ( usually 1 hour) the token for the client expired and I received exceptions like this:
IDX10223: Lifetime validation failed. The token is expired.
After some searches I found the solution to refresh token here:
https://github.com/Microsoft/azure-docs/blob/master/includes/mobile-android-authenticate-app-refresh-token.md
if the activiy is alive the code works and refreshes successfully the token if expired. But if the activity was destroyed it doesn't work. So I decided to pass the ApplicationContext to the client, this way:
mClient.setContext(activity.getApplicationContext());
but now I receive a ClassCastException, because the client tries to cast the context to Activity. Here are the interesting lines of the exception:
java.lang.ClassCastException: android.app.Application cannot be cast to android.app.Activity
at com.microsoft.windowsazure.mobileservices.authentication.LoginManager.showLoginUI(LoginManager.java:349)
at com.microsoft.windowsazure.mobileservices.authentication.LoginManager.authenticate(LoginManager.java:161)
at com.microsoft.windowsazure.mobileservices.MobileServiceClient.login(MobileServiceClient.java:371)
at com.microsoft.windowsazure.mobileservices.MobileServiceClient.login(MobileServiceClient.java:356)
at com.microsoft.windowsazure.mobileservices.MobileServiceClient.login(MobileServiceClient.java:309)
So how can I refresh the token from a service without an activity? Or is there another way to keep the client always authenticated?
EDIT
I try to paste here some code, hoping to make clearer the way I am using authentication token. I have a LoginManager for managing authentication. Here some meaningful code from that class:
public boolean loadUserTokenCache(Context context)
{
init(context); //update context
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREF_FILE, Context.MODE_PRIVATE);
String userId = prefs.getString(USERID_PREF, null);
if (userId == null)
return false;
String token = prefs.getString(LOGIN_TOKEN_PREF, null);
if (token == null)
return false;
MobileServiceUser user = new MobileServiceUser(userId);
user.setAuthenticationToken(token);
mClient.setCurrentUser(user);
return true;
}
The filter is:
private class RefreshTokenCacheFilter implements ServiceFilter {
AtomicBoolean mAtomicAuthenticatingFlag = new AtomicBoolean();
//--------------------http://stackoverflow.com/questions/7860384/android-how-to-runonuithread-in-other-class
private final Handler handler;
public RefreshTokenCacheFilter(Context context){
handler = new Handler(context.getMainLooper());
}
private void runOnUiThread(Runnable r) {
handler.post(r);
}
//--------------------
#Override
public ListenableFuture<ServiceFilterResponse> handleRequest(
final ServiceFilterRequest request,
final NextServiceFilterCallback nextServiceFilterCallback
)
{
// In this example, if authentication is already in progress we block the request
// until authentication is complete to avoid unnecessary authentications as
// a result of HTTP status code 401.
// If authentication was detected, add the token to the request.
waitAndUpdateRequestToken(request);
Log.d(Constants.TAG, logClassIdentifier+"REFRESH_TOKEN_CACHE_FILTER is Sending the request down the filter chain for 401 responses");
Log.d(Constants.TAG, logClassIdentifier+mClient.getContext().toString());
// Send the request down the filter chain
// retrying up to 5 times on 401 response codes.
ListenableFuture<ServiceFilterResponse> future = null;
ServiceFilterResponse response = null;
int responseCode = 401;
for (int i = 0; (i < 5 ) && (responseCode == 401); i++)
{
future = nextServiceFilterCallback.onNext(request);
try {
response = future.get();
responseCode = response.getStatus().code;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
if (e.getCause().getClass() == MobileServiceException.class)
{
MobileServiceException mEx = (MobileServiceException) e.getCause();
responseCode = mEx.getResponse().getStatus().code;
if (responseCode == 401)
{
// Two simultaneous requests from independent threads could get HTTP status 401.
// Protecting against that right here so multiple authentication requests are
// not setup to run on the UI thread.
// We only want to authenticate once. Requests should just wait and retry
// with the new token.
if (mAtomicAuthenticatingFlag.compareAndSet(false, true))
{
// Authenticate on UI thread
runOnUiThread(new Runnable() {
#Override
public void run() {
// Force a token refresh during authentication.
SharedPreferences pref = context.getSharedPreferences(Constants.SHARED_PREF_FILE, Context.MODE_PRIVATE);
MobileServiceAuthenticationProvider provider = Utilities.getProviderFromName(pref.getString(Constants.LAST_PROVIDER_PREF, null));
authenticate(context, provider, true);
}
});
}
// Wait for authentication to complete then update the token in the request.
waitAndUpdateRequestToken(request);
mAtomicAuthenticatingFlag.set(false);
}
}
}
}
return future;
}
}
The authenticate method ( I modified some little things for correct showing of dialog and main activity, but the way it works should be the same as the original code from Microsoft):
/**
* Returns true if mClient is not null;
* A standard sign-in requires the client to contact both the identity
* provider and the back-end Azure service every time the app starts.
* This method is inefficient, and you can have usage-related issues if
* many customers try to start your app simultaneously. A better approach is
* to cache the authorization token returned by the Azure service, and try
* to use this first before using a provider-based sign-in.
* This authenticate method uses a token cache.
*
* Authenticates with the desired login provider. Also caches the token.
*
* If a local token cache is detected, the token cache is used instead of an actual
* login unless bRefresh is set to true forcing a refresh.
*
* #param bRefreshCache
* Indicates whether to force a token refresh.
*/
public boolean authenticate(final Context context, MobileServiceAuthenticationProvider provider, final boolean bRefreshCache) {
if (mClient== null)
return false;
final ProgressDialog pd = null;//Utilities.createAndShowProgressDialog(context, "Logging in", "Log in");
bAuthenticating = true;
// First try to load a token cache if one exists.
if (!bRefreshCache && loadUserTokenCache(context)) {
Log.d(Constants.TAG, logClassIdentifier+"User cached token loaded successfully");
// Other threads may be blocked waiting to be notified when
// authentication is complete.
synchronized(mAuthenticationLock)
{
bAuthenticating = false;
mAuthenticationLock.notifyAll();
}
QueryManager.getUser(context, mClient, mClient.getCurrentUser().getUserId(), pd);
return true;
}else{
Log.d(Constants.TAG, logClassIdentifier+"No cached token found or bRefreshCache");
}
// If we failed to load a token cache, login and create a token cache
init(context);//update context for client
ListenableFuture<MobileServiceUser> mLogin = mClient.login(provider);
Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
#Override
public void onFailure(Throwable exc) {
String msg = exc.getMessage();
if ( msg.equals("User Canceled"))
return;
if ( pd!= null && pd.isShowing())
pd.dismiss();
createAndShowDialog(context, msg, "Error");
synchronized(mAuthenticationLock)
{
bAuthenticating = false;
mAuthenticationLock.notifyAll();
}
}
#Override
public void onSuccess(MobileServiceUser user) {
cacheUserToken(context, mClient.getCurrentUser());
if(!bRefreshCache)//otherwise main activity is launched even from other activity (like shop activity)
QueryManager.getUser(context, mClient, mClient.getCurrentUser().getUserId(), pd);//loads user's info and shows MainActivity
else if ( pd!= null && pd.isShowing())
pd.dismiss();
synchronized(mAuthenticationLock)
{
bAuthenticating = false;
mAuthenticationLock.notifyAll();
}
ClientUtility.UserId = mClient.getCurrentUser().getUserId();
}
});
return true;
}
I think that the API should have a method for refreshing tokens without showing activity ( that as far as I understant, is needed only for inserting credentials; but credentials are not needed for token refreshing). Another solution I am thinking on is to switch to a different cloud services provider, giving up on Microsoft Azure:(
The error java.lang.ClassCastException: android.app.Application cannot be cast to android.app.Activity was caused by the method MobileServiceClient.setContext need a context of Activity like activity.this, but the context from activity.getApplicationContext() is a context for the whole android app. It is incorrect usage.
The offical solution for your needs is shown in the section Cache authentication tokens on the client, please refer to it to try solving your issue.
Whenever I use the following to login to QBChatService:
chatService.login(user, new QBEntityCallbackImpl() {
#Override
public void onSuccess() {
// success
Log.d("CHAT_READY", String.valueOf(chatService.isLoggedIn()));
try {
chatService.startAutoSendPresence(60);
} catch (SmackException.NotLoggedInException e) {
e.printStackTrace();
}
}
#Override
public void onError(List errors) {
// errror
AlertDialog.Builder dialog = new AlertDialog.Builder(ctx);
dialog.setMessage("chat login error: " + errors).create().show();
}
}
I observe the following log output:
D/QBASDK﹕ Connecting to chat..
D/QBASDK﹕ Connected. Login to chat, currentUser JID: myUserID-myAppID, resource: someRandomString
Which I assumed was an indication that login was successful. However, the onSuccess and onError functions of the callback are not called.
Also, Calling
QBChatService.getInstance().getGroupChatManager()
returns null.
Am I missing something on setting up chat service properly?
The first thing I did was allow QBChatService to print debug logs. Doing that revealed that a password not verified error was being returned by the service.
Next, I tried using the BaseService token as password but same result. It seems you can't just use user.SetID, do QBAuth.login(user...) and then set Password in the onSuccess when using facebook login.
In the end, I had to store the facebook access token, login again with facebook (sign in using social provider) before setting the password with the BaseService token to login to the chat service successfully.
I am working with QuickBlox library for video chat. How can i manage it session?? because when i move to the next activity from the live chat activity i just lost the session because it says "Chat can't initialized" then i have to create the session again to do the calling. So what's the lifetime of quickblox session and how can i manage that.
I am also facing problem with recalling when stop the call or move to the next activity and try to recall i was not able to do that actually i tried different things so each time i am getting different errors. So if any one has experience with QuickBlox library need help here.
When i stop a call i call this function.
private void stopCall() {
//Toggle view show the smile view again
//ToggleSmileView();
try
{
cancelCallTimer();
if (videoChat != null) {
videoChat.stopCall();
videoChat = null;
}
if (videoChannel != null) {
videoChannel.close();
videoChannel = null;
}
sessionId = null;
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
and when i do the call i call this function
private void call() {
//toggle view
//ToggleSmileView();
// get opponent
//
VideoChatApplication app = (VideoChatApplication)getApplication();
opponent = new QBUser();
opponent.setId((app.getCurrentUser().getId() == VideoChatApplication.FIRST_USER_ID ? VideoChatApplication.SECOND_USER_ID : VideoChatApplication.FIRST_USER_ID));
// call
//
callTimer = new Timer();
callTimer.schedule(new CancelCallTimerTask(), 30 * 1000);
createSenderChannel();
initVideoChat();
if (videoChat != null)
{
videoChat.call(opponent, getCallType(), 3000);
//toggleMicrophoneMute();
}
else
{
logAndToast("Stop current chat before call");
}
}
For: Lifetime of quickblox session and how can i manage that.
To authenticate your application you have to set valid a auth_key and
generate a signature using your application auth_secret and receive a
session token which you should use to send requests to QuickBlox API
And,
Expiration time for token is 2 hours. Please, be aware about it. If
you will perform query with expired token - you will receive error
Required session does not exist.
Source: Authentication and Authorization Session Info
That part fits the Android sample code of creating the session,
QBAuth.createSession(new QBEntityCallbackImpl<QBSession>() {
#Override
public void onSuccess(QBSession session, Bundle params) {
Log.i(TAG, "session created, token = " + session.getToken());
}
#Override
public void onError(List<String> errors) {
}
});
Source: Android developers documentation
I have worked with the Android SDK, and feel it still needs some work, esp to reach a stage equivalent to the iOS SDK and REST API.
Though looking at your code, you should use getToken() before creating the new QBUser and related video chat calls, if the token has expired, just create a new one.
I have implemented similar code, not a video chat application, but in a general manner, write the functions in onSuccess() of session creation if the session needs to be recreated.
Fyi, for the multiple ones, you can try checking the error with the summary that has been given, categorized into 4; ..developers/Errors