JWT refresh tokens not required for re-authentication - oauth-2.0

Till now i am using short-lived token and refresh token for API auth. I am using refresh token only for getting user-id to query database to check latest permissions and active/blocked status of user. Now i am thinking that why should not i extract this user-id from short-liven token itself. The following function is used to decode JWT, in this expiration is verified after signature verification. So if i get 'expiration' error it means then token signature is good and token is un-tempered. Now i can extract middle(yyy out of xxx.yyy.zzz) base64 encoded data from expired JWT to get user-id. So i don't seeing worth using refresh token. Further longer time access can also be defined in token itself with just custom timestamp so that i have both time limits in one token for example 5 minutes and 90 days. What are your thoughts?
public static function decode($jwt, $key, array $allowed_algs = array())
{
$timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;
if (empty($key)) {
throw new InvalidArgumentException('Key may not be empty');
}
$tks = explode('.', $jwt);
if (count($tks) != 3) {
throw new UnexpectedValueException('Wrong number of segments');
}
list($headb64, $bodyb64, $cryptob64) = $tks;
if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
throw new UnexpectedValueException('Invalid header encoding');
}
if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
throw new UnexpectedValueException('Invalid claims encoding');
}
if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
throw new UnexpectedValueException('Invalid signature encoding');
}
if (empty($header->alg)) {
throw new UnexpectedValueException('Empty algorithm');
}
if (empty(static::$supported_algs[$header->alg])) {
throw new UnexpectedValueException('Algorithm not supported');
}
if (!in_array($header->alg, $allowed_algs)) {
throw new UnexpectedValueException('Algorithm not allowed');
}
if (is_array($key) || $key instanceof \ArrayAccess) {
if (isset($header->kid)) {
if (!isset($key[$header->kid])) {
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
}
$key = $key[$header->kid];
} else {
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
}
}
// Check the signature
if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
throw new SignatureInvalidException('Signature verification failed');
}
// Check the nbf if it is defined. This is the time that the
// token can actually be used. If it's not yet that time, abort.
if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
);
}
// Check that this token has been created before 'now'. This prevents
// using tokens that have been created for later use (and haven't
// correctly used the nbf claim).
if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
);
}
// Check if this token has expired.
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
throw new ExpiredException('Expired token');
}
return $payload;
}

Don't do this. Your plan breaks the common practice. It is also against the OAuth standard. New developer will think it is a bug. Your company might hire security auditor and that person will test access with expired token. This will be reported as something you have to fix.

Related

MSAL.NET OBO refresh token problems

