Access Azure AD secured WebApi in Android - android

I am creating an Android application which uses Azure AD for authentication. I need to access some WebAPIs on the Azure server which is secured using Azure AD authentication. I am able to generate the token. When I try to access the WebAPI it gives an 401 unauthorized error.
private void azureADLogin(){
mAuthContext = new AuthenticationContext(getApplicationContext(), AUTHORITY, false);
/* Instantiate handler which can invoke interactive sign-in to get the Resource
* sIntSignInInvoked ensures interactive sign-in is invoked one at a time */
mAcquireTokenHandler = new Handler(Looper.getMainLooper()){
#Override
public void handleMessage(Message msg) {
if( sIntSignInInvoked.compareAndSet(false, true)) {
if (msg.what == MSG_INTERACTIVE_SIGN_IN_PROMPT_AUTO){
mAuthContext.acquireToken(MainActivity.this, RESOURCE_ID, CLIENT_ID, REDIRECT_URI, PromptBehavior.Auto, getAuthInteractiveCallback());
}else if(msg.what == MSG_INTERACTIVE_SIGN_IN_PROMPT_ALWAYS){
mAuthContext.acquireToken(MainActivity.this, RESOURCE_ID, CLIENT_ID, REDIRECT_URI, PromptBehavior.Always, getAuthInteractiveCallback());
}
}
}
};
/* ADAL Logging callback setup */
Logger.getInstance().setExternalLogger(new Logger.ILogger() {
#Override
public void Log(String tag, String message, String additionalMessage, Logger.LogLevel level, ADALError errorCode) {
// You can filter the logs depending on level or errorcode.
Log.d(Constants.TAG, message + " " + additionalMessage);
}
});
/*Attempt an acquireTokenSilent call to see if we're signed in*/
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
String userId = preferences.getString(USER_ID, "");
if(!TextUtils.isEmpty(userId)){
mAuthContext.acquireTokenSilentAsync(RESOURCE_ID, CLIENT_ID, userId, getAuthSilentCallback());
}else {
mAcquireTokenHandler.sendEmptyMessage(MSG_INTERACTIVE_SIGN_IN_PROMPT_ALWAYS);
}
} // end of azure AD Login

Related

AWS IoT Android application over MQTT throws MqttException (0) - java.io.IOException: Already connected

