Getting AdWords customerId without a refresh token - asp.net

I'm working on a Web Application that allows user to connect AdWords account.
1) So once user connected their AW account I am getting token from AW API that includes an access token and a refresh token. So far so good, it's just a typical OAuth2 process.
2) Next time another user connects same AW account, AW would not provide me with a refresh token, assuming I have it stored somewhere, which is expected.
So here is the problem, there doesn't seem to be a way to get user information without the refresh token, meaning I can't identify the user to retrieve the refresh token.
I'm using .NET library (Google.Apis.Analytics.v3) and it doesn't allow me to request customer information without providing the refresh token...
Here is the sample code:
var tokenResponse = await adWordsService.ExchangeCodeForTokenAsync(code, redirectUri);
var adWordsUser = adWordsService.GetAdWordsUser();
var customerService = (CustomerService)adWordsUser.GetService(AdWordsService.v201502.CustomerService);
var customer = customerService.get();
adWordsService is just a wrapper around the API.
so when I execute this line var customer = customerService.get() I get a following error:
ArgumentNullException: Value cannot be null.
Parameter name: AdWords API requires a developer token. If you don't have one, you can refer to the instructions at https://developers.google.com/adwords/api/docs/signingup to get one.
Google.Api.Ads.AdWords.Lib.AdWordsSoapClient.InitForCall(String methodName, Object[] parameters)
Developer token is there and so are all the client IDs.
If I add this line adWordsUser.Config.AuthToken = tokenResponse.AccessToken; before making the call, it complains about the refresh token.
ArgumentNullException: Value cannot be null.
Parameter name: Looks like your application is not configured to use OAuth2 properly. Required OAuth2 parameter RefreshToken is missing. You may run Common\Utils\OAuth2TokenGenerator.cs to generate a default OAuth2 configuration.
Google.Api.Ads.Common.Lib.OAuth2ProviderBase.ValidateOAuth2Parameter(String propertyName, String propertyValue)
So the question is, how does one acquire user information (in this case customerId, according to this article https://developers.google.com/adwords/api/docs/guides/optimizing-oauth2-requests#authenticating_multiple_accounts) during authentication for the purpose of storing the refresh token?

So I just found the problem is in the adWordsService. When the AdWordsUser is created it isn't assigned the updated authentication provider which contains the latest authentication token. Once this authentication provider is added the AdWordsUser doesn't try to refresh the token since the token hasn't expired yet, thus there is no need for the refresh token.

Related

Incremental authorization with Firebase and GoogleAuthProvider

I'm using Firebase v8 with the GoogleAuthProvider.
Firebase documentation provides the following code to authenticate the user.
firebase.auth().signInWithPopup(provider).then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
Questions
Google's Using OAuth 2.0 to Access Google APIs article recommends incremental authorization (it's not Firebase, but the recommendation is clear)
It is generally a best practice to request scopes incrementally, at
the time access is required, rather than up front. For example, an app
that wants to support saving an event to a calendar should not request
Google Calendar access until the user presses the "Add to Calendar"
button.
AFAICT, there is no way to achieve incremental authorization with Firebase without re-authenticating the user. While scopes can be added to GoogleAuthProvider using addScope, a subsequent call to signInWithPopup is required (i.e. the user is re-authenticated). Is there any way to prompt only for authorization (e.g. Drive access) without re-authenticating?
Assuming the access token is short lived, can the Google ID token be used to obtain a new access token? Is re-authenticating the user the only way to obtain a new access token?
Is there a way to determine whether the access token has expired?

Firebase admin - get Google OAuth token