I am trying to implement an OBO flow through to the graph API on a middle-tier API (.NET 5.0) using MSAL.NET. I'm running into two frustrating problems, and I can't find anyone having similar problems, so I think I'm misunderstanding something!
Problem 1: Whenever I call MSAL's GetAccountAsync, it always returns null when there should be an account loaded.
Problem 2: Whenever I call MSAL's AcquireTokenSilent, I always get the error "No refresh token found in the cache." even though I got one.
Here's what I have:
Once the web app authenticates, it passes through the token to a graph auth endpoint on the API:
var authenticationResult = await ClaimHelper.ClientApplication.AcquireTokenByAuthorizationCode(GraphHelpers.BasicGraphScopes, context.Code).ExecuteAsync();
var apiUserSession = await CouncilWiseAPIHelper.APIClient.Graph.AuthoriseUserAsync(authenticationResult.AccessToken);
which seems to work fine, and passes through a JWT to the API auth endpoint. The API implements an MSAL Confidential Client application and uses the SetBeforeAccess/SetAfterAccess token cache methods to save the cache to a database.
_msalClient = ConfidentialClientApplicationBuilder.Create(_graphConfig.ClientId)
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
.WithClientSecret(_graphConfig.ClientSecret)
.Build();
SetSerialiser(serialiser);
public void SetSerialiser(MSALTokenCacheSerialiser serialiser)
{
_msalClient.UserTokenCache.SetBeforeAccessAsync(serialiser.BeforeAccessCallbackAsync);
_msalClient.UserTokenCache.SetAfterAccessAsync(serialiser.AfterAccessCallbackAsync);
}
And the serialiser methods look like this:
public async Task BeforeAccessCallbackAsync(TokenCacheNotificationArgs notification)
{
GraphUserTokenCache tokenCache = await _graphUserTokenCacheRepository.GetByUserIdentifier(notification.SuggestedCacheKey);
if (tokenCache == null)
{
tokenCache = await _graphUserTokenCacheRepository.Get(notification.SuggestedCacheKey);
}
if (tokenCache != null)
{
notification.TokenCache.DeserializeMsalV3(tokenCache.Value);
}
}
public async Task AfterAccessCallbackAsync(TokenCacheNotificationArgs notification)
{
if (!notification.HasTokens)
{
// Delete from the cache
await _graphUserTokenCacheRepository.Delete(notification.SuggestedCacheKey);
}
if (!notification.HasStateChanged)
{
return;
}
GraphUserTokenCache tokenCache;
if (notification.SuggestedCacheKey == notification.Account.HomeAccountId.Identifier)
{
tokenCache = await _graphUserTokenCacheRepository.GetByUserIdentifier(notification.SuggestedCacheKey);
}
else
{
tokenCache = await _graphUserTokenCacheRepository.Get(notification.SuggestedCacheKey);
}
if (tokenCache == null)
{
var cache = notification.TokenCache.SerializeMsalV3();
tokenCache = new GraphUserTokenCache
{
Id = notification.SuggestedCacheKey,
AccountIdentifier = notification.Account.HomeAccountId.ToString(),
Value = cache
};
await _graphUserTokenCacheRepository.Add(tokenCache);
}
else
{
await _graphUserTokenCacheRepository.Update(tokenCache.Id, notification.TokenCache.SerializeMsalV3());
}
}
I can see the token BeforeAccess and AfterAccess methods being called, and I can see the caches being created in the database (encryption has been removed while I'm trying to track down this issue). If I inspect the serialised token cache being saved, it NEVER has a refresh token populated, but if I inspect the requests with fiddler I can see a refresh token was indeed provided.
Finally, here is the code for retrieving the access token which is called whenever a graph request is made:
public async Task<AuthenticationResult> GetAccessToken(string accountId, string jwtBearerToken)
{
try
{
IAccount account = null;
if (accountId.IsNotNullOrEmpty())
{
account = await _msalClient.GetAccountAsync(accountId);
}
var scope = _graphConfig.Scopes.Split(' ');
if (account == null)
{
var result = await _msalClient.AcquireTokenOnBehalfOf(scope,
new UserAssertion(jwtBearerToken))
.ExecuteAsync();
return result;
}
else
{
var result = await _msalClient.AcquireTokenSilent(scope, account)
.ExecuteAsync();
return result;
}
}
catch (MsalClientException ex)
{
ex.CwApiLog();
return null;
}
catch(Exception ex)
{
ex.CwApiLog();
return null;
}
}
When it's called with the jwtBearerToken, it will successfully call AcquireTokenOnBehalfOf() and the token is cached and a result returned, but when I come back to retrieve the account via GetAccountAsync() it always returns null even though I can see the token cache was loaded in BeforeAccessCallbackAsync().
Also, even if I call AcquireTokenSilent() immediately after acquiring the obo token with the account it just returned, I will get an exception saying there is no refresh token in the cache.
I am totally lost on what I'm doing wrong here, any help would be greatly appreciated.
I recently ran into the same problem while running a long runing OBO flow, MSAL has recently implemented an interface ILongRunningWebApi for these use cases you can go and see this new documentation
Here is an extract:
One OBO scenario is when a web API runs long running processes on
behalf of the user (for example, OneDrive which creates albums for
you). Starting with MSAL.NET 4.38.0, this can be implemented as such:
Before you start a long running process, call:
string sessionKey = // custom key or null
var authResult = await ((ILongRunningWebApi)confidentialClientApp)
.InitiateLongRunningProcessInWebApi(
scopes,
userToken,
ref sessionKey)
.ExecuteAsync();
userToken is a user token used to call this web API. sessionKey will
be used as a key when caching and retrieving the OBO token. If set to
null, MSAL will set it to the assertion hash of the passed-in user
token. It can also be set by the developer to something that
identifies a specific user session, like the optional sid claim from
the user token (for more information, see Provide optional claims to
your app). If the cache already contains a valid OBO token with this
sessionKey, InitiateLongRunningProcessInWebApi will return it.
Otherwise, the user token will be used to acquire a new OBO token from
AAD, which will then be cached and returned.
In the long-running process, whenever OBO token is needed, call:
var authResult = await ((ILongRunningWebApi)confidentialClientApp)
.AcquireTokenInLongRunningProcess(
scopes,
sessionKey)
.ExecuteAsync();
Pass the sessionKey which is associated with the current user's
session and will be used to retrieve the related OBO token. If the
token is expired, MSAL will use the cached refresh token to acquire a
new OBO access token from AAD and cache it. If no token is found with
this sessionKey, MSAL will throw a MsalClientException. Make sure to
call InitiateLongRunningProcessInWebApi first.
Hope this helps :)

