I am developing an Android app where all my users who logged inside my application should be remembered. More specifically, get their Device Key register in their user profile. The problem here is my currSignedDevice shows “null” as shown below and when I try to remember it, It is not being remembered.
I am using AWS Cognito and followed their documentation here https://aws.amazon.com/blogs/mobile/tracking-and-remembering-devices-using-amazon-cognito-your-user-pools/. I set my device settings in Cognito and implemented with code below.
fun getSignInUserDeviceDetails(user: String?):Boolean {
var currSignedDevice = userPool.getUser (user).thisDevice();
println("device is "+ currSignedDevice +"\t")
var changeDeviceSettingsHandler: GenericHandler = object : GenericHandler {
override fun onSuccess() {
// Device status successfully changed
println("device remembered successfully")
}
override fun onFailure(exception: java.lang.Exception) {
// Probe exception for the cause of the failure
println("failure in remember device")
}
}
currSignedDevice?.rememberThisDevice(changeDeviceSettingsHandler)
return true
}
Called this function in my loginfragment after the valid authentication.
login.setOnClickListener {
val email = email.text.toString()
val password = password.text.toString()
if (email.trim().isEmpty()) {
toastError("Enter an email.")
return#setOnClickListener
}
if (password.trim().isEmpty()) {
toastError("Enter a password.")
return#setOnClickListener
}
emailVal = email.toLowerCase()
passwordVal = password
signInDialog?.show()
activity?.let {
(it as MainActivity).setDialog50PercentWidth(signInDialog)
}
viewModel.authenticate(email, password, confirmUserHandler)
AWSClient.instance.getSignInUserDeviceDetails(emailVal)
}
The problem here is my currSignedDevice shows “null” as shown below in the figure and when I try to remember it, It is not being remembered. This is also not going inside of success or failure. It’s directly jumping out from that block.
fun getSignInUserDeviceDetails(user: String?):Boolean {
var currSignedDevice = userPool.getUser (user).thisDevice();
println("device is "+ currSignedDevice +"\t") // gives my currSignedDevice as NULL when I debug
var changeDeviceSettingsHandler: GenericHandler = object : GenericHandler {
override fun onSuccess() {
println("device remembered successfully")
}
override fun onFailure(exception: java.lang.Exception) {
// Probe exception for the cause of the failure
println("failure in remeber device")
}
}
currSignedDevice.rememberThisDevice(changeDeviceSettingsHandler)
return true
}
Related
My ViewModel function
patchProfileEmailAddress fuction wants the emailAddress variable
'
private fun saveUserEmailChanges(email: String?) {
profileRepository.patchProfileEmailAddress(emailAddress)
.onEach {
when (it) {
is Result.Success -> {
setLoading(false)
emailAddress = email
updateActionState(
MyProfilePersonInformationASMActionState.DismissBottomSheet)
updateActionState(MyProfilePersonInformationASMActionState.OnSuccess)}
is Result.Error -> {
setLoading(false)
updateActionState(
MyProfilePersonInformationASMActionState
.ShowErrorMessage(it.errorResponse?.message))}
is Result.Loading -> setLoading(true)} }
.launchIn(viewModelScope)}'
My Fragment part
'
var usersNewMail : String? =null
private fun setOnClickListeners() {
binding.apply {
adressArrowImageView.setOnClickListener{ openBodyBottomSheet() }
mailArrowImageView.setOnClickListener{ clickMailArrowImageView() }
checkOkeyImageView.setOnClickListener{ clickOkeyCheckImageView() }}}
private fun getMailChange(){
viewModel.saveUserEmailChanges(usersNewMail)
}
private fun clickMailArrowImageView(){
binding.apply {
txtEditMail.isEnabled = true
checkOkeyImageView.isVisible = true
mailArrowImageView.isVisible = false
}
}
private fun clickOkeyCheckImageView(){
binding.apply {
txtEditMail.isEnabled = false
checkOkeyImageView.isVisible = false
mailArrowImageView.isVisible = true
usersNewMail = txtEditMail.text.toString()
getMailChange()
}
}'
Postman works fine. In application patch response 500 Internal Server Error. My API wants string and I'm giving string.
It's certain you are sending something wrong if it works in Postman, so the first you have to do in any case is to know what you are sending; which can be done in various ways.
For example, if you are using OkHttp then have an interceptor for logging.
This way you can tell in LogCat what's going on.
After finding out what you are sending, if you still need help just update your question and I'll update my answer.
I'm trying to set up a Google OneTap SignIn button to my developed App (I'm not using Firebase to sign in) guiding by this source:
https://developers.google.com/identity/one-tap/android/get-started
I've created both OAuth & Web credentials on Cloud Console. To generate OAuth Id I took SHA1 which was provided by Android Studio in signing-in report (I took develop SHA-1, but they are all the same anyway).
I've put to R.string.default_web_client_id the Client Id from WebAuth (Not an Android Id from OAuth).
As I use Firebase RTDB for storing some data, I set this SHA-1 there as well. The project name in Manifest, Cloud Console and Firebase Console are the same. From Firebase I downloaded "google-services.json" and put it in app root. On Firebase I also set valid service email.
This is how I implemented OneTap on login activity:
class LoginActivity : AppCompatActivity() {
...
private lateinit var oneTapClient: SignInClient
private lateinit var signUpRequest: BeginSignInRequest
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
oneTapClient = Identity.getSignInClient(this)
signUpRequest = BeginSignInRequest.builder()
.setPasswordRequestOptions(BeginSignInRequest.PasswordRequestOptions.builder()
.setSupported(false)
.build())
.setGoogleIdTokenRequestOptions(BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
// Your server's client ID, not your Android client ID.
.setServerClientId(getString(R.string.default_web_client_id))
// Only show accounts previously used to sign in.
.setFilterByAuthorizedAccounts(false)
.build())
// Automatically sign in when exactly one credential is retrieved.
.setAutoSelectEnabled(false)
.build()
}
// THIS CALLED FROM FRAGMENT WHEN GOOGLE BUTTON IS CLICKED
fun onGoogleClick() {
when (GoogleApiAvailability().isGooglePlayServicesAvailable(applicationContext)) {
0 -> {
beginGoogleSignIn()
}
1 -> {
toaster.show("Google services required")
}
2 -> {
toaster.show("Google services update required")
}
}
}
private fun beginGoogleSignIn() {
val tag = "$atag-beginGoogleSignIn"
oneTapClient.beginSignIn(signUpRequest)
.addOnSuccessListener(this) { result ->
try {
startIntentSenderForResult(
result.pendingIntent.intentSender, library.GOOGLE_SIGNIN_REQUEST_CODE,
null, 0, 0, 0)
} catch (e: IntentSender.SendIntentException) {
logg.d(atag, "Couldn't start One Tap UI: ${e.localizedMessage}")
}
}
.addOnFailureListener(this) { e ->
// No Google Accounts found. Just continue presenting the signed-out UI.
toaster.show("Please sign in to your google account on your phone")
logg.d(tag, e.localizedMessage)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val tag = "$atag-onActivityResult"
super.onActivityResult(requestCode, resultCode, data)
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
if (requestCode == library.GOOGLE_SIGNIN_REQUEST_CODE) {
try {
val credential = oneTapClient.getSignInCredentialFromIntent(data)
token = credential.googleIdToken
when {
token != null -> {
// Got an ID token from Google. Use it to authenticate
// with your backend.
login = credential.id
name = credential.displayName
avatarUrl = credential.profilePictureUri.toString()
tryToLogin()
}
else -> {
// Shouldn't happen.
logg.d(atag, "No ID token!")
}
}
} catch (e: ApiException) {
when (e.statusCode) {
CommonStatusCodes.CANCELED -> {
toaster.show("Please sign in to your google account on your phone")
logg.d(tag, "One-tap dialog was closed.")
// Don't re-prompt the user.
}
CommonStatusCodes.NETWORK_ERROR -> {
logg.d(tag, "One-tap encountered a network error.")
// Try again or just ignore.
}
else -> {
logg.d(tag, "Couldn't get credential from result." +
" (${e.localizedMessage})")
}
}
}
}
}
}
The main problem is that on some of AVD it works well, on other AVD id gives an Error:
16: Cannot find a matching credential.
However on this AVD Google Services is up to date, and user is logged in to Google Play
On real device I got this error:
10: Caller not whitelisted to call this API.
Google services is also up to date here and user is logged in to Play Store.
Everywhere I used my real gmail address.
What can be wrong?
UPDATE
Spending a lot of time trying to solve this I have just figured out that OneTap is working on some devices, and is not working on other. Also tried to re-create credentials several times.
Not having very much time to solve this I just use both OneTap way and alternate way to sign in with Google credentials.
// In activity
lateinit var oneTapClient: SignInClient
lateinit var signUpRequest: BeginSignInRequest
private lateinit var gso: GoogleSignInOptions
lateinit var mGoogleSignInClient: GoogleSignInClient
private fun initApp() {
oneTapClient = Identity.getSignInClient(this)
signUpRequest = BeginSignInRequest.builder()
.setPasswordRequestOptions(
BeginSignInRequest.PasswordRequestOptions.builder()
.setSupported(false)
.build()
)
.setGoogleIdTokenRequestOptions(
BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
// Your server's client ID, not your Android client ID.
.setServerClientId(getString(R.string.default_web_client_id))
// Only show accounts previously used to sign in.
.setFilterByAuthorizedAccounts(false)
.build()
)
// Automatically sign in when exactly one credential is retrieved.
.setAutoSelectEnabled(false)
.build()
gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestProfile()
.requestEmail()
.requestIdToken(getString(R.string.default_web_client_id))
.build()
mGoogleSignInClient = GoogleSignIn.getClient(this, gso)
}
// Somewhere else in fragment, 'parent' is reference to activity
// Call this when button clicked
private fun beginGoogleSignIn() {
parent.oneTapClient.beginSignIn(parent.signUpRequest)
.addOnSuccessListener(parent) { result ->
try {
startIntentSenderForResult(
result.pendingIntent.intentSender, state.library.GOOGLE_SIGNIN_REQUEST_CODE,
null, 0, 0, 0, null)
} catch (e: IntentSender.SendIntentException) {
stopSignIn("Couldn't start One Tap UI: ${e.localizedMessage}")
}
}
.addOnCanceledListener(parent) {
stopSignIn("Cancelled")
}
.addOnFailureListener(parent) { e ->
// Use alternate sign in
val signInIntent = parent.mGoogleSignInClient.signInIntent
startActivityForResult(signInIntent, state.library.GOOGLE_ALT_SIGNIN_REQUEST_CODE)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
if (requestCode == state.library.GOOGLE_SIGNIN_REQUEST_CODE) {
try {
handleOneTapSignIn(data)
} catch (e: ApiException) {
val message: String
when (e.statusCode) {
CommonStatusCodes.CANCELED -> {
message = "Please sign in to your google account on your phone"
// Don't re-prompt the user.
}
CommonStatusCodes.NETWORK_ERROR -> {
// Try again or just ignore.
message = "One-tap encountered a network error."
}
else -> {
message = "Couldn't get credential from result." +
" (${e.localizedMessage})"
}
}
stopSignIn(message)
}
} else if (requestCode == state.library.GOOGLE_ALT_SIGNIN_REQUEST_CODE) {
try {
handleAlternateSignIn(data)
} catch (e: ApiException) {
if (e.statusCode == 12501) {
// Dismissed
stopSignIn()
} else {
stopSignIn("${e.statusCode}")
}
}
}
}
private fun handleOneTapSignIn(data: Intent?) {
val credential = parent.oneTapClient.getSignInCredentialFromIntent(data)
val token = credential.googleIdToken
when {
token != null -> {
// Got an ID token from Google. Use it to authenticate
// with your backend.
login = credential.id
avatarUrl = credential.profilePictureUri?.toString() ?: ""
tryToLogin()
}
else -> {
// Shouldn't happen.
stopSignIn("No ID token!")
}
}
}
private fun handleAlternateSignIn(data: Intent?) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
val account = task.getResult(ApiException::class.java)
// Signed in successfully, show authenticated UI.
login = account.email!!
token = account.idToken ?: ""
avatarUrl = account.photoUrl?.toString() ?: ""
tryToLogin()
} catch (e: ApiException) {
// The ApiException status code indicates the detailed failure reason.
// Please refer to the GoogleSignInStatusCodes class reference for more information.
val message = when (e.statusCode) {
CommonStatusCodes.NETWORK_ERROR -> {
"Could not reach network"
}
else -> {
"SignIn failed with exception $e"
}
}
stopSignIn(message)
}
}
UPDATE 2
It seems 16: Cannot find a matching credential on Android Emulators is related not to user credentials attempting to sign in, but an app credential in Google Cloud Console.
The issue is not reproduced in my case when I filled consent page at Google Cloud Console as described at Get started with One Tap sign-in and sign-up
(I have left empty fields for links to EULA, etc.). Then I have changed package name (for another reasons), then double check that this package name was the same in both credentials configs on Google Cloud Console and in google-services.json. Also I checked that google-services.json is located under [project_root]/app directory.
I will check this on real device in a few days.
NOTE: I was able to figure this out. There is no need to change the rules in Firebase. See code below.
ORIGINAL POST
I have an IOS app and I decided to build the Android/Kotlin version and I'm having a hard time with Firebase/isEmailVerify. I'm able to register a new user and send the email for verification, but, if I don't verify, I'm still able to login. I'm new at Kotlin. Any help is greatly appreciated.
UPDATED CODE
class LoginActivity : AppCompatActivity() {
lateinit var auth: FirebaseAuth
private var emailVerifier: Boolean = true
private val emailVerificationAlert = { _: DialogInterface, _: Int ->
Toast.makeText(this.applicationContext, android.R.string.yes, Toast.LENGTH_SHORT).show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
auth = FirebaseAuth.getInstance()
}
private fun verifyEmail() {
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
emailVerifier = user.isEmailVerified()
}
if (emailVerifier) {
finish()
} else {
userDidNotVerify()
auth.signOut()
}
}
fun loginBtnClicked(view: View) {
val email = loginEmailTxt.text.toString()
val password = loginPasswordTxt.text.toString()
auth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener { exception ->
println("USER LOGGED IN")
verifyEmail()
}
.addOnFailureListener { exception ->
Log.e("Exception", "Could not sign in user - ${exception.localizedMessage}")
}
}
private fun userDidNotVerify() {
val builder = android.app.AlertDialog.Builder(this)
with(builder) {
this.setTitle("Confirm your email address.")
this.setMessage("A confirmation email has been sent to" + " " + (loginEmailTxt.text) + " " +
"." + " " + "Click on the confirmation link to activate your account")
this.setPositiveButton("OK", DialogInterface.OnClickListener(function = emailVerificationAlert))
this.show()
}
}
fun loginCreateClicked(view: View) {
val createIntent = Intent(this, CreateUserActivity::class.java)
startActivity(createIntent)
}
}
It's expected that the user can still sign in before the email is verified. This provides a way for your app to allow the user to request another verification email to be sent, in case something happened to the first one.
If you want to restrict what the user can do before the email is verified, you can check isEmailVerfied() on the UserInfo object, and you can use the auth.token.email_verified in security rules to limit their access to databases and storage also provided by Firebase.
When the user wants to change his password, I prompt him with a Dialog to Re-authenticate. In this dialog, he can re-auth with password or via Google/Facebook. But when I update the firebase email, the user gets signed out and I want to avoid this.
Consider this code:
private fun btnGoogle(){
val acct = GoogleSignIn.getLastSignedInAccount(context)
Timber.d(acct?.email)
if (acct != null) {
val credential = GoogleAuthProvider.getCredential(acct.idToken, null)
auth(credential)
}
}
private fun btnFacebook(){
val token = AccessToken.getCurrentAccessToken()
if(token!=null){
val credential = FacebookAuthProvider.getCredential(token.token)
auth(credential)
}else{
activity?.showBackgroundToast(getString(R.string.no_facebook_auth), Toast.LENGTH_LONG)
}
}
private fun auth(credential: AuthCredential) {
FirebaseAuth.getInstance().currentUser!!.reauthenticate(credential)
.addOnFailureListener{e -> activity?.showBackgroundToast(e.localizedMessage, Toast.LENGTH_LONG)}
.addOnCompleteListener { task ->
if (task.isSuccessful) {
mListener.onReAuthentication(true)
dialog.dismiss()
} else {
activity?.showBackgroundToast(task.exception?.localizedMessage, Toast.LENGTH_LONG)
}
}
}
Then I call this function to actually update the email:
fun updateEmail(newEmail: String): Completable {
return Completable.create { emitter ->
val currentUser = FirebaseAuth.getInstance().currentUser
currentUser!!.updateEmail(newEmail).addOnCompleteListener {
if (it.isSuccessful) {
Timber.d("updated email to %s", newEmail)
emitter.onComplete()
} else {
emitter.onError(Throwable(it.exception?.localizedMessage))
}
}
}
}
Everything works fine until I finally update the email. When I do, Firebase signs the user out every time!! (the following is from Android Studio logcat)
D/FirebaseAuth: Notifying id token listeners about a sign-out event.
D/FirebaseAuth: Notifying auth state listeners about a sign-out event.
It only happens when I change the email. Since I have an auth state listener, the user gets redirected to login screen after successfully updating the email, which makes no sense to me. Why? How can I avoid this?
I'm trying to use AWS Cognito user pools in combination with the AWS API Gateway.
Logging in works fine, when entering the credentials the success handler is called and I can see the credentials. When verifying the JWT token at jwt.io I can also see that the user is correct.
However, when calling the API Gateway using the ApiClientFactory I always receive an error: com.amazonaws.mobileconnectors.apigateway.ApiClientException: Basic (classic) flow is not supported with RoleMappings, please use enhanced flow. (Service: AmazonCognitoIdentity; Status Code: 400; Error Code: InvalidParameterException; Request ID: 1a61f1fd-91d8-11e8-82bc-675071b1c307) (Service: null; Status Code: 0; Error Code: null; Request ID: null)
Please see my code below:
Main activity:
AWSMobileClient.getInstance().initialize(this) {
// Obtain the reference to the AWSCredentialsProvider and AWSConfiguration objects
// Use IdentityManager#getUserID to fetch the identity id.
IdentityManager.getDefaultIdentityManager().getUserID(object : IdentityHandler {
override fun onIdentityId(identityId: String) {
Log.d("MainActivity", "Identity ID = " + identityId)
}
override fun handleError(exception: Exception) {
Log.d("MainActivity", "Error in retrieving the identity" + exception)
}
})
}.execute()
LoginFragment:
val authenticationHandler = object : AuthenticationHandler {
override fun getAuthenticationDetails(continuation: AuthenticationContinuation, userID: String) {
val authDetails = AuthenticationDetails(inputUsername.text.toString(), inputPassword.text.toString(), null)
// Now allow the authentication to continue
continuation.setAuthenticationDetails(authDetails)
continuation.continueTask()
}
override fun onSuccess(userSession: CognitoUserSession, newDevice: CognitoDevice?) {
progressLoader.visibility = View.GONE
(activity as? OnboardingActivity)?.proceedAfterLogin()
}
override fun onFailure(exception: Exception) {
progressLoader.visibility = View.GONE
val snackbar = Snackbar.make(view, R.string.ERR_GENERAL, Snackbar.LENGTH_LONG)
snackbar.show()
progressLoader.visibility = View.GONE
}
override fun getMFACode(continuation: MultiFactorAuthenticationContinuation) {
continuation.continueTask()
}
override fun authenticationChallenge(continuation: ChallengeContinuation) {
continuation.continueTask()
}
}
loginButton.setOnClickListener {
val userPool = CognitoUserPool(context, AWSMobileClient.getInstance().configuration)
val user = userPool.getUser(inputUsername.text.toString())
progressLoader.visibility = View.VISIBLE
user.getSessionInBackground(authenticationHandler)
}
Api client:
val factory = ApiClientFactory().credentialsProvider(AWSMobileClient.getInstance().credentialsProvider)
val = factory.build(MyClient::class.java)
try {
val request = GetChallengesRequest("", nextPageKey)
val response = client.getRunningChallenges(request)
} catch (t: Throwable) {
// This catch is allways called with the error
}
The config is loaded using the awsconfiguration.json which is stored in the raw resource folder.
When setting a breakpoint in AWS4Signer sign method I can see the sign method is called with AnonymousAWSCredentials but I really can't figure out why, as I call the method after logging in.
I hope someone can help me resolve this weird issue, it's been bugging me for days!