Android - Do Firebase User Auth Tokens Expire? - android

I decided to use Volley and go the RESTful route with Firebase since their listeners seem to hang when there's no internet connection. At least with Volley, it lets me know if a network request was not successful due to internet connection or not.
I need to know whether FirebaseUser auth tokens expire or not. In my app, I only allow Google and Facebook authentication, and I use the following code assuming that Firebase user auth token DO NOT expire:
private String authToken;
// Callbacks
public interface ApiCallbacks {
public void onSuccess(JSONObject response);
public void onError(String errorString);
}
private interface AuthTokenCallbacks {
public void onAuthTokenSuccess();
public void onAuthTokenError(String errorString);
}
// Request Helpers
private void getAuthToken(final AuthTokenCallbacks callbacks) {
// Lazy instantiation
if (authToken == null) {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user == null) {
callbacks.onAuthTokenError("Please log in");
} else {
user.getToken(false).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
#Override
public void onComplete(#NonNull Task<GetTokenResult> task) {
if (task.isSuccessful()) {
authToken = task.getResult().getToken();
callbacks.onAuthTokenSuccess();
} else {
callbacks.onAuthTokenError("Please log in");
}
}
});
}
} else {
callbacks.onAuthTokenSuccess();
}
}
public void sendGetRequestTo(final String endpoint, final HashMap<String, String> arguments, final RequestQueue requestQueue, final String tag, final ApiCallbacks callbacks) {
// Only execute get request if we have an auth token
getAuthToken(new AuthTokenCallbacks() {
#Override
public void onAuthTokenSuccess() {
final String requestUrl = getRequestUrlString(endpoint, arguments);
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, requestUrl, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
callbacks.onSuccess(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
callbacks.onError(error.toString());
}
});
request.setTag(tag);
requestQueue.add(request);
}
#Override
public void onAuthTokenError(String errorString) {
callbacks.onError("Please log in");
}
});
}
Is this the correct way of doing it? I just need to know if I'm going in the right direction because I don't want future problems with my auth tokens expiring (if they do).
EDIT
I forgot to mention that my final String requestUrl = getRequestUrlString(endpoint, arguments); method basically constructs the url request string with auth=authTokenString appended at the end of my url.

Yes, they do expire (you can check out the expiration date at jwt.io). If you don't force a refresh (i.e. user.getToken(false)), the returned token will be updated only if it has expired. If you pass true to getToken(...), a new token will be created which also involves the linked providers' token validation on the firebase servers (e.g. validating against Facebook whether the user still has his/her account linked). Note that the latter counts towards your daily token service quotas, so make sure you use it only when it's necessary.

Related

How to get AWS credentials from Identity Pools (Federated Identities) with android