I am trying to use 'Authenticate using Cognito-Identity with Cognito user pool' in my Android application. My Cognito user pool authentication works well, when I run that separately and I had seen a JWTToken as well. When I run the the 'PubSub' sample application with Unauthenticated role, it worked as expected. When I integrate these two features in one application, the application threw following error.
W/System.err: MqttException (0) - java.io.IOException: Already connected
W/System.err: at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:38)
W/System.err: at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:664)
W/System.err: at java.lang.Thread.run(Thread.java:761)
W/System.err: Caused by: java.io.IOException: Already connected
W/System.err: at java.io.PipedOutputStream.connect(PipedOutputStream.java:100)
W/System.err: at java.io.PipedInputStream.connect(PipedInputStream.java:195)
W/System.err: at org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketReceiver.<init>(WebSocketReceiver.java:42)
W/System.err: at org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketSecureNetworkModule.start(WebSocketSecureNetworkModule.java:78)
W/System.err: at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:650)
W/System.err: ... 1 more
I have been trying to resolve this issue since last Thursday and still stuck at the same place. Really No idea where should i check.!
I am adding my Authentication(Cognito user pool authentication) activity and Connect activity.
AmazonCognitoIdentityProviderClient identityProviderClient = new
AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), new ClientConfiguration());
identityProviderClient.setRegion(Region.getRegion(Regions.US_WEST_2));
CognitoUserPool userPool = new CognitoUserPool(getApplicationContext(), "us-west-2_ghtcc6ho9", "4t0mk45hNso69dp2j4jvel5ghm", "1jmq0lhhq721oif9k6nug31c29i760vihua8hvrgu5umfr2a1vd7", identityProviderClient);
cogUser = userPool.getUser();
authenticationHandler = new AuthenticationHandler() {
#Override
public void onSuccess(CognitoUserSession userSession, CognitoDevice newDevice) {
String ids = userSession.getIdToken().getJWTToken();
Log.d("MyToken","session id___"+userSession.getIdToken().getExpiration()+"___"+userSession.getIdToken().getIssuedAt());
Intent pubSub = new Intent(MainActivity.this, PubSubActivity.class);
pubSub.putExtra("token",""+ids);
startActivity(pubSub);
//MainActivity.this.finish();
}
#Override
public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String userId) {
Log.d("MyToken","getAuthenticationDetails");
AuthenticationDetails authenticationDetails = new AuthenticationDetails("shone", "172737", null);
authenticationContinuation.setAuthenticationDetails(authenticationDetails);
// Allow the sign-in to continue
authenticationContinuation.continueTask();
}
#Override
public void getMFACode(MultiFactorAuthenticationContinuation multiFactorAuthenticationContinuation) {
Log.d("MyToken","getMFACode");
multiFactorAuthenticationContinuation.continueTask();
}
#Override
public void authenticationChallenge(ChallengeContinuation continuation) {
Log.d("MyToken","authenticationChallenge"+continuation.getChallengeName());
newPasswordContinuation.continueTask();
}
#Override
public void onFailure(Exception exception) {
exception.printStackTrace();
Log.d("MyToken","onFailure");
}
};
cogUser.getSessionInBackground(authenticationHandler);
When It reaches 'OnSuccess' I am launching my connect activity and passing my session token along with the Intent. Moving to the next activity
private static final String COGNITO_POOL_ID = "us-west-2:a153a090-508c-44c0-a9dd-efd450298c4b";
private static final Regions MY_REGION = Regions.US_WEST_2;
AWSIotMqttManager mqttManager;
String clientId;
AWSCredentials awsCredentials;
CognitoCachingCredentialsProvider credentialsProvider;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = getIntent();
if(null == intent){
Toast.makeText(getApplicationContext(), "Token is null", Toast.LENGTH_SHORT).show();
}else {
token = intent.getStringExtra("token");
}
clientId = UUID.randomUUID().toString();
credentialsProvider = new CognitoCachingCredentialsProvider(
getApplicationContext(),
COGNITO_POOL_ID,
MY_REGION
);
mqttManager = new AWSIotMqttManager(clientId, CUSTOMER_SPECIFIC_ENDPOINT);
Map loginsMap = new HashMap();
loginsMap.put("cognito-idp.us-west-2.amazonaws.com/us-west-2_ghtcc6ho9", token);
credentialsProvider.setLogins(loginsMap);
Log.d("SESSION_ID", ""+token);
new Thread(new Runnable() {
#Override
public void run() {
credentialsProvider.refresh();
awsCredentials = credentialsProvider.getCredentials();
Log.d("SESSION_ID B: ", ""+awsCredentials.getAWSAccessKeyId());
Log.d("SESSION_ID C: ", ""+awsCredentials.getAWSSecretKey());
}
}).start();
}
View.OnClickListener connectClick = new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(LOG_TAG, "clientId = " + clientId);
try {
mqttManager.connect(credentialsProvider, new AWSIotMqttClientStatusCallback() {
#Override
public void onStatusChanged(final AWSIotMqttClientStatus status,
final Throwable throwable) {
Log.d(LOG_TAG, "Status = " + String.valueOf(status)+"______"+((null !=throwable)?throwable.getMessage():""));
runOnUiThread(new Runnable() {
#Override
public void run() {
if (status == AWSIotMqttClientStatus.Connecting) {
tvStatus.setText("Connecting...");
} else if (status == AWSIotMqttClientStatus.Connected) {
tvStatus.setText("Connected");
} else if (status == AWSIotMqttClientStatus.Reconnecting) {
if (throwable != null) {
Log.e(LOG_TAG, "Connection error.", throwable);
}
tvStatus.setText("Reconnecting");
} else if (status == AWSIotMqttClientStatus.ConnectionLost) {
if (throwable != null) {
Log.e(LOG_TAG, "Connection error.", throwable);
throwable.printStackTrace();
}
tvStatus.setText("Disconnected");
} else {
tvStatus.setText("Disconnected");
}
}
});
}
});
} catch (final Exception e) {
Log.e(LOG_TAG, "Connection error.", e);
}
}
};
What is wrong in my code? Why it throws exception when the MQTT connect is being invoked? Any help would be appreciated.
I beat my head up with this almost a week.
Full course of action ->
After succesfull login you will have a jwt token
String idToken = cognitoUserSession.getIdToken().getJWTToken();
put it into a map
Map<String, String> logins = new HashMap<String, String>();
//fill it with Cognito User token
logins.put("cognito-idp.<REGION>.amazonaws.com/<COGNITO_USER_POOL_ID>", idToken);
then use it to set in two places (not stated in any documentation!)
CognitoCachingCredentialsProvider credentialsProvider = new
CognitoCachingCredentialsProvider(context, IDENTITY_POOL_ID, REGION);
credentialsProvider.setLogins(logins);
and
AmazonCognitoIdentity cognitoIdentity = new AmazonCognitoIdentityClient(credentialsProvider);
GetIdRequest getIdReq = new GetIdRequest();
getIdReq.setLogins(logins); //or if you have already set provider logins just use credentialsProvider.getLogins()
getIdReq.setIdentityPoolId(COGNITO_POOL_ID);
GetIdResult getIdRes = cognitoIdentity.getId(getIdReq);
after that you still nedd to make some call
AttachPrincipalPolicyRequest attachPolicyReq = new AttachPrincipalPolicyRequest(); //in docs it called AttachPolicyRequest but it`s wrong
attachPolicyReq.setPolicyName("allAllowed"); //name of your IOTAWS policy
attachPolicyReq.setPrincipal(getIdRes.getIdentityId());
new AWSIotClient(credentialsProvider).attachPrincipalPolicy(attachPolicyReq);
and only after that you can enable connect button and continue like that
mqttManager.connect(credentialsProvider, new AWSIotMqttClientStatusCallback() {
Really for this small piece of code i spent a lot of time...
I was also getting same error -
Feb 27, 2019 10:23:09 AM com.amazonaws.services.iot.client.mqtt.AwsIotMqttConnectionListener onFailure
WARNING: Connect request failure
MqttException (0) - java.io.IOException: Already connected
at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:38)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:664)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.IOException: Already connected
at java.io.PipedOutputStream.connect(PipedOutputStream.java:100)
but the problem was different.
First of all, you do not need to call attachPrincipalPolicy from code. You can use the command line as well. You can do something like -
aws iot attach-principal-policy --principal us-east-1:1c973d17-98e6-4df6-86bf-d5cedc1fbc0d --policy-name "thingpolicy" --region us-east-1 --profile osfg
You will get the principal ID from identity browser of your identity pool. Now lets come to the error -
To successfully connect to mqtt with authenticated Cognito credentials, you need 2 correct policies -
Authenticated role corresponding to your identity pool should allow all mqtt operations.
AWS IoT policy should allow the same operations and you need to associate your cognito identity with this policy. We use attachPrincipalPolicy to do so.
If anyone step is missed we get above error. I agree the error is misleading - Already connected makes no sense to me for this. I would normally think it has to do with clientId, which should be unique. But anyways hopefully AWS folks would make this better at some point.
For my particular case issue was point 1. Though my IoT policy had all the required permissions, the auth role corresponding to the identity pool did not. So make sure you do that.
I have created a youtube video to show this as well: https://www.youtube.com/watch?v=j2KJVHGHaFc
When a client connected to broker it has a unique client ID. If clients tried to connect with same client id then this error occur. Use different client IDs like foo1, foo2, foo3, etc.

