I wrote an MVC app using Google Oauth2 as instructed here:
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web_applications
I have an issue with access token expiration. When access token expires, I get the exception when calling Google API: "The access token has expired but we can't refresh it"
The initial authentication is two iterations mechanism:
first iteration AuthorizeAsync returns result with empty Credential, and populated RedirectUri:
So, the authorization url created is this:
https://accounts.google.com/o/oauth2/auth?access_type=offline&response_type=code&client_id=MYCLIENTID&redirect_uri=http:%2F%2Flocalhost%2FHomepage%2FAuthCallback%2FIndexAsync&scope=https:%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar https:%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.readonly&state=http:%2F%2Flocalhost%2FHomepage%2F95419199
Note that access_type=offline is present. So I should get the refresh token back as well (doesn't happen).
second iteration - AuthorizeAsync returns result with populated Credential and empty RedirectUri:
Question1 - is RefreshToken supposed to be null at this moment?
The result is remembered, since it's defined as static.
Next request that comes in - the Calendar action that requires result.Credential to call Google Calendar API:
Question2 - if access token expires by that moment (for testing I just set ExpiresInSeconds = 0), I call RefreshTokenAsync method, but it always returns false! Why? What am I missing here?
And what would be the right way to handle when RefreshTokenAsync returns false?
Current RedirectResult(result.RedirectUri) command will fail since result.RedirectUri is null.
Oh, I finally got it :)
For those who interested - refresh token is only issued once, when you get that Consent screen, where you have to click Yes.
So, in order to get refresh token, go to your account setting, Account Permissions: https://security.google.com/settings/security/permissions
and revoke access for the project you configured in Google Developers Console: https://console.developers.google.com/project
Now, put a breakpoint on the next line after you call AuthorizeAsync, restart your application in Debug mode, get that consent screen asking for permissions, click Accept.
The app will return to VS and will stop on your break point.
Now, record somewhere the result.Credential.Token.RefreshToken value, it's an encrypted string.
I placed my in web.config appsetting for simplicity.
Now, I just assign that value back to result.Credential.Token.RefreshToken = refreshToken;
and every time, when access token expires, it will automatically refresh it.
Like here when I call GmailService request.Execute(...) passing the credential object that contains the token, the token will be refreshed.
Related
I'm using the AppleAuth npm package in my server to submit token requests to Apple's servers during the sign in with Apple process. (This is based off the sample server code provided with the sign_in_with_apple package in pub.dev) I have no issues submitting the authorization code to get my first access and refresh tokens.
However, when trying to test how my app would get a new refresh token, when I submit a POST request to https://appleid.apple.com/auth/token with the grant_type set to refresh_token the response I get is different than from having the grant_type set to authorization_code.
I looked at the source code of the package, and for its AppleAuth(myConfig).refreshToken(myRefreshToken) (<- pseudo code lol) the only difference in the POST payload is:
{
grant_type: 'refresh_token', // instead of grant_type: 'authorization_code'
refresh_token: refreshToken, // instead of code: authorizationCode
... // other params
}
While the initial request with authorization code returns both an access token and a refresh token, for some reason the refresh token request is only returning the access token. I really don't think it's the package causing the error, nor can I see how my code would be the source either, since the above code is the only difference.
I tried passing the access token that it returns in a new refresh token request in order to test that I can get new refresh tokens whenever necessary in the future, but it returns a 400 status error.
Am I missing something here? Is there a reason the refresh token request returns no new refresh token? Or am I missing something entirely about how the process/flow is supposed to work? I am trying to do this for the "check the refresh token once daily to confirm the user is still in good standing with Apple's servers" part of the process.
I've really been stuck on what to do at this point. I can save the identity_token.sub field in my database to check whether my user is signed in, but of course I want to make sure my user's apple ID is still valid with apple, and that they haven't revoked access. Could it be that because I tried to get a new refresh_token too soon Apple only returned the access_token?
Oh also, the app itself is a Flutter app and I am testing all of this on my iPhone 11 so it's not an Android/Web flow.
Whether you get a new 'rolling / rotating' refresh token in a refresh token grant response is generally vendor specific:
You may get a new refresh token occasionally but not always
The primary purpose of this message is to get a new access token, not a new refresh token
In terms of token handling, the client should update its tokens similarly to the saveTokens method in this sample of mine.
I've not used Sign In with Apple but I suspect proceeding as follows would be the simplest solution:
Keep access tokens short lived: no more than 60 minutes
This forces a frequent token refresh, which by default is very quick
If the user's Apple Id is revoked I would expect this to return an invalid_grant response
I'm being redirected to the Google Consent for the first time and asked if I allow this application to manage Gmail account and stuff. And when I'm redirected back, refresh token is null.
+token:"ya29.Gls1BTQIRIuCW2dnzIpQlciOZNpidhjfsoidjfsomethingsm"
+refreshToken: null
+expiresIn: 3600
I know Refresh Token comes null on subsequent requests. But this was the first request I made.
Is there something I need to do on the console side.
Solved it. I forgot to set access_type to offline
$client->setAccessType('offline');
In my case I was using Larave/Socialite
Socialite::driver('google')->with(['access_type'=>'offline'])->redirect();
Ref: https://developers.google.com/identity/protocols/OAuth2WebServer
Upon a successful login to the Microsoft Graph API service, 2 ADTokenCacheStoreItems are created. One of them contains the accessToken (refreshToken is nil) and the other one contains the refreshToken (accessToken is nil).
Having 2 cached items is fine but it is causing the refresh token logic to fail. The [attemptToUseCacheItem:] method uses the ADTokenCacheStoreItem that contains the accessToken. If the accessToken is expired, it checks the refresh token. In this item, the refresh token is nil so the whole process fails with the 'Attempting to use an item without refresh token' error. The second item (with the refresh token) is actually never accessed.
As a result, the access token is never refreshed. What seems to be the problem here?
ADALiOS is working well, I can't reproduce the issue. The issue is resolved, the details are in the thread: https://github.com/AzureAD/azure-activedirectory-library-for-objc/issues/598#issuecomment-211636229
Copying over Ryan's response here just in case the link goes down:
The access token is stored separately from the Multi Resource Refresh
token in the cache for ADALiOS. If you call acquireTokenSilent it will
successfully refresh an AT from the MRRT.
The line "The second item (with the refresh token) is actually never
accessed." is false. Look at -attemptToUseCacheItem: in
ADAuthenticationContext (1.2.x) or ADAuthenticationRequest (2.1) you
can see where ADAL looks for the MRRT and tries it. Look for the
"//Try multi-resource refresh token if not currently trying it"
comment if you're having a hard time finding it.
There is a considerable amount of unit testing around this very
scenario and you try try it manually in the test app as well. The only
way you see the behavior you claim is if you're not using the proper
acquireToken entry points.
I'm using this site https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx to do Authorization Code Grant Flow with the goal of reading my office 365 calendars using this type of flow. The problem is that when I request for an oauth token the response is not updating the "scope" variable. I'm requesting the oauth token using this POST call "https://login.microsoftonline.com/common/oauth2/token" and passing in the body my grant_type, redirect_uri, client_id, client_secret, code, resource. The response is 200OK but for scope it only reads -> "'scope': 'Contacts.Read'" when it should also have Calendars.Read as well. In manage.windowsazure.com for the app that has the same client_id I'm passing in has the read calendars checked as well as the read contacts checked. When I first got my authorization code by typing this into the browser "login.microsoftonline.com/common/oauth2/authorize" I only had "Contacts Read" checked. But now every time I type that into my browser it skips the page where I accept my app to look at my calendars and contacts page. When I login with someone else's computer and get the auth code and request the token it updates their scope to both contacts and calendar and works fine. For me I'm getting a new Auth code in the url but it skips the page where I could accept my app to look at my contacts AND calendars. I'm getting a new authorization each time. I tried clearing my browsing data but It still wouldn't work.
You need the user to logon again so they can consent to the new scope. Try adding prompt=consent to your logon URL.
This works much nicer in the v2 app model, which does dynamic scopes.
We have Azure ACS configured to issue JWT that is valid for 15 minutes. Once the user is logged-in to the web application (MVC), the user will use the token to access resources on another server (WebAPI). The WebAPI server would then validate that token.
So, is there any way to renew the JWT somehow without interrupting user's work on the web app? We don't want to popup a window and ask the user to sign in again.
Thanks!
If you are using Active Directory Authentication Library (ADAL) for .NET, then it includes a token cache. Per this blog post:
One last thing I’d highlight at this point is that every time you get a token from the authority ADAL adds it to a local cache. Every subsequent call to AcquireToken will examine the cache, and if a suitable token is present it will be returned right away. If a suitable token cannot be found, but there is enough information for obtaining a new one without repeating the entire authentication process (as it is the case with OAuth2 refresh tokens) ADAL will do so automatically. The cache is fully queryable and can be disabled or substituted with your own implementation, but if you don’t need either you don’t even need to know it’s there: AccessToken will use it transparently.
ADAL.NET is available on Nuget here: https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/
If you aren't using ADAL.NET, provide more info, such as:
What library you are using
What is ACS on top of, AD FS or Azure Active Directory
We use ACS + ADAL and there seems to be no clever way to refresh the token. Even if the ExpiresOn Time on the Token inside the Cache is due the AcquireToken always returns the stale cached token. We cache the token ourself, so this code is only invoked when the ExpiresOn is due.
I ended up with this dirty hack:
var authContext = new AuthenticationContext(ServiceInfo.AcsUrl);
if (authContext.TokenCacheStore.Count > 0)
{
authContext.TokenCacheStore.Remove(authContext.TokenCacheStore.First());
}
result = authContext.AcquireToken(acsRealm, allProviders.First());