I have a web application where users can sign in with Google.
To the sign-in process, I add a scope to be able to access Google Calendar.
Now that the user is signed in, I would like to - in server-side - get their current Google access token in order to make a request and get a list of their events.
Is there a way to get the current OAuth token (no need for refresh token) in order for me to make this completely on the server-side?
I'd say that you can check this article and put special attention to the recommendation for websites.
I understand you have configured already the consent screen, which is the first step of the basic steps on using OAuth 2.0. So I understand that you only have to perform the following steps:
Obtain an access token from the Google Authorization Server
Examine scopes of access granted by the user.
Send the access token to an API
I think you can also give a look to this other doc for more GCP insights over your goal to authorize the request using user tokens
Edited:
Regarding the Firebase Authentication, I understand this happens at the user's device, and you could use some code to retrieve the token and then send it to your back end servers as mentioned in here.
As a sample here there's the sample code for retrieving the token in Android:
FirebaseUser mUser = FirebaseAuth.getInstance().getCurrentUser();
mUser.getIdToken(true)
.addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
public void onComplete(#NonNull Task<GetTokenResult> task) {
if (task.isSuccessful()) {
String idToken = task.getResult().getToken();
// Send token to your backend via HTTPS
// ...
} else {
// Handle error -> task.getException();
}
}
});
A little about OAuth 2.0
Whenever a user signs up to your app/website via Google or 3rd Party, an Authorization Code, this Authorization Code is exchanged for an AccessToken & RefreshToken.
The AccessToken sent via Google are valid generally for 60 minutes.
Offline Access (Server Side)
Let's break it down to two parts:
If your need to update within 60 minutes of user's last activity
You can use firebase along with gapi to achieve that. You'll be provided with the AccessToken that can be sent back to server to add to calendar.
More info on implementation
If you need to update after 60 minutes of user's last activity
Firebase & gapi's most method handle the AuthorizationCode flow internally. They even further refresh the AccessToken after 60 minutes. This is beneficial for most developers as they won't have a headache of managing all the tokens.
This method but, hides RefreshToken & AuthorizationCode from the developer. That is even if your server has the access token, it won't be able to refresh it and it would be deemed useless.
To achieve complete offline access, in the initial request to get AuthorizationCode you will need to send a HTTP GET parameter access_type to offline
GAPI provides you with grantOfflineAccess() method which returns the AuthorizationCode that can be later used on your server to fetch access token & refresh token.
Note: If you are storing AuthorizationCode in your database, make sure it is secure. The limitation in Firebase are set due to security reason. It is more secure to not talk with AuthorizationCode generally.
More links
https://developers.google.com/identity/protocols/oauth2/web-server
https://developers.google.com/identity/sign-in/web/reference
https://developers.google.com/identity/sign-in/web/server-side-flow
https://developers.google.com/identity/sign-in/web/backend-auth
Retrieve Google Access Token after authenticated using Firebase Authentication

Get the refresh token from the PersistedGrantStore key property in identityserver4

I have already implemented my own IPersistedGrantStore called PostgresPersistedGrantStore that stores grant in my postgresql database and it works really great.
Now i want to move really forward and i want to get the refresh token from the key that is stored in my postgresql table. But from what i read it is not a proper refreshtoken but a hash to protect the refreshtoken. Is there a way to decrypt, read the refresh token from the key property, using maybe a fuction from the identityserver api?
I am trying to implement my own impersonation workflow, so it would be easy to login as any user using the latest refresh token that exists persisted in my db
A long time has passed since the question had been asked, but I think I'm sharing a relevant information.
Here is the method which is implemented at IdentityServer4.Stores.DefaultGrantStore<T> and actually creates the key for the refresh token.
protected virtual string GetHashedKey(string value)
{
return (value + KeySeparator + GrantType).Sha256();
}
Where
value is the actual value of the refresh token,
KeySeparator is a
constant string field defined at the same class, the
value is ":",
GrantType is 'refresh_token' in this particular case.
This method is being used while creating/validating the refresh token.
I think this information clearly states that there is no way to get the refresh token value by using the key.
Reference:
https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/src/Stores/Default/DefaultGrantStore.cs#L77
Identity Server 4 has a build-in endpoint for this - <server_url>/Connect/Token.
You need to send a POST request to this endpoint, with x-www-form-urlencoded body type, which contains:
refresh_token: current refresh_token
client_id: the client that you are refreshing the token for
client_secret: the secret of the client
grant_type: refresh_token
It will give you back a "refreshed" access_token along with a new refresh_token.
The initial refresh_token you should have received once you have logged in. Have in mind - once refresh_token is used (to get a new access_token) it gets invalidated. This is why you are receiving a new with every request to the endpoint.
Here is some more info about the refresh_token itself.
And here - about the token endpoint.

How to sync firebase users across several locations? (extension + website)

