I'm trying to get the Google Refresh token, the thing is that i haven't seen my current scenario anywhere, what i'm trying to accomplish is get the refresh token from a user auth token already generated by a google sign in from a native android app, basically i request every google permission i need on the app, and the the auth code from the sign in, with that auth code i send it to my Firebase functions backend, there i try to get the refresh token but i always get invalid_grant from google, so i don't know where could i be messing up
I saw that requesting oauth offline may solve the problem, but i'm sign in in the android app, so i cannot make a sign in request offline from the backend, i need the sign in and authentication to be only on the android app and i need this to make Google assistant requests over the backend and for this, the only thing missing is the refresh token
I have my google oauth2 key from google cloud console and the client initialized
var OAuth2 = google.auth.OAuth2;
var _client_id = require('./config/secrets/omni.json').installed.client_id;
var _client_secret = require('./config/secrets/omni.json').installed.client_secret;
var redirect = require('./config/secrets/omni.json').installed.redirect_uris[1];
return new OAuth2(
_client_id,
_client_secret,
redirect
);
And finally try to get the token:
function getRefreshToken(authCode, idFBUser) {
return new Promise((resolve, reject) => {
getOAuthClient().getToken(authCode, function(err, token) {
if (!err) {
console.log(`Get Token success, token: ${token}`);
getOAuthClient().setCredentials(token);
saveRefreshFirebase(idFBUser, token)
resolve(token);
} else {
console.log("Get Token error", err);
reject(err);
}
});
});
}
the error i'm getting exactly is this one: { error: 'invalid_grant', error_description: 'Bad Request' }
The method i use in the android app to request the auth code is this one:
Creating the GoogleSignIn instance
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.requestId()
.requestIdToken(getClientId())
.requestServerAuthCode(getClientId())
.requestScopes(ASSISTANT_SCOPE)
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso)
Calling the Sign in Intent:
val signInIntent = googleSignInClient.signInIntent
startActivityForResult(signInIntent, RESULT_GOOGLE_SIGNIN)
Handle the Sign in Result:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
RESULT_GOOGLE_SIGNIN -> {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
task.result?.let { account ->
val id = account.id
val idToken = account.idToken
val authCode = account.serverAuthCode
Log.d("GoogleSignIn", "ID: $id")
Log.d("GoogleSignIn", "ID Token: $idToken")
Log.d("GoogleSignIn", "Auth code: $authCode")
btnLogin.text = "Sign Out"
loggedIn = true
} ?: Log.e("GoogleSignIn", "Exception: ${task.exception}")
} catch (e: Exception) {
Log.e("GoogleSignIn", "Catch Exception: $e")
}
}
}
}
}
and the authCode is the one i used to try to get the Refresh Token in the backend
You need to call
.requestServerAuthCode(getClientId(),true)
instead of
.requestServerAuthCode(getClientId())
If you want to get a refresh token. The javascript code you posted works with refresh tokens.
This is the easiest way to authenticate your app because all other kinds of token will expire. Refresh tokens dont expire. The Google Sign in library gives you an access code which you are already using to obtain a refresh token. You need to save this refresh token and send it to Google in order to obtain access tokens. The access token can then finally be used to make API requests.
By the way, that is actually what 'offline access' refers to. Took me a while to figure this out as well.
Related
I'm trying to save the Device Token for an Android device and use it with Firebase Cloud Messaging, but I'm having a problem, and I think I'm getting an incorrect token.
When I try to request the Device Token from an Android device, I use the following function:
FirebaseInstallations.getInstance().id.addOnCompleteListener { task: Task<String?> ->
if (task.isSuccessful) {
val token = task.result
if (token != null && userID != null) {
Log.d("token ---->>", token)
}
}
However, when I get the token, it's super short, almost like it's being cut off. This is an example: fEOC4mBXRguYo4ur1v-fs_
I've comparing it to the device ID's I would get on iOS devices. I also tried to use the token to receive notifications, but it wouldn't work.
On iOS, the device id was generated by th device, and not Firebase, which is what the function above seems to be doing.
What am I doing wrong?
FirebaseInstallations isn't the correct token for FCM! I had to use FirebaseMessaging and use the following:
FirebaseMessaging.getInstance().token
.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(TAG, "Fetching FCM registration token failed", task.exception)
return#OnCompleteListener
}
val token = task.result
Log.d(TAG, token)
})
I've followed the guides on https://developers.google.com/identity/sign-in/android/sign-in and I can get the Google ID token of the signed in user after the user signed in. But token will expires in 1 hour.
How can I refresh a valid Google ID token after the old one expires, without bothering user to manually sign in again and again? I have tried using silent sign-in when token expires. But it doesn't seem to work. Is there any way to get a valid Google ID token? Thanks in advance!!
For first time signing in,
private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
try {
val account = completedTask.getResult(ApiException::class.java)
val idToken = account!!.idToken
CheckDB(this, db).execute()
} catch (e: ApiException) {
Log.e("TAG","signInResult:failed code=" + e.statusCode)
}
}
when token expires, I am using silent signin
override fun onStart() {
super.onStart()
val account = GoogleSignIn.getLastSignedInAccount(this)
if(account!=null) {
sign_in_button.visibility = GONE
mGoogleSignInClient!!.silentSignIn().addOnCompleteListener(this, object : OnCompleteListener<GoogleSignInAccount>{
override fun onComplete(p0: Task<GoogleSignInAccount>) {
val account = p0.getResult(ApiException::class.java)
CheckDB(this#SignInActivity, db).execute()
}
})
}
}
When your user is signed in (the first and only time), use the obtained token to authenticate to your own backend server. No need to re-signin at google's.
Details here: https://developers.google.com/identity/sign-in/android/backend-auth
I have an app that get Google credentials in order to register user into my API. I need the idToken provided by the Google flow.
Following this tutorial, I was able to sign in.
Then, I have a splash screen. I want it to verify if the user is already allowed by Google, to go to the main activity. If not, go to the login activity.
But according to the tutorial's code, it is asking Firebase using the code below, which provides me, of course, a Firebase token, not the Google one.
override fun onStart() {
super.onStart()
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
startActivity(MainActivity.getLaunchIntent(this))
finish()
}
}
So, the question is: how can I get the Google information to retrieve the token, so my API can validate if the token?
If someone else need to implements something alike, I'll share how I did it.
There is a silentSignIn from Google SDK which allows to do it.
So, this is the code:
val signInOptions = getSignInOptions(this)
val googleSignInClient = GoogleSignIn.getClient(this, signInOptions)
googleSignInClient.silentSignIn().addOnCompleteListener {
val account: GoogleSignInAccount? = it.result
if (account != null) {
// success
}
else {
// failure
}
}
I am using Amazon Cognito for authentication and I want to ask that my access token and id token get expired like in one hour, moreover I have gone through many answers they have told to use refresh token as they have a validity of 10 years,
so my question is how to use refresh token in android?
Right now after login, I am setting CognitoCachingCredentialProvider's login map to the token - and I am using both Facebook and email login.
val authenticationHandler = object : AuthenticationHandler {
override fun onSuccess(userSession: CognitoUserSession?, newDevice: CognitoDevice?) {
//After Authentication User Cognito Access Id and Access Secret Extraction
currentSession = userSession!!
//Getting Session Token
val id = currentSession.idToken.jwtToken
//Credential Provider
val cognitoCachingCredentialsProvider = CognitoCachingCredentialsProvider(this#LoginActivity,resources.getString(R.string.cognito_identity_pool_id),Regions.myRegion)
cognitoCachingCredentialsProvider.clear()
//Login Map
val login = HashMap<String,String>()
login["myString"] = id
cognitoCachingCredentialsProvider.logins = login
//Off the main thread
SimpleAsyncTask(this#LoginActivity,cognitoCachingCredentialsProvider).execute()
}
override fun authenticationChallenge(continuation: ChallengeContinuation?) {
continuation?.continueTask()
}
override fun getAuthenticationDetails(authenticationContinuation: AuthenticationContinuation, userId: String) {
// The API needs user sign-in credentials to continue
Log.d(TAG, "userId is : $userId")
val authenticationDetails = AuthenticationDetails(userId, password, null)
authenticationDetails.authenticationType = "USER_PASSWORD"
// Pass the user sign-in credentials to the continuation
authenticationContinuation.setAuthenticationDetails(authenticationDetails)
// Allow the sign-in to continue
authenticationContinuation.continueTask()
}
override fun getMFACode(multiFactorAuthenticationContinuation: MultiFactorAuthenticationContinuation) {
// Multi-factor authentication is required; get the verification code from user
multiFactorAuthenticationContinuation.setMfaCode(null)
// Allow the sign-in process to continue
multiFactorAuthenticationContinuation.continueTask()
}
override fun onFailure(exception: Exception) {
// Sign-in failed, check exception for the cause
Log.e(TAG, "${exception.message}")
}
}
// Sign in the user
user.getSessionInBackground(authenticationHandler)
}
internal class SimpleAsyncTask(private val activity: Activity,private val credential:CognitoCachingCredentialsProvider) :
AsyncTask<Void, Void, Void>() {
override fun doInBackground(vararg p0: Void?):Void ?{
credential.refresh()
credential.setPersistenceEnabled(true)
return null
}
Similar kind of code is also used for facebook login like this
FacebookCallback<LoginResult> {
override fun onSuccess(loginResult: LoginResult) {
//Getting access Token
val accessToken = loginResult.accessToken.token
//Credentials Extraction
val credentials = CognitoCachingCredentialsProvider(this#LoginActivity,resources.getString(R.string.cognito_identity_pool_id),Regions.myRegion)
credentials.clear()
//Map of login
val login = HashMap<String,String>()
login["graph.facebook.com"] = accessToken
//Setting the value of map
credentials.logins = login
//Off the main thread
SimpleAsyncTask(this#LoginActivity,credentials).execute()
}
override fun onCancel() {
//Cancel code
Toast.makeText(this#LoginActivity,"Canceled",Toast.LENGTH_SHORT).show()
}
override fun onError(exception: FacebookException) {
//Error code
Toast.makeText(this#LoginActivity,exception.toString(),Toast.LENGTH_SHORT).show()
}
})
Now I am using this to check user status of login, I check the condition cognitoCachingCredentialProvider.cachedId!=null for checking user login.
But it gets logged in for like an hour how to get user logged in for a long long time
Refresh token is distinctly different from id or access token. You can use refresh token to get fresh access and id tokens (as the name suggests). When you call getSession it should automatically refresh your tokens if they have expired AND if your refresh token hasn't expired.
More information: https://stackoverflow.com/a/39480690/6941447
Okay, so as Ninad said we have to use getSession for refreshing credentials you have to just add this check.
if(credentialsProvider.cachedIdentityId==null)
{
userPool.currentUser.getSessionInBackground(AuthenticationHandler)
}
Make an authentication Handler seperate for this and your tokens are refreshed.
I've followed the guides on https://developers.google.com/ and I can get the Google ID token of the signed in user after the user signed in. But I noticed that the token will expire in 1 hour. I cannot find any official reference that tells me how to deal with expired Google ID token, so I can only ask the user to click the Google sign-in button again.
How can I refresh a valid Google ID token after the old one expires, without bothering user to manually sign in again and again?
Yes, Google ID tokens are issued for one hour validity and will expire, you can simply use silentSignIn in your app to get a new one without any user interaction. If your existing token hasn't expired yet, you will get the (cached) version back (OptionalPendingResult returned will have isDone() == true); if it expired already, you will get a refreshed one (but it will take a little longer and thus OptionalPendingResult isDone() will be false).
Here is sample code (UI thread, see note below about a worker thread):
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.server_client_id))
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
...
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
// If the user's cached credentials are valid, the OptionalPendingResult will be "done"
// and the GoogleSignInResult will be available instantly.
Log.d(TAG, "Got cached sign-in");
GoogleSignInResult result = opr.get();
handleSignInResult(result); // result.getSignInAccount().getIdToken(), etc.
} else {
// If the user has not previously signed in on this device or the sign-in has expired,
// this asynchronous branch will attempt to sign in the user silently. Cross-device
// single sign-on will occur in this branch.
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
#Override
public void onResult(GoogleSignInResult googleSignInResult) {
handleSignInResult(googleSignInResult); // result.getSignInAccount().getIdToken(), etc.
}
});
}
Keep in mind whether you call silentSignIn on a UI thread or worker thread. If you call it on worker thread, take a look at this post with blockingConnect() + await() which simplifies the code a lot:
Silent sign in to retrieve token with GoogleApiClient
I ran into this problem while trying to use the Google Photos Java API along with Google Sign In in an Android app. Here is the problem I was facing. I essentially solved it by periodically fetching a new instance of PhotosLibraryClient, which itself fetches a new GoogleAccountCredential in the process. But I did have to be careful to properly tear down the existing PhotosLibraryClient before creating a new one:
var lastGooglePhotosClientRefreshTimestamp = 0L
fun createGooglePhotosClient(token: String): PhotosLibraryClient? {
return try {
App.googlePhotosClient?.let { client ->
if (!client.isShutdown) {
client.shutdownNow()
client.awaitTermination(30, TimeUnit.SECONDS)
client.close()
}
}
val settings = PhotosLibrarySettings.newBuilder().setCredentialsProvider(
FixedCredentialsProvider.create(getUserCredentials(token))).build()
val photosLibraryClient = PhotosLibraryClient.initialize(settings)
if (photosLibraryClient == null) {
logMessage(TAG, "Google Photos library client could not be fetched.")
} else {
App.googlePhotosClient = photosLibraryClient
lastGooglePhotosClientRefreshTimestamp = System.currentTimeMillis()
}
return photosLibraryClient
} catch (throwable: Throwable) {
logMessage(TAG, throwable.stackTraceToString())
null
}
}
private fun getUserCredentials(token: String): UserCredentials {
val accessToken = AccessToken(token, null)
return UserCredentials.newBuilder()
.setClientId(App.appContext.getString(R.string.google_client_id))
.setClientSecret(App.appContext.getString(R.string.google_client_secret))
.setAccessToken(accessToken)
.build()
}
// Call this method at or near the time of credential expiration
// to avoid an UnauthenticatedException. The server will only
// grant you a new credential in such a circumstance
// (otherwise it will return the same credential).
suspend fun refreshGooglePhotosClient() {
withContext(Dispatchers.Default) {
if (App.googleSignInAccount == null) return#withContext
val credential = getGoogleAccountCredential(App.googleSignInAccount!!, true, false)
try {
val token = credential.token
createGooglePhotosClient(token)
} catch (throwable: Throwable) {
logMessage(TAG, "Error while calling refreshGooglePhotosClient(): ${throwable.stackTraceToString()}")
}
}
}
// Fetch a GoogleSignInClient with all the required Scopes requested.
fun getGoogleSignInClient(activity: Activity, needsPhotosScope: Boolean, needsDriveScope: Boolean): GoogleSignInClient {
val signInOptions = GoogleSignInOptions
.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestServerAuthCode(App.appContext.getString(R.string.google_client_id), true)
.requestEmail()
val scopes = getRequiredGoogleServiceScopes(needsPhotosScope, needsDriveScope)
when (scopes.size) {
0 -> throw RuntimeException("Attempting to use Google Sign-In without requesting any Scopes.")
1 -> signInOptions.requestScopes(scopes[0])
2 -> signInOptions.requestScopes(scopes[0], scopes[1])
}
return GoogleSignIn.getClient(activity, signInOptions.build())
}
fun getGoogleAccountCredential(googleSignInAccount: GoogleSignInAccount, needsPhotosScope: Boolean,
needsDriveScope: Boolean): GoogleAccountCredential {
// Create list of Scopes in String form.
val scopeStringList = getRequiredGoogleServiceScopes(needsPhotosScope, needsDriveScope).map { it.scopeUri }
val credential = GoogleAccountCredential.usingOAuth2(App.appContext, scopeStringList)
credential.selectedAccount = googleSignInAccount.account
return credential
}
// Fetch a List of Scopes that match the requirements based on the user's current search criteria.
fun getRequiredGoogleServiceScopes(needsPhotosScope: Boolean, needsDriveScope: Boolean): List<Scope> {
val scopes = mutableListOf<Scope>()
if (needsPhotosScope) scopes.add(Scope(googlePhotosScope))
if (needsDriveScope) scopes.add(Scope(DriveScopes.DRIVE))
return scopes
}