Invalid Access Token/Missing Claims when logged into IdentityServer4

I have a standard .NET Core 2.1 (MVC and API) and Identity Server 4 project setup.
I am using reference tokens instead of jwt tokens.
The scenario is as follows:
Browse to my application
Redirected to Identity Server
Enter valid valid credentials
Redirected back to application with all claims (roles) and correct access to the application and API
Wait an undetermined amount of time (I think it's an hour, I don't have the exact timing)
Browse to my application
Redirected to Identity Server
I'm still logged into the IDP so I'm redirected immediately back to my
application
At this point the logged in .NET user is missing claims (roles) and no longer has access to the API
The same result happens if I delete all application cookies
It seems obvious to me that the access token has expired. How do I handle this scenario? I'm still logged into the IDP and the middleware automatically logged me into my application, however, with an expired (?) access token and missing claims.
Does this have anything to do with the use of reference tokens?
I'm digging through a huge mess of threads and articles, any guidance and/or solution to this scenario?
EDIT: It appears my access token is valid. I have narrowed my issue down to the missing user profile data. Specifically, the role claim.
When I clear both my application and IDP cookies, everything works fine. However, after "x" (1 hour?) time period, when I attempt to refresh or access the application I am redirected to the IDP then right back to the application.
At that point I have a valid and authenticated user, however, I am missing all my role claims.
How can I configure the AddOpenIdConnect Middleware to fetch the missing claims in this scenario?
I suppose in the OnUserInformationReceived event I can check for the missing "role" claim, if missing then call the UserInfoEndpoint...that seems like a very odd workflow. Especially since on a "fresh" login the "role" claim comes back fine. (Note: I do see the role claim missing from the context in the error scenario).
Here is my client application configuration:
services.AddAuthentication(authOpts =>
{
authOpts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
authOpts.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opts => { })
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, openIdOpts =>
{
openIdOpts.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
openIdOpts.Authority = settings.IDP.Authority;
openIdOpts.ClientId = settings.IDP.ClientId;
openIdOpts.ClientSecret = settings.IDP.ClientSecret;
openIdOpts.ResponseType = settings.IDP.ResponseType;
openIdOpts.GetClaimsFromUserInfoEndpoint = true;
openIdOpts.RequireHttpsMetadata = false;
openIdOpts.SaveTokens = true;
openIdOpts.ResponseMode = "form_post";
openIdOpts.Scope.Clear();
settings.IDP.Scope.ForEach(s => openIdOpts.Scope.Add(s));
// https://leastprivilege.com/2017/11/15/missing-claims-in-the-asp-net-core-2-openid-connect-handler/
// https://github.com/aspnet/Security/issues/1449
// https://github.com/IdentityServer/IdentityServer4/issues/1786
// Add Claim Mappings
openIdOpts.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username"); /* SID alias */
openIdOpts.ClaimActions.MapJsonKey("role", "role", "role");
openIdOpts.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = settings.IDP.ClientId,
ValidIssuer = settings.IDP.Authority,
NameClaimType = "name",
RoleClaimType = "role"
};
openIdOpts.Events = new OpenIdConnectEvents
{
OnUserInformationReceived = context =>
{
Log.Info("Recieved user info from IDP.");
// check for missing roles? they are here on a fresh login but missing
// after x amount of time (1 hour?)
return Task.CompletedTask;
},
OnRedirectToIdentityProvider = context =>
{
Log.Info("Redirecting to identity provider.");
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Log.Debug("OnTokenValidated");
// this addressed the scenario where the Identity Server validates a user however that user does not
// exist in the currently configured source system.
// Can happen if there is a configuration mismatch between the local SID system and the IDP Client
var validUser = false;
int uid = 0;
var identity = context.Principal?.Identity as ClaimsIdentity;
if (identity != null)
{
var sub = identity.Claims.FirstOrDefault(c => c.Type == "sub");
Log.Debug($" Validating sub '{sub.Value}'");
if (sub != null && !string.IsNullOrWhiteSpace(sub.Value))
{
if (Int32.TryParse(sub.Value, out uid))
{
using (var configSvc = ApiServiceHelper.GetAdminService(settings))
{
try
{
var usr = configSvc.EaiUser.GetByID(uid);
if (usr != null && usr.ID.GetValueOrDefault(0) > 0)
validUser = true;
}
catch { }
}
}
}
Log.Debug($" Validated sub '{sub.Value}'");
}
if (!validUser)
{
// uhhh, does this work? Logout?
// TODO: test!
Log.Warn($"Unable to validate user is SID for ({uid}). Redirecting to '/Home/Logout'");
context.Response.Redirect("/Home/Logout?msg=User not validated in source system");
context.HandleResponse();
}
return Task.CompletedTask;
},
OnTicketReceived = context =>
{
// TODO: Is this necessary?
// added the below code because I thought my application access_token was expired
// however it turns out I'm actually misisng the role claims when I come back to the
// application from the IDP after about an hour
if (context.Properties != null &&
context.Properties.Items != null)
{
DateTime expiresAt = System.DateTime.MinValue;
foreach (var p in context.Properties.Items)
{
if (p.Key == ".Token.expires_at")
{
DateTime.TryParse(p.Value, null, DateTimeStyles.AdjustToUniversal, out expiresAt);
break;
}
}
if (expiresAt != DateTime.MinValue &&
expiresAt != DateTime.MaxValue)
{
// I did this to synch the .NET cookie timeout with the IDP access token timeout?
// This somewhat concerns me becuase I thought that part should be done auto-magically already
// I mean, refresh token?
context.Properties.IsPersistent = true;
context.Properties.ExpiresUtc = expiresAt;
}
}
return Task.CompletedTask;
}
};
});
I'm sorry folks, looks like I found the source of my issue.
Total fail on my side :(.
I had a bug in the ProfileService in my Identity Server implementation that was causing the roles to not be returned in all cases
humph, thanks!

AcquireTokenSilentAsync fails to authenticate user

I am trying to get the silent token request to initialize the ConfidentialClientApp object as in the 'Microsoft Graph SDK ASPNET Connect' project and outlined in Add sign-in with Microsoft to an ASP.NET web app
With my code mirroring the samples above, I expect that my call will return a successful result with an access to.
var result = await cca.AcquireTokenSilentAsync(graphScopes, cca.Users.First());
return result.AccessToken;
However, I get an error where it says the user needs to be authenticated. I am not sure what I am missing from the examples that make this work in them.
You can only acquire the token silently if there is already a cached token for that user that includes the scopes you're requesting (it can have more, but it needs to have at least what you've asked for).
This is why AcquireTokenSilentAsync should always be wrapped in a Try/Catch block. If it fails to find a matching token, you need to launch an interactive flow. Here is an example from the MSAL Wiki:
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenSilentAsync(scopes, app.Users.FirstOrDefault());
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync.
// This indicates you need to call AcquireTokenAsync to acquire a token
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
result = await app.AcquireTokenAsync(scopes);
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
return;
}
if (result != null)
{
string accessToken = result.AccessToken;
// Use the token
}