I'm working on chrome extension (provides main functionality) and the complementary website (mostly profile and billing related functionality) both backed with firebase backend.
I'm wondering if it's possible to implement the below scenario:
user logs in with the extension using firebase authentication (with firebaseUI lib)
I store a token that I can use to reauthenticate that user (is there such a token?)
when user opens the website, I login that user automatically with the token.
While both the extension and the website has their login/signup forms I'm wondering if it's possible to login user in the extension and to somehow automatically login that same user on the website so they don't have to enter their credentials twice?
So far I was hoping that I could use something like below:
firebase.auth().currentUser.getIdToken(true).then(function(idToken) {
console.log("idToken = ", idToken)
})
And then to use that idToken like this, since if I understand correctly, it's an AWT:
firebase.auth().signInWithCustomToken(idToken).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log("signInWithCustomToken: error = ", error)
})
But it gives the following error:
code: "auth/invalid-custom-token"
message: "The custom token format is incorrect. Please check the documentation."
I can parse the token on https://jwt.io/ which shows all the user information but in the end it says "invalid signature"
So I guess this token can be only used to check authentication (like admin.auth().verifyIdToken(idToken)) but not to login user. Am I right?
I know I can create a custom token, but is there any straightforward way to workaround that and to login user from one place only using firebase funstionality? (of course without storing username/password)
You can't sign in with a Firebase ID token. What you can do is the following:
Keep the user session in the chrome extension and run all authenticated requests from there. Use postMessage (with origin verification) to talk with extension from app anytime a request is to be sent. With this you don't have to worry about session synchronization and no Firebase tokens are stored or passed to the web app or every web app that can access the extension.
Add a postMessage API to get an ID token from the extension after verifying the origin of the request. You can then send the request from the web app with the ID token. (less secure than 1 but easier to implement and session is stored in one place).
Create an HTTP endpoint that takes an ID token and returns a custom token. This would verifyIdToken and then create a corresponding custom token for that user using createCustomToken provided by Admin SDK. You then postMessage that from chrome extension to the web page after verifying origin and signInWithCustomToken with that custom token in that web app. This is the least secure as you are providing an endpoint to exchange a short lived ID token with an indefinite session. You will also deal with synchronization issues (user signs out from chrome extension, you have to sign out from websites, etc).

Authenticate with signInWithCredential()

I'm trying to connect to the second Firebase app and authenticate with signInWithCredential(), but I don't know how to get valid idToken for the second app:
connect(accessToken: string, config: FirebaseAppConfig) {
let one: firebase.app.App = this.angularFireTwo.database["fbApp"];
one.auth().currentUser.getToken()
.then(idToken => firebase.auth.GoogleAuthProvider.credential(idToken, accessToken))
.then(credential => {
let two = firebase.initializeApp(config, `[${config.apiKey}]`);
return two.auth().signInWithCredential(credential);
})
.catch(console.warn)
.then(console.info);
}
I'm getting and error from https://www.googleapis.com/identitytoolkit/v3/:
Invalid id_token in IdP response
If I use signInWithPopup() I can authenticate and connection is working:
two.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider())
Anyone knows what should I do to get valid idToken?
UPDATE:
I've been trying to figure out authentication process and, as far I understand it , it's something like this:
from config: FirebaseAppConfig firebase reads apiKey and authDomain
it contacts the servers and gets Web Client ID for enabled Google provider 123.apps.googleusercontent.com
with this Web Client ID and authDomain it contacts www.googleapis.com, which returns idToken
this idToken is then used to identify the app that's asking user for permission to access user's profile, etc.
when user agrees, callback returns user details + credential used for this authentication, which contains idToken of the web app and accessToken of the user
Now, if I use signInWithPopup() steps 2-3-4 are done in the background (popup window). I just need a way to generate idToken for the step 4, so I can use it to generate credential firebase.auth.GoogleAuthProvider.credential(idToken, accessToken) and sign-in using signInWithCredential().
I have access to everything I need to sign-in to the second app - it's; apiKey, authDomain, Web Client id 456.apps.googleusercontent.com, and user's unique accessToken.
But still can't figure out how to do it. I tried white-listing apps' one and two Web client IDs in their auth configurations, hoping that will allow them to accept each others idTokens, but that didn't work...
When you call:
firebase.auth.GoogleAuthProvider.credential(idToken, accessToken))
The first parameter should be a Google OAuth Id token. You are using the Firebase Id token and that is why you getting the error. Besides, if you are already logged in, why are you logging in again with signInWithCredential?
If you need to sign in with a Google credential you need either a Google OAuth Id token or a Google OAuth access token.
To duplicate Firebase OAuth sign-in state from one app to another, you get the credential from signInWithPopup result and use it to signInWithCredential in the second instance.
two.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider())
.then(function(result) {
return one.auth().signInWithCredential(result.credential);
});

Resources