Cannot receive specific user push notification using azure notification hub

I am trying to learn to use azure mobile app, but I am having serious problems in using the NotificationHub. I have an Imagine subscription to Azure. I creating an android mobile app with azure backend. I have created a notification hub associated to the azure mobile app on the azure portal.
To register the app on the notification hub I used the code in this tutorial:
https://learn.microsoft.com/en-gb/azure/notification-hubs/notification-hubs-android-push-notification-google-fcm-get-started
The users are authenticated on the azure backend previuosly by using their google account, microsoft account or facebook account. New users are inserted into the table Users by the following node js code written for the table script Users.js. I want a push notification to Welcome the new User.
var azureMobileApps = require('azure-mobile-apps');
var logger = require('azure-mobile-apps/src/logger');
var table = azureMobileApps.table();
table.access = 'authenticated';
/**
* Adds the email address from the claims to the context item - used for
* insert operations
* #param {Context} context the operation context
* #returns {Promise} context execution Promise
*/
function addEmailToContext(context) {
/*
* Getting claim fields
*/
return context.user.getIdentity().then((data) => {
if( data.microsoftaccount != undefined){
context.item.email = data.microsoftaccount.claims.emailaddress;
context.item.name = data.microsoftaccount.claims.givenname;
context.item.surname = data.microsoftaccount.claims.surname;
}
if( data.google != undefined){
context.item.email = data.google.claims.emailaddress;
context.item.name = data.google.claims.givenname;
context.item.surname = data.google.claims.surname;
context.item.picture_url = data.google.claims.picture;
}
if( data.facebook != undefined){
context.item.email = data.facebook.claims.emailaddress;
context.item.name = data.facebook.claims.givenname;
context.item.surname = data.facebook.claims.surname;
}
logger.info('[tables/Users.js] --> NEW USER REGISTERED:'
+'\n\t Name:'+context.item.name
+'\n\t Surname:'+context.item.surname
+'\n\t Email:'+context.item.email);
// Execute the insert. The insert returns the results as a Promise,
// Do the push as a post-execute action within the promise flow.
return context.execute()
.then(function (results) {
// Only do the push if configured
if (context.push) {
// Mobile Apps adds a user tag when registering for push notifications
// Define the GCM payload.
var payload = {
"data": {
"message": 'Welcome '+context.item.username
}
};
context.push.gcm.send(context.user.id, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
}
// Don't forget to return the results from the context.execute()
return results;
})
.catch(function (error) {
logger.error('Error while running context.execute: ', error);
});
});
}
// CREATE - add or overwrite the authenticated user
table.insert(addEmailToContext);
module.exports = table;
According to "How to: Send push notifications to an authenticated user using tags" in the tutorial on How to use the Azure Mobile Apps Node.js SDK
"When an authenticated user registers for push notifications, a user ID tag is automatically added to the registration. "
So in the Users.js, as suggested in this tutorial I wrote the following code to send the push notification to the user.
context.push.gcm.send(context.user.id, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
With this code the push notification results to be sent successfully, but the device doesn't receive any notifications. If I use null instead of context.user.id then all devices receive the push notification correctly:
context.push.gcm.send(null, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
I also tried to invoke the following custom API to create tag when the user is registered to the hub. The invoked API is the following:
var logger = require('azure-mobile-apps/src/logger');
exports.post = function(req, res) {
logger.info('[api/registerTag.js] --> Invoked');
// Get the notification hub used by the mobile app.
var push = req.azureMobile.push,
installationId = req.get('X-ZUMO-INSTALLATION-ID'),
tags = req.body.tag.toString();
// Define an update tags operation.
var updateOperation = [{
"op": "add",
"path": "/tags",
"value": tags
}];
// Update the installation to add the new tags.
push.patchInstallation(installationId, updateOperation, function(error) {
if(error){
logger.error('[api/registerTag.js] --> An error occurred while adding'
+'the following tags: \n\t'+tags, error);
res.status(error.statusCode).send(error.detail);
} else {
logger.info('[api/registerTag.js] --> The following tags have been added'
+'to the Notification Hub: \n\t'+tags, error);
res.status(200).send(tags);
}
});
};
On the console it is printed that the tag has been added successfully. But if I then modify the Users.js code like this:
...
// Only do the push if configured
if (context.push) {
// Mobile Apps adds a user tag when registering for push notifications
var userTag = '_UserId:' + context.user.id;
logger.info("TAG "+userTag);
// Define the GCM payload.
var payload = {
"data": {
"message": 'Welcome '+context.item.username
}
};
context.push.gcm.send(userTag, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
}
...
again nothing is received. I have also tried whitelisting tags or adding them automatically using the Push section of the mobile app like shown in the image:
IMAGE LINK: i.stack.imgur.com/KBvQI.png
But the problem is still there. Hope someone can help me. Thanks.
After several times of testing, I succeeded in reproducing your issue and got the same problem. To achieve your requirement I did some modification in Android client-end:
1, Cache authentication user in the MainActivity class. Following is my code snippet. For more details you can refer here.
public static final String SHAREDPREFFILE = "temp";
public static final String USERIDPREF = "uid";
public static final String TOKENPREF = "tkn";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
// Create the Mobile Service Client instance, using the provided Mobile Service URL and key
mClient = new MobileServiceClient(
"https://yourwebsitename.azurewebsites.net",
this).withFilter(new ProgressFilter());
// Extend timeout from default of 10s to 20s
mClient.setAndroidHttpClientFactory(new OkHttpClientFactory() {
#Override
public OkHttpClient createOkHttpClient() {
OkHttpClient client = new OkHttpClient();
client.setReadTimeout(20, TimeUnit.SECONDS);
client.setWriteTimeout(20, TimeUnit.SECONDS);
return client;
}
});
authenticate();
} catch (MalformedURLException e) {
createAndShowDialog(new Exception("There was an error creating the Mobile Service. Verify the URL"), "Error");
} catch (Exception e){
createAndShowDialog(e, "Error");
}
}
private void authenticate() {
// We first try to load a token cache if one exists.
if (loadUserTokenCache(mClient)) {
createTable();
register();
}
// If we failed to load a token cache, login and create a token cache
else {
// Login using the Google provider.
ListenableFuture<MobileServiceUser> mLogin = mClient.login(MobileServiceAuthenticationProvider.Google);
Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
#Override
public void onFailure(Throwable exc) {
createAndShowDialog("You must log in. Login Required", "Error");
}
#Override
public void onSuccess(MobileServiceUser user) {
createAndShowDialog(String.format("You are now logged in - %1$2s", user.getUserId()), "Success");
cacheUserToken(mClient.getCurrentUser());
createTable();
register();
}
});
}
}
private void cacheUserToken(MobileServiceUser user) {
SharedPreferences prefs = getSharedPreferences(SHAREDPREFFILE, Context.MODE_PRIVATE);
Editor editor = prefs.edit();
editor.putString(USERIDPREF, user.getUserId());
editor.putString(TOKENPREF, user.getAuthenticationToken());
editor.commit();
}
private void register() {
NotificationsManager.handleNotifications(this, NotificationSettings.SenderId, MyHandler.class);
registerWithNotificationHubs();
}
2, In RegistrationIntentService class replace regID = hub.register(FCM_token).getRegistrationId(); with the following code:
regID = hub.register(FCM_token, prefs.getString("uid", "")).getRegistrationId();
3, Make sure add the line below to the first line within onHandleIntent method.
SharedPreferences prefs = getSharedPreferences("temp", Context.MODE_PRIVATE);