I am new to AWS Cognito.
From my program, I want to get AWS temporary credentials to access API services such as api from API gateway. What I have is "IdentityPoolId", "IdentityId" and "OpenIdToken".
When I tried accessing with AWS Credential by getCredentialsForIdentity, I got "Identity 'ap-northeast-1:xxxx' not found." at onError method everytimes. Please help me what I was wrong?
Single<GetCredentialsForIdentityResult> primeSingle = Single.fromCallable(MyClass::getResult);
primeSingle
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<GetCredentialsForIdentityResult>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onSuccess(#NonNull GetCredentialsForIdentityResult result) {
Credentials credentials = result.getCredentials();
}
#Override
public void onError(#NonNull Throwable e) {
Log.d("Test", "onError: " + e.getMessage());
}
});
Here is getting Credential Result code.
private static GetCredentialsForIdentityResult getResult() {
AmazonCognitoIdentity identityClient = new AmazonCognitoIdentityClient(new AnonymousAWSCredentials());
Map<String, String> logins = new HashMap<String, String>();
logins.put("cognito-identity.amazonaws.com", MyClass.OPEN_ID_TOKEN);
GetCredentialsForIdentityRequest getCredentialsForIdentityRequest =
new GetCredentialsForIdentityRequest()
.withIdentityId(MyClass.IDENTITY_ID) // Not Identity Pool Id
.withLogins(logins);
getCredentialsForIdentityRequest.setIdentityId(identityId);
GetCredentialsForIdentityResult result = identityClient.getCredentialsForIdentity(getCredentialsForIdentityRequest);
return result;
}
Finally, I got Credentials by referencing this.
https://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html
Thanks in advance.
Here is the code:
public class DeveloperAuthenticationProvider extends AWSAbstractCognitoDeveloperIdentityProvider {
private static final String developerProvider = null;
public DeveloperAuthenticationProvider(String identityPoolId, Regions region) {
super(null, identityPoolId, region);
// Initialize any other objects needed here.
}
// Return the developer provider name which you choose while setting up the
// identity pool in the &COG; Console
#Override
public String getProviderName() {
return developerProvider;
}
// Use the refresh method to communicate with your backend to get an
// identityId and token.
#Override
public String refresh() {
// Override the existing token
setToken(null);
// Get the identityId and token by making a call to your backend
// (Call to your backend)
// Call the update method with updated identityId and token to make sure
// these are ready to be used from Credentials Provider.
update(identityId, token);
return token;
}
// If the app has a valid identityId return it, otherwise get a valid
// identityId from your backend.
#Override
public String getIdentityId() {
// Load the identityId from the cache
identityId = "ap-northeast-1:xxxx";
return identityId;
}}
Call above call from one method:
private static AWSSessionCredentials getResult(Context context) {
DeveloperAuthenticationProvider developerProvider =
new DeveloperAuthenticationProvider("ap-northeast-1:your_pool_id", Regions.AP_NORTHEAST_1);
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider( context, developerProvider, Regions.AP_NORTHEAST_1);
return credentialsProvider.getCredentials();
}
And use rxjava to get response:
Single<AWSSessionCredentials> primeSingle = Single.fromCallable(() -> getResult(this));
primeSingle
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<AWSSessionCredentials>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onSuccess(#NonNull AWSSessionCredentials result) {
String secretKey = result.getAWSSecretKey();
}
#Override
public void onError(#NonNull Throwable e) {
Log.d("Test", "onError: " + e.getMessage());
}
});
After successful, you can get Credentials from onSuccess method.

RxJava Android wait for callback to finish before returning data