How do I register a JWT token expired event with AngularFire? (AngularFire Custom Authentication)

How do I register a JWT token expired event with AngularFire?
Right now I have my Rails server sending a JWT to the AngularJS client, which is set to expire in 60 seconds. I'm connecting to Firebase on my client successfully. After 60 seconds, it expires and I get a "FIREBASE WARNING: auth() was canceled: Auth token is expired." message in my browser console.
It is not clear to me what to watch for this event. My goal would be to call my server for a new token when my current one expires.
client code snippet: here is me initializing a firebase obj and watching a url
var ref = new Firebase(URL);
// Create a callback which logs the current auth state
var authDataCallback = function (authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider);
} else {
console.log("User is logged out", authData);
}
}
// Register the callback to be fired every time auth state changes
ref.onAuth(authDataCallback);
var authHandler = function(error, authData) {
if(error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
var firebaseBal = $firebase(ref).$asObject();
// update balance when val changes
firebaseWatch = firebaseBal.$watch(function(newVal, oldVal) {
$scope.balance = firebaseBal.$value;
});
}
}
// Authenticate users with a custom Firebase token
ref.authWithCustomToken(Auth.firebaseToken(), authHandler);
server code snippet (I use firebase-token-generator to generate my JWT token):
generator = Firebase::FirebaseTokenGenerator.new(secret);
payload = { :uid => "123" }
# expired in a minute, for testing
expires = Time.now.to_i + (60*1)
options = {:expires => expires}
token = generator.create_token(payload, options)
// this is then sent to the client
Problem to be solved soon, see GitHub issue - https://github.com/firebase/angularfire/issues/514
This GitHub issue exists for the exact reason to help you accomplish
what you want to accomplish :) The short answer is that we don't
really support this that well right now.
For the time being, you will have to use $extendFactory() and put your
logic in the $$error()method. Here are the slim docs on that. The
$$error() method will fire once authentication fails due to token
expiry or security rules.
You should see improvements in this area in the coming weeks and this
is tagged for the 1.0.0 release.
var ref = new Firebase(URL);
var authHandler = function(error, authData) {
if(error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
// create a factory to pass into $firebase
var timeoutErrorFactory = $FirebaseObject.$extendFactory({
$$error: function(data) {
// TODO: not sure if super needs to be applied
// var err = $FirebaseObject.prototype.$$error.apply(this, arguments);
console.log('firebase timeout error cleanup', data.code);
// TODO: call server for a new token
}
});
// create an instance which uses the customized factory
firebaseBal = $firebase(ref, {objectFactory: timeoutErrorFactory}).$asObject();
// update balance when val changes
firebaseWatch = firebaseBal.$watch(function(newVal, oldVal) {
$scope.balance = firebaseBal.$value;
});
}
}
// Authenticate users with a custom Firebase token
ref.authWithCustomToken(Auth.firebaseToken(), authHandler);