Microsoft Azure Mobile authentication with Google provider SDK in Android

I am currently testing Microsoft Azure and the App Service/Mobile Apps feature using a native Android app and C# on the back end.
I started with the Getting Started application (ToDo) as the base app and now I am trying to enable Authentication using the https://azure.microsoft.com/en-us/documentation/articles/app-service-authentication-overview/ page and Google as the provider.
So far I have
created a Google project with a OAuth Web client
the authorized redirect uri set there is: https://.azurewebsites.net/.auth/login/google/callback
in the Azure portal and the App Service instance I have enabled Authorization/Authentication
the "Action to take when request is not authenticated" option is set to "Allow Request"
For the Google Provider I have set the Client Id and Client Secret
In the Android app I am using the GoogleApiClient class to let the user select a Google Account. Also I get the ID token and the Server Auth Code
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestIdToken(getString(R.string.server_client_id))
.requestServerAuthCode(getString(R.string.server_client_id))
.build();
mScopes = gso.getScopeArray();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
Once the user picks up an account I retrieve the token and code and then I ask for an access token using the GoogleAuthUtil class. After I get the access_token I try to exchange it with an App Service token (authenticate2 method)
private void handleSignInResult(GoogleSignInResult result) {
Log.d("", "handleSignInResult: " + result.isSuccess());
if(result.isSuccess()) {
final GoogleSignInAccount account = result.getSignInAccount();
final String idToken = account.getIdToken();
String serverAuthCode = account.getServerAuthCode();
mSignInButton.setVisibility(View.GONE);
mGoogleUserText.setText(account.getDisplayName());
mGoogleUserText.setVisibility(View.VISIBLE);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putString("idToken", idToken).commit();
prefs.edit().putString("serverAuthCode", serverAuthCode).commit();
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
try {
StringBuilder scopesBuilder = new StringBuilder("oauth2:");
for(Scope scope : mScopes) {
scopesBuilder//.append("https://www.googleapis.com/auth/")
.append(scope.toString())
.append(" ");
}
String token = GoogleAuthUtil.getToken(ToDoActivity.this,
account.getEmail(), scopesBuilder.toString());
return token;
} catch (IOException | GoogleAuthException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ToDoActivity.this);
prefs.edit().putString("accessToken", result).apply();
authenticate2();
}
}.execute();
} else {
mSignInButton.setVisibility(View.VISIBLE);
mGoogleUserText.setVisibility(View.GONE);
}
}
private void authenticate2() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String idToken = prefs.getString("idToken", null);
String serverAuthCode = prefs.getString("serverAuthCode", null);
String accessToken = prefs.getString("accessToken", null);
JsonObject json = new JsonObject();
json.addProperty("access_token", accessToken);
json.addProperty("id_token", idToken);
json.addProperty("authorization_code", serverAuthCode);
ListenableFuture<MobileServiceUser> loginFuture =
mClient.login(MobileServiceAuthenticationProvider.Google, json);
Futures.addCallback(loginFuture, new FutureCallback<MobileServiceUser>() {
#Override
public void onSuccess(MobileServiceUser result) {
createTable();
}
#Override
public void onFailure(Throwable t) {
Log.e(TAG, t.getMessage(), t);
}
});
}
So I am using the MobileServiceClient.login() method to send back to the server the access_token of the user in order to get back an Azure session.
Nevertheless, this call fails and I get back a MobileServiceException:
com.microsoft.windowsazure.mobileservices.MobileServiceException: You do not have permission to view this directory or page.
Any ideas what am I missing here?
Thanks
Well this is more than embarassing :-)
I first tried to "manually" verify the id_token using the Google tokeninfo endpoint:
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=
but I was getting a generic error that didn't give a lot of info.
Then I used the Google API Java client library and created a small test to verify my token (more info here: https://developers.google.com/identity/sign-in/android/backend-auth)
That test was failing as well and I realized that the reason for that was the expiring time of my token which was smaller than the current time. And this was happening because my emulator time was not correct!
When I set the "correct" timezone everything worked as expected.
Sorry for the post guys. You can use the code here as a template and don't forget to check your emulator time :-)

AWS Developer Authenticated Identities using Android

I am trying to use a Developer Authenticated Provider to login to my android app basing it loosely off of this demo: https://github.com/awslabs/aws-sdk-android-samples/tree/master/CognitoSyncDemo. I successfully logged in through our own backend got the idToken and subsequently got session credentials to access our AWS database. I then used those credentials to make a POST to the db.
But this only worked once, now I cannot get through again, without having changed any code. I am also using a generated SDK through http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-sdk.html. I'm not sure if this is causing any errors.
Here is my DeveloperAuthenticationProvider:
public class AuthenticationProvider extends
AWSAbstractCognitoDeveloperIdentityProvider {
private static final String TAG = AuthenticationProvider.class.getSimpleName();
private static SocializeClient mSocializeClient;
private static final String developerProvider = Constants.AWS_PROVIDER_NAME;
private static final String cognitoSampleDeveloperAuthenticationAppEndpoint = UrlEndpoints.URL_DOMAIN;
public AuthenticationProvider(Context context, String accountId, String identityPoolId, Regions region) {
super(accountId, identityPoolId, region);
/*
* Initialize the client using which you will communicate with your
* backend for user authentication.
*/
AWSCredentialsProvider awsCredentialsProvider = new CognitoCachingCredentialsProvider(
context,
this,
region
);
ApiClientFactory factory = new ApiClientFactory()
.endpoint(cognitoSampleDeveloperAuthenticationAppEndpoint)
.credentialsProvider(awsCredentialsProvider);
mSocializeClient = factory.build(SocializeClient.class);
}
/*Only refreshes the login info, when it has expired*/
/*
* (non-Javadoc)
* #see com.amazonaws.auth.AWSCognitoIdentityProvider#refresh() In refresh
* method, you will have two flows:
*/
/*
* 1. When the app user uses developer authentication. In this case, make
* the call to your developer backend, from where call the
* GetOpenIdTokenForDeveloperIdentity API of Amazon Cognito service. Be sure to call update(), so as to
* set the identity id and the token received.
*/
/*
* 2.When the app user is not using the developer authentication, just call
* the refresh method of the AWSAbstractCognitoDeveloperIdentityProvider
* class which actually calls GetId and GetOpenIDToken API of Amazon
* Cognito.
*/
#Override
public String refresh() {
Log.i(TAG, "refresh");
// If there is a key with developer provider name in the logins map, it
// means the app user has used developer credentials
if (!loginsMap.isEmpty()
&& loginsMap.containsKey(developerProvider)) {
Log.i(TAG, "contains provider");
} else {
Log.i(TAG, "does not contain developer provider");
Map<String, String> logins = new HashMap<>();
logins.put(developerProvider, UserSingleton.imei);
setLogins(logins);
}
// TODO:: Temp code to login. Once available, need to add code to GetToken from SocializeClient
Login login = new Login();
login.setImei(UserSingleton.imei);
login.setPassword(UserSingleton.password);
LoginReponse loginReponse = mSocializeClient.socializeAuthLoginPost(login);
Log.i(TAG, "login response: " + loginReponse.getIdentityId() + " - token: " + loginReponse.getToken());
update(loginReponse.getIdentityId(), loginReponse.getToken());
Log.i(TAG, "updated");
return loginReponse.getToken();
}
/*
* (non-Javadoc)
* #see com.amazonaws.auth.AWSBasicCognitoIdentityProvider#getIdentityId()
*/
/*
* This method again has two flows as mentioned above depending on whether
* the app user is using developer authentication or not. When using
* developer authentication system, the identityId should be retrieved from
* the developer backend. In the other case the identityId will be retrieved
* using the getIdentityId() method which in turn calls Cognito GetId and
* GetOpenIdToken APIs.
*/
#Override
public String getIdentityId() {
Log.i(TAG, "getIdentityId");
identityId = CognitoSyncClientManager.credentialsProvider.getCachedIdentityId();
if (identityId == null) {
Log.i(TAG, "identityId is null");
if (!loginsMap.isEmpty()
&& loginsMap.containsKey(developerProvider)) {
Log.i(TAG, "grabbing identityId using logins map");
// TODO:: Temp code to login. Once available, need to add code to GetToken from SocializeClient
Login login = new Login();
login.setImei(loginsMap.get(developerProvider));
login.setPassword(UserSingleton.password);
LoginReponse loginReponse = mSocializeClient.socializeAuthLoginPost(login);
Log.i(TAG, "login response: " + loginReponse.getIdentityId() + " - token: " + loginReponse.getToken());
update(loginReponse.getIdentityId(), loginReponse.getToken());
return loginReponse.getIdentityId();
} else {
return super.getIdentityId();
}
} else {
return identityId;
}
}
/*
* (non-Javadoc)
* #see
* com.amazonaws.auth.AWSAbstractCognitoIdentityProvider#getProviderName()
* Return the developer provider name which you chose while setting up the
* identity pool in the Amazon Cognito Console
*/
#Override
public String getProviderName() {
return developerProvider;
}
/**
* This function validates the user credentials against the sample Cognito
* developer authentication application. After that it stores the key and
* token received from sample Cognito developer authentication application
* for all further communication with the application.
*
* #param imei
* #param password
*/
public void login(String imei, String password, Context context) {
Log.i(TAG, "login");
Login login = new Login();
login.setImei(imei);
login.setPassword(password);
new AuthenticationTask(context).execute(login);
}
public void publishProfile(Context context, Profile profile){
Log.i(TAG, "publishProfile");
ProfileKey profileKey = new ProfileKey();
profileKey.setUserID(identityId);
profile.setKey(profileKey);
new UploadProfileTask(context).execute(profile);
}
protected static SocializeClient getSocializeClientInstance() {
if (mSocializeClient == null) {
throw new IllegalStateException(
"Dev Auth Client not initialized yet");
}
return mSocializeClient;
}
}
Here is my AuthenticationTask as well where I attempt to login, then grab credentials to access the AWS database:
public class AuthenticationTask extends
AsyncTask<Login, Void, Void> {
private static final String TAG = AuthenticationTask.class.getSimpleName();
// The user name or the developer user identifier you will pass to the
// Amazon Cognito in the GetOpenIdTokenForDeveloperIdentity API
private String mImei;
private String mPassword;
private GetCredentialsForIdentityResult credentialsForIdentityResult;
private boolean isSuccessful;
private final Context context;
public AuthenticationTask(Context context) {
this.context = context;
}
#Override
protected Void doInBackground(Login... params) {
Log.i(TAG, "doInBackground get refreshing threshold: " + CognitoCachingCredentialsProvider.DEFAULT_THRESHOLD_SECONDS);
mImei = params[0].getImei();
mPassword = params[0].getPassword();
Login login = params[0];
// if(mPassword == null){
// Log.i(TAG, "register");
// mPassword = Utils.generateRandomString();
// final Register register = new Register();
// register.setImei(mImei);
// register.setPassword(mPassword);
// login.setPassword(mPassword);
// RegisterResponse registerResponse = AuthenticationProvider.getSocializeClientInstance().socializeAuthRegisterPost(register);
// Log.i(TAG, "registerResponse: " + registerResponse.getCreated());
UserSingleton.password = mPassword;
UserSingleton.getInstance().saveRegistrationInfo();
Log.i(TAG, "imei: " + mImei);
// }
Log.i(TAG, "calling login post");
LoginReponse loginReponse = AuthenticationProvider.getSocializeClientInstance().socializeAuthLoginPost(login);
Log.i(TAG, "login response: " + loginReponse.getIdentityId() + " - token: " + loginReponse.getToken());
// Set up the loginsMap to send with the credentials request
Map<String, String> loginsMap = new HashMap<>();
loginsMap.put(CognitoSyncClientManager.developerIdentityProvider.getProviderName(), loginReponse.getToken());
// get AWS credentials to access DB
GetCredentialsForIdentityRequest credentialsForIdentityRequest = new GetCredentialsForIdentityRequest();
credentialsForIdentityRequest.setIdentityId(loginReponse.getIdentityId());
credentialsForIdentityRequest.setLogins(loginsMap);
Log.i(TAG, "credentials request: " + credentialsForIdentityRequest.getIdentityId() + credentialsForIdentityRequest.getLogins());
AmazonCognitoIdentityClient cognitoIdentityClient = new AmazonCognitoIdentityClient(CognitoSyncClientManager.credentialsProvider);
credentialsForIdentityResult = cognitoIdentityClient
.getCredentialsForIdentity(credentialsForIdentityRequest);
isSuccessful = credentialsForIdentityResult != null;
return null;
}
#Override
protected void onPostExecute(Void result) {
if (isSuccessful) {
Log.i(TAG, "accessKeyId: " + credentialsForIdentityResult.getCredentials().getAccessKeyId()
+ "\nsecretKey: " + credentialsForIdentityResult.getCredentials().getSecretKey()
+ "\nsessionToken: " + credentialsForIdentityResult.getCredentials().getSessionToken());
CognitoSyncClientManager
.addLogins(
((AuthenticationProvider) CognitoSyncClientManager.credentialsProvider
.getIdentityProvider()).getProviderName(),
mImei);
} else {
Log.i(TAG, "login error: " + result);
}
}
}
In my refresh call I am just relogging in. I'm not sure if that is correct.
The biggest issue now is upon startup when I attempt to login using, mSocializeClient.socializeAuthLoginPost(login) it seems to call refresh every time, before it even logs in. Refresh then attempts to login again and it keeps calling itself endlessly.
Any help/explanations would be greatly appreciated.
The way the dev auth sample works is as follows:
The DeveloperAuthenticationTask.login() is supposed to login to the server and get a session key.
It sets up the logins map and calls DeveloperAuthenticationProvider.refresh()
Refresh exchanges the session key with the server for a valid cognito token and identity id and calls update with the token and identity id.
In your case, you don't have this session key, just username and password. So you don't need the AuthenticationTask. All you need is:
a login() in your AuthenticationProvider, that puts the username/password in a secure location, sets up the login map and calls refresh (it shouldn't actually attempt to login to your service).
In refresh() you retrieve the username/pass from the secure location, call your service and then call update with the token and identity id returned from your service.
Can you simplify your code to this flow?

SignalR HTTP status 400 multiple clients

I'm running an application with SignalR 2.2.0 on server side and signalr-java-client (self compiled, last GitHub version) on Android as client.
Currently, there are 4 clients connected to my hub. From time to time, it happens, that all 4 clients simultaneously receive the HTTP status 400 with the message "The connection id is in the incorrect format" (the clients were connected before). I analyzed this multiple times and am not able to find any information/pattern when or why this happens.
The connecten is secured via JWT, the token is definitely valid. When retrieving a new token, the connection is stopped and started again. Apart from this, it is very unlikely that the error is device-related, because the error is thrown at all 4 clients the same time.
I know, this error can occur when the client's Identity changes, but an Identity change for 4 clients the same time seems very unlikely to me.
This is the server-code used for authentication (Deepak asked).
The following method gets called in my Startup.cs:
public static void ConfigureOAuth(IAppBuilder app, string audienceID, string sharedSecret)
{
byte[] secret = TextEncodings.Base64Url.Decode(sharedSecret);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
Provider = new MyOAuthBearerAuthenticationProvider(),
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceID },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(Issuer, secret)
}
});
}
Here's the code of MyOAuthBearerAuthenticationProvider class:
class MyOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
/// <summary>
/// Get's a JWT from querysting and puts it to context
/// </summary>
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context.Token == null)
{
string value = context.Request.Query.Get("auth_token");
if (!string.IsNullOrEmpty(value)) //token from queryString
{
context.Token = value;
}
}
return Task.FromResult<object>(null);
}
}
I have to retrieve the token from query string, because additionally to the java-client, a javascript client is used, which is not able to set headers.
Lastly, I secure my hub and some of it's methods with the Authorization attribute:
[Authorize(Roles = "MyExampleRole")]
This is the client-code for connection:
public boolean connect(String url, String token) {
if (connected) {
return true;
}
try {
this.hubConnection = new HubConnection(url, "auth_token=" + token, true, logger);
this.hubProxy = hubConnection.createHubProxy("MyHub");
this.hubProxy.subscribe(this.signalRMethodProvider);
this.hubConnection.stateChanged(stateChangedCallback);
SignalRFuture<Void> awaitConnection = this.hubConnection.start();
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
return true;
}
catch (InterruptedException | TimeoutException | ExecutionException e) {
log.error("connect", e);
return false;
}
}
Does anybody have an Idea, how to fix this problem or where I may receive further information?
Thank you very much
-Lukas
seems fine...
possible alteration you can do is change
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
to
awaitConnection.done(new Action<Void>() {
#Override
public void run(Void obj) throws Exception {
Log.d(TAG, "Hub Connected");
}
}).onError(new ErrorCallback() {
#Override
public void onError(Throwable error) {
error.printStackTrace();
Log.d(TAG, "SignalRServiceHub Cancelled");
}
}).onCancelled(new Runnable() {
#Override
public void run() {
Log.d(TAG, "SignalRServiceHub Cancelled");
}
});

Categories

Resources