I'm new to RxJava. I have an Android app that is using AWS Cognito SDK for authentication. I have an AwsAuthClient class that handles calling the SDK and returning the results. I have a fragment that calls the SignUp method in AwsAuthClient. I need to return the results of signup to the fragment so that it can react appropriately.
RegisterFragment class:
public class RegisterFragment{
AwsAuthClient authClient;
public void onCreateAccountClick() {
Subscription createSubscription = authClient.SignUp(params)
.compose(Transformers.applyIoToMainSchedulers())
.subscribe((CognitoUser currentUser) -> {
transitionToVerificationScreen();
}, (Throwable throwable) -> {
// Report the error.
});
}
}
Here is the AwsAuthClient:
public class AwsAuthClient {
public void SignUp(CreateParams createParams){
// Create a CognitoUserAttributes object and add user attributes
CognitoUserAttributes userAttributes = new CognitoUserAttributes();
// Add the user attributes. Attributes are added as key-value pairs
// Adding user's given name.
// Note that the key is "given_name" which is the OIDC claim for given name
userAttributes.addAttribute("given_name", createParams.getFirstname() + " " + createParams.getLastname());
// Adding user's phone number
userAttributes.addAttribute("phone_number", createParams.getPhone());
// Adding user's email address
userAttributes.addAttribute("email", createParams.getPhone());
SignUpHandler signupCallback = new SignUpHandler() {
#Override
public void onSuccess(CognitoUser cognitoUser, boolean userConfirmed, CognitoUserCodeDeliveryDetails cognitoUserCodeDeliveryDetails) {
// Sign-up was successful
currentUser = cognitoUser;
// Check if this user (cognitoUser) needs to be confirmed
if(!userConfirmed) {
// This user must be confirmed and a confirmation code was sent to the user
// cognitoUserCodeDeliveryDetails will indicate where the confirmation code was sent
// Get the confirmation code from user
Timber.d("Sent confirmation code");
}
else {
// The user has already been confirmed
Timber.d("User has already been confirmed.");
}
}
#Override
public void onFailure(Exception exception) {
// Sign-up failed, check exception for the cause
}
};
userPool.signUpInBackground(userId, password, userAttributes, null, signupCallback);
}
}
How can I return the results of onSuccess or OnFailure up to the RegisterFragment class?
It looks like the Cognito SDK already provides an async way to get information. In order for you to wrap this into an rx stream, you should consider using a Subject.
Subject are both Observables capable of emitting data, and Observers capable of receiving data. A Subject can wait to receive the callback data, take the data, and then emit it onto a stream.
public Observable<CognitoUser> SignUp(CreateParams createParams){
BehaviorSubject<CognitoUser> subject = BehaviorSubject.create();
// ...
SignUpHandler signupCallback = new SignUpHandler() {
#Override
public void onSuccess(CognitoUser cognitoUser, boolean userConfirmed, CognitoUserCodeDeliveryDetails cognitoUserCodeDeliveryDetails) {
// Sign-up was successful
// Check if this user (cognitoUser) needs to be confirmed
if(!userConfirmed) {
// This user must be confirmed and a confirmation code was sent to the user
// cognitoUserCodeDeliveryDetails will indicate where the confirmation code was sent
// Get the confirmation code from user
Timber.d("Sent confirmation code");
}
else {
// The user has already been confirmed
Timber.d("User has already been confirmed.");
}
subject.onNext(cognitoUser);
subject.onComplete();
}
#Override
public void onFailure(Exception exception) {
subject.onError(exception);
}
};
userPool.signUpInBackground(userId, password, userAttributes, null, signupCallback);
return subject;
}
If you are using RxJava2. You can use the create() operator to create your own async call:
public class AwsAuthClient {
public Observable<CognitoUser> SignUp(CreateParams createParams){
return Observable.create(emitter -> {
SignUpHandler signupCallback = new SignUpHandler() {
#Override
public void onSuccess(CognitoUser cognitoUser, boolean userConfirmed, CognitoUserCodeDeliveryDetails cognitoUserCodeDeliveryDetails) {
// Sign-up was successful
emitter.onNext(cognitoUser);
// Check if this user (cognitoUser) needs to be confirmed
if(!userConfirmed) {
// This user must be confirmed and a confirmation code was sent to the user
// cognitoUserCodeDeliveryDetails will indicate where the confirmation code was sent
// Get the confirmation code from user
Timber.d("Sent confirmation code");
}
else {
// The user has already been confirmed
Timber.d("User has already been confirmed.");
}
emitter.onComplete();
}
#Override
public void onFailure(Exception exception) {
// Sign-up failed, check exception for the cause
emitter.onError(exception);
}
};
//cancel the call
Observable.setCancellable(//your cancel code)
})
}
Edit: If you are using RxJava1(latest version 1.3.2) you can just use Observable.create(Action1>,BackPressureMode) instead of create and it's safe
Observable.create(new Action1<Emitter<CognitoUser extends Object>>() {
#Override
public void call(Emitter<CognitoUser> emitter) {
SignUpHandler signupCallback = new SignUpHandler() {
#Override
public void onSuccess(CognitoUser cognitoUser, boolean userConfirmed, CognitoUserCodeDeliveryDetails cognitoUserCodeDeliveryDetails) {
if (!userConfirmed) {
Timber.d("Sent confirmation code");
} else {
Timber.d("User has already been confirmed.");
}
emitter.onNext(cognitoUser);
emitter.onComplete();
}
#Override
public void onFailure(Exception exception) {
emitter.onError(exception);
}
};
emitter.setCancellation(new Cancellable() {
#Override
public void cancel() throws Exception {
//Your Cancellation
}
});
signUpInBackground(userId, password, userAttributes, null, signupCallback);
}
//Because RxJava 1 doesn't have Flowable so you need add backpressure by default.
}, Emitter.BackpressureMode.NONE );

How to sign out of aws cognito - android?

So here is the code i used to sign my user into cognito (i hope im correct). Now, how would i sign out? Currently i have my own signing up process (so no facebook or google yet).
// Callback handler for the sign-in process
private AuthenticationHandler authenticationHandler = new AuthenticationHandler()
{
#Override
public void onSuccess(CognitoUserSession userSession, CognitoDevice newDevice)
{
Log.d(COGNITO_LOGIN,"Login success!");
cognitoUser.getDetailsInBackground(getDetailsHandler);
//Now we get user from dynamoDB and store it into a local user object.
}
#Override
public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String userId)
{
Log.d(COGNITO_LOGIN,passwordET.getText().toString());
// The API needs user sign-in credentials to continue
AuthenticationDetails authenticationDetails = new AuthenticationDetails(userId, passwordET.getText().toString(), null);
// Pass the user sign-in credentials to the continuation
authenticationContinuation.setAuthenticationDetails(authenticationDetails);
// Allow the sign-in to continue
authenticationContinuation.continueTask();
}
#Override
public void getMFACode(MultiFactorAuthenticationContinuation multiFactorAuthenticationContinuation) {
// Multi-factor authentication is required; get the verification code from user
multiFactorAuthenticationContinuation.setMfaCode("verificationCode");
// Allow the sign-in process to continue
multiFactorAuthenticationContinuation.continueTask();
}
#Override
public void authenticationChallenge(ChallengeContinuation continuation) {
}
#Override
public void onFailure(Exception exception)
{
// Sign-in failed, check exception for the cause
Log.d(COGNITO_LOGIN,"Login failed!");
Log.d(COGNITO_LOGIN,exception.getMessage());
}
};
cognitoUser.getSessionInBackground(authenticationHandler);
You should be able to just call signOut on a cognitoUser object such as below. What that does is clear access, id and refresh tokens from the device so you would need to authenticate again.
// This has cleared all tokens and this user will have to go through the authentication process to get tokens.
user.signOut();
There is also a globalSignOut call that revokes tokens server-side.
There is a way to wipe or clear the session for the current user who logged, the following is the way, which I found so far.
This is for fb in federated identities
if (fbAccessToken != null) {
LoginManager.getInstance().logOut();
}
This is for twiiter
if (mAuthManager != null) {
mAuthManager.clearAuthorizationState(null);
}
// wipe data
CognitoSyncClientManager.getInstance()
.wipeData();
CognitoUserPool pool = AwsCognitoHelper.getPool();
if (pool != null) {
CognitoUser user = pool.getCurrentUser();
if (user != null) {
GenericHandler handler = new GenericHandler() {
#Override
public void onSuccess() {
}
#Override
public void onFailure(Exception e) {
}
};
user.globalSignOutInBackground(handler);
}
}

retrofit 2 post rxjava (login)

I'm new using retrofit2 and rxjava, i was able to use GET to get information from api's but now, using POST for a login request is not working how is suposed too.
Application application = Application.get(mLoginView.getContext());
Service Service = application.getmService();
Log.i(TAG,""+username);
Log.i(TAG,""+password);
mSubscription = Service.login(username,password)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<User>() {
#Override
public void onCompleted() {
Log.i(TAG,"User: " + mUser.getHash());
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
Log.i(TAG,"USERNAME DON'T EXIST");
}
#Override
public void onNext(User user) {
// LoginPresenter.this.mUser = user;
}
});
Service:
public interface Service {
String mUrl = "https://blabla.com/api/index.php/"; // TODO Change
#FormUrlEncoded
#POST("user/login")
Observable<User> login(#Field(value="email",encoded=true) String email, #Field(value="password",encoded = true) String password );
I enter a POST with username and pass from an existing user and return me a 404 page and not the hash im supposed to get.
Thank you
I forgot this was here, I found a solutions months ago, and what i did was create a object UserCredentials to do the body request and a object to get the response.
#POST("user/login")
Observable<LoginResponse> login(#Body UserCredentials userCredentials);

Auth Token and Gcm Registration Id

I'm able to receive a GCM registration id (for push notifications) thanks to this google guide and store the reg.id in a database and without authentication everything works fine.
I use web api 2, oauth 2 authentication and account manager.
1) User sign in to application, App creates an account for the user.
- Or if an account exists auto sign the user in.
2) App gets auth token, if expired retrieves it via Volley string request
3) App checks UserData of Account Manager if reg. id received before. If not App requests an reg. id from GCM and posts it to the server via Volley(AuthToken is required here) and App sets a userdata in account that reg. id has received.
With the above flow of my app which is exists only in my mind at the moment, I've some questions.
First, how can I get auth token first and move to the step 3 which is IntentService according to the Guide.
Second, let's say we managed to do first question. What happens if user login to his account from a different device? Should I update his reg. id for his new device. But what if this device was temporary and he returns to use his permanent device? Notifications will be sent to tempopary device because it was the last device he signed in!
I'm really confused and will be apreciated for anyone who lights my way. Thanks.
Edit
Instead of following Google's guide (instead of using IntentService) is it possible getting both Authorization Token and Registration Id(Token) in a AsyncTask?
Just as answer for 2nd part of question.
Relation between user and his regId should be 1 to n. So 1 user can have multiple devices, and by this multiple regId. And when you would like to send message to this user - you should send multiple messages (1 to every device). Other possible solution is using lately introduced Device Group Messaging, and IMO is preferable way for it.
Thanks for your answers, it only works if I use two async tasks and here is my solution. Any criticism or recommendation will be welcome.
I create a TokenCheckerActivity and put it between login activity and main activity.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_token_checker);
webApiUri = "url Here";
tokenContext = this;
accountManager = accountManager.get(tokenContext);
PpSharedPreferences ppSharedPreferences = new PpSharedPreferences(tokenContext);
ppAuthenticator = new PpAuthenticator(tokenContext);
account = ppAuthenticator.getCurrentAccount(ppSharedPreferences.getUsername());
new GetAuthorizationToken().execute(ppSharedPreferences.getUsername());
}
Async Tasks
/**
* Gets the authorization token and checks if GCM registration id received.
* Gets reg is if not exists.
*/
private class GetAuthorizationToken extends AsyncTask<String,Void,String>{
#Override
protected String doInBackground(String... params) {
String username = params[0];
String mAuthToken = ppAuthenticator.getPpAuthToken(username);
return mAuthToken;
}
#Override
protected void onPostExecute(String authToken) {
if(!TextUtils.isEmpty(authToken))
{
final String gcmTokenSent = accountManager.getUserData(account, AccountGeneral.GCM_REGISTRATION_ID);
if (gcmTokenSent == null || !gcmTokenSent.equals("true")) {
new GetGcmRegistrationToken().execute(authToken);
} else {
// We have the Gcm Registration Id continue to the main activity
Intent intent = new Intent(tokenContext, MainActivity.class);
startActivity(intent);
finish();
}
}
}
}
private class GetGcmRegistrationToken extends AsyncTask<String,Void,PpTokens>{
#Override
protected PpTokens doInBackground(String... params) {
PpTokens tokens = new PpTokens();
tokens.setAuthToken(params[0]);
try {
if (checkPlayServices()) {
InstanceID instanceID = InstanceID.getInstance(tokenContext);
String regToken = instanceID.getToken(getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
tokens.setRegToken(regToken);
}
} catch (IOException e) {
e.printStackTrace();
}
return tokens;
}
#Override
protected void onPostExecute(PpTokens tokens) {
if (!TextUtils.isEmpty(tokens.getRegToken()))
{
sendRegistrationToServer(tokens.getRegToken(),tokens.getAuthToken());
}
}
}
private class PpTokens
{
private String authToken;
private String regToken;
public String getAuthToken() {
return authToken;
}
public void setAuthToken(String authToken) {
this.authToken = authToken;
}
public String getRegToken() {
return regToken;
}
public void setRegToken(String regToken) {
this.regToken = regToken;
}
}
Send reg id to server
private void sendRegistrationToServer(String regToken, final String authToken) {
final String tag_json_obj = "json_obj_req";
String url = webApiUri + "?gcmRegistrationToken=" + regToken;
JsonObjectRequest objectRequest = new JsonObjectRequest(Request.Method.POST, url, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
Log.d("Name for Reg Token:", response.getString("Name"));
// You should store a boolean that indicates whether the generated token has been
// sent to your server. If the boolean is false, send the token to your server,
// otherwise your server should have already received the token.
accountManager.setUserData(account, AccountGeneral.GCM_REGISTRATION_ID, "true");
Intent intent = new Intent(tokenContext, MainActivity.class);
startActivity(intent);
finish();
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
String errorMessage = JsonErrorMessageHandler.onErrorResponse(error);
accountManager.setUserData(acc, AccountGeneral.GCM_REGISTRATION_ID, "false");
}
}) {
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "Bearer " + authToken);
return headers;
}
};
int socketTimeout = 5000;
int maxRetries = 3;
RetryPolicy policy = new DefaultRetryPolicy(socketTimeout, maxRetries, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
objectRequest.setRetryPolicy(policy);
// Adding request to request queue
AppController.getInstance().addToRequestQueue(objectRequest, tag_json_obj);
}

Categories

Resources