How to get a refresh_token with an auto-approved response?

I have the following code:
if (isset($_REQUEST['logout']))
{
unset($_SESSION['upload_token ']);
}
if (isset($_GET['code']))
{
$client->authenticate($_GET['code']);
$_SESSION['upload_token'] = $client->getAccessToken();
$redirect = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
header('Location: ' . filter_var($redirect, FILTER_SANITIZE_URL));
}
if (isset($_SESSION['upload_token']) && $_SESSION['upload_token'])
{
$client->setAccessToken($_SESSION['upload_token']);
if ($client->isAccessTokenExpired())
{
echo "The access token is expired.<br>"; // Debug
$client->refreshToken(json_decode($_SESSION['upload_token']));
unset($_SESSION['upload_token']);
}
}
else
{
$authUrl = $client->createAuthUrl();
}
and am receiving the following error:
Uncaught exception 'Google_Auth_Exception' with message 'The OAuth 2.0 access token has expired, and a refresh token is not available. Refresh tokens are not returned for responses that were auto-approved.'
I am assuming I am getting this error because the response was auto-approved.
What should be changed?
UPDATE: I've tried adding this to my code:
$client->setAccessType("online");
$client->setApprovalPrompt("auto");
Based on this question. I still am receiving the same error of missing refresh token.
UPDATE: After kroikie's update, my code looks like the following:
$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope("https://www.googleapis.com/auth/drive");
$client->setAccessType("offline");
$client->setApprovalPrompt("auto");
$client->setApplicationName("Appraisal App");
$service = new Google_Service_Drive($client);
if (isset($_REQUEST['logout']))
{
unset($_SESSION['upload_token ']);
}
if (isset($_GET['code']))
{
$resp = $client->authenticate($_GET['code']);
$_SESSION['upload_token'] = $client->getAccessToken();
$array = get_object_vars(json_decode($resp));
// store and use $refreshToken to get new access tokens
$refreshToken = $array['refreshToken'];
$redirect = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
header('Location: ' . filter_var($redirect, FILTER_SANITIZE_URL));
}
if (isset($_SESSION['upload_token']) && $_SESSION['upload_token'])
{
$client->setAccessToken($_SESSION['upload_token']);
if ($client->isAccessTokenExpired())
{
echo "The access token is expired. Let Raph know that you saw this.<br>";
$client->refreshToken($refreshToken);
unset($_SESSION['upload_token']);
}
}
else
{
$authUrl = $client->createAuthUrl();
}
Unfortunately, I still receive the same fatal error when the refresh token is needed.
When the access type is offline, an access token and a refresh token are returned when the user first grants data access. The access token can be used to access the user's data, and the refresh token is stored and used to get a new access token when the initial access token has expired.
So try using offline access type
$client->setAccessType('offline');
and use the refresh token to refresh the client's access token
// $refreshToken is retrieved from the response of the
// user's initial granting access
$client->refreshToken($refreshToken)
UPDATE:
To get the refresh token use something like:
if (isset($_GET['code'])) {
$resp = $client->authenticate($_GET['code']);
$_SESSION['token'] = $client->getAccessToken();
$array = get_object_vars(json_decode($resp));
// store and use $refreshToken to get new access tokens
$refreshToken = $array['refreshToken'];
}
Assuming you want to use the offline access type, you have two choices: either you force the approval prompt (so you never get an auto-approved response):
$client->setAccessType("offline");
$client->setApprovalPrompt("force");
Or you ignore the refresh token and simply get a new auto-approved authorization when your current token is expired. I never tested this but since no user action is required for approval, I assume it works.
You can read more about this here (Change #3: Server-side auto-approval): http://googlecode.blogspot.ca/2011/10/upcoming-changes-to-oauth-20-endpoint.html
Sometimes it's seems that the error is in the code, but in my case was not.
I had the same problem and tried all kind of solution, but the error was in the console.developers.google.com configuration.
I thought was not necessary to confirm the domain in the Credentials configuration.
So when i did it, so the code stopped giving the error.

Resources