Getting access_token from identityserver returns null - asp.net-core-mvc

I have 3 projects, an MVC .net core website, an API service and an IdentityServer (IdentityServer4). Logging in to the website works like a charm. It's when I want to get data from the API the problems are starting. Getting the access_token from the httpcontext fails and returns null. The strange thing is that when I debug this, I seem to be getting an access_token back from the httpcontext. Only when I run the website from the live webserver it causes problems.
I configured my OpenIdConnect as follows:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultAuthenticateScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://idserverurl";
options.RequireHttpsMetadata = true;
options.ClientId = "clientid";
options.ClientSecret = "xxxxxxxx";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
};
});
To set the bearertoken for the API calls I use de following code:
var client = new HttpClient();
var accessToken = await HttpContext.GetTokenAsync("access_token");
client.SetBearerToken(accessToken);
When I run this code in debug I get the access_token from the HttpContext. When I run this from the live servers, I get null.
Does anybody have any idea what I'm doing wrong? Could it be a configuration mistake at server level?

I think I solved it myself.
It had to do with the line
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
By removing this line and altering the lines of code where I get my claims (they have a different key now), I am getting the access_token.
FYI I published the project to my own IIS. That way I could attach Visual Studio to the dotnet process and debug it.
Why it worked locally and not online I still don't know.

The above code looks OK - ultimately the tokens should come back from the Authorization Server after user login, then be persisted between requests in the authentication cookie.
Are you able to run Fiddler on your live web server and capture a trace of traffic, including that between your Web App and the Authorization Server? That's the ideal way to troubleshoot - and see if the expected tokens / cookies are present in the HTTPS requests.
In case you're not aware, in .Net Core 2.0 you can construct your web app's HttpClient with an HttpClientHandler that sets proxy details like this:
public class ProxyHttpHandler : HttpClientHandler
{
public ProxyHttpHandler()
{
this.Proxy = new WebProxy("http://127.0.0.1:8888");
}
}
In older versions of MS OpenIdConnect libraries I've seen problems related to load balancing, where not all servers could decrypt the authentication cookie. So if your live servers are load balanced the details in this older link may be relevant.

Related

Problem in refreshing the login token from client to identity server

I have setup an Identity Server 4 on a .Net 6 web app. My web UI is another web app that is configured as the client of the Identity Sever. User is correctly refered to the login page when request accessing to a secured page/api and login is done OK. The solution also has other microservices that are also configured to use IS as oidc. The problem is after a while if I do not refresh the page, authentication fails when calling webapis. When I check the request, before the main call to the webapi controller, a request to the IS is made but is refused with CORS exception. I have configured the IS web app to accept CORS like this:
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
and then:
app.UseCors("CorsPolicy");
What I am missing?
The mentioned settings did not solve the problem
The problem is might be caused from the expiration of the cookie or token (I'm not sure). But you should also add
builder.Services.AddSingleton<ICorsPolicyService>((container) => {
var logger = container.GetRequiredService<ILogger<DefaultCorsPolicyService>>();
return new DefaultCorsPolicyService(logger)
{
AllowedOrigins = { /*webUIOriginHere!NotUrl!*/ }
};
});
to the program.cs of Identity Server webapp and the problem should be solved.
Also adding AnyOrigin is dangerous. try doing something like this:
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.WithOrigins( webUIOrigin )
.AllowAnyMethod()
.AllowAnyHeader());
});

AAD Token from SPA app works to call Nodejs API but not .Net WebAPI

I have a SPA application that uses MSAL to acquire a token from AAD. Because MSAL works with v2 endpoint and because v2 endpoint does not currently support issuing tokens for custom API's, I'm passing the ID Token to my api and essentially treating my api as the same application. (While this has a smell to it, it does actually work -- at least with Nodejs API).
SPA app
let idToken = Msal.Storage('localStorage').getItem(Msal.Constants.idTokenKey);
this.http.configure(config => {
config.withBaseUrl("http://localhost:3001/")
config.withDefaults({headers: {'Authorization': 'Bearer ' + idToken}})
});
//Call API
this.http.fetch("account")
...
Node.js API
//Using express/passport
var BearerStrategy = require("passport-azure-ad").BearerStrategy;
var options = {
identityMetadata: "https://login.microsoftonline.com/tenantid/.well-known/openid-configuration/",
clientID: "xxxxxxx-xxxx-xxxxxxx-xxxxx",
passReqtoCallback: false,
validateIssuer: true,
issuer: "http://login.microsoftonline.com/{tenantid}/v2.0"
};
app.get("/account",passport.authenticate('oauth-bearer',{session: false}),...
The above all works. Once a user authenticates with the SPA, the token is passed and the call to the Node API works.
I'm now trying to replace the Nodejs API with a .Net WebAPI. I have the following:
Startup.cs
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
//Same ID as used for ClientID in Nodejs
ValidAudience = "xxxxxx-xxxxx-xxxxx-xxxxx",
ValidIssuer = "https://login.microsoftonline.com/{tenantid}/v2.0",
ValidateIssuer = true,
AuthenticationType = "WebApi" //Tried both with and without this
},
Tenant = "{tenantid}" //have tried both id and name
}
)
AccountController.cs
[Authorize]
[Route("account")]
public IHttpActionResult AccountProfile(){
//Get Account information
....
return Ok(profile);
}
However, when I point the SPA app to call the .Net api, I always get Authorization has been denied for this request .
Is there something I'm missing?
Edit
Incidentally, I've inspected the token that is being used.
The value I'm using for clientID (Nodejs) and ValidAudience (.Net) exactly match the aud claim in the token. The issuer (Nodejs) and ValidIssuer (.Net) exactly match the iss claim in the token. Lastly, the anywhere in my code where I've inserted {tenantid}, the actual value there matches exactly the tid claim in the token.
We had a similar issue when switching from ADAL to MSAL and got it to work by using a similar approach like this Github project. Specifically take a look at these files:
https://github.com/oktadeveloper/okta-oauth-aspnet-codeflow/blob/master/Api/Startup.cs
https://github.com/oktadeveloper/okta-oauth-aspnet-codeflow/blob/master/Api/OpenIdConnectCachingSecurityTokenProvider.cs
Update: Our Startup.cs:
var provider = new OpenIdConnectCachingSecurityTokenProvider(
string.Format(bc2Instace, tenant, policyId));
var jwt = new JwtFormat(clientId, provider);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = jwt,
});

Asp.net Core Client with Identityserver 3 missing claims

Given:
A JavaScript app that authenticates with oidc over identityerver v3
A Asp.net Core Webapi that authenticates with the given bearer token to identiyserver
The Javascript clients makes calls with the access token itself to the api.
Problem:
The Authentication suceeds but the restored principal is missing some custom claim like "username" and "familyName". I can see that the oidc client in the javascript client has these informations
some claims like "idp" is set in both Javascript and Api Client. But bot are not handled explicitly.
The main difference is that idp is part of the access_token which the username is not.
the configuration of the api is :
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var authority = config["identity:authority:url"];
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
LegacyAudienceValidation = true,
Authority = authority,
RequireHttpsMetadata = false,
EnableCaching = false,
ApiName = "MyApp.Read",
});
Any hint what i'm missing ? ( I assume it is some kind of profile read in the api?)
Workaround
I extend the configuration with JwtBearerEvents and make a manual read with userclient when the token was authenticated like this
JwtBearerEvents = new JwtBearerEvents
{
OnTokenValidated = async context =>
{
string header = context.Request.Headers["Authorization"];
string accessToken = header.Substring(6);
var result = await userInfoClient.GetAsync(accessToken);
but is this the intended way? Are extended / profile claims meant to be returned only by manually querying them?

google ExchangeCodeForTokenAsync invalid_grant in webapi

i have implemented GoogleAuthorizationCodeFlow scenario from google api client dotnet and tutorial to get token from what my client sent to server as a code. but when i call flow.ExchangeCodeForTokenAsync , I get the following error :
{"Error:\"invalid_grant\", Description:\"\", Uri:\"\""}
I read google authorization invalid_grant and gusclass oauth 2 using google dotnet api client libraries but they didn't help me and. I think it must be very simple but I don't know why it doesn't work.
For client side , I have used Satellizer and this is my server Codes:
public bool PostExchangeAccessToken(GoogleClientAccessCode code)
{
string[] SCOPES = { "email" };
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets()
{
ClientSecret = "******",
ClientId = "********.apps.googleusercontent.com"
},
Scopes = SCOPES
});
try
{
TokenResponse token;
token = flow.ExchangeCodeForTokenAsync("*****#gmail.com", Newtonsoft.Json.JsonConvert.SerializeObject(code), "https://localhost:44301/",
CancellationToken.None).Result;
}
catch (Exception ex)
{
throw ex;
}
return true;
}
what is the problem?
On Github I found that I must use the Token from the client and use
GoogleAuthorizationCodeFlow.Initializer()
to create my UserCredential object.
You can check your google developer console settings.(Authorized redirect URIs)
Credentials => OAuth 2.0 client IDs => Your Application Settings => Authorized redirect URIs
You must add url. ("https://localhost:44301/")
My code :
flow.ExchangeCodeForTokenAsync("me", authCode, redirectUri, CancellationToken.None).Result;
Authorized redirect URIs
For use with requests from a web server. This is the path in your application that users are redirected to after they have authenticated with Google. The path will be appended with the authorization code for access. Must have a protocol. Cannot contain URL fragments or relative paths. Cannot be a public IP address.

invalid_grant trying to get oAuth token from google

I keep getting an invalid_grant error on trying to get an oAuth token from Google to connect to their contacts api. All the information is correct and I have tripple checked this so kind of stumped.
Does anyone know what may be causing this issue? I have tried setting up a different client id for it but I get the same result, I have tried connecting many different ways including trying the force authentication, but still the same result.
Although this is an old question, it seems like many still encounter it - we spent days on end tracking this down ourselves.
In the OAuth2 spec, "invalid_grant" is sort of a catch-all for all errors related to invalid/expired/revoked tokens (auth grant or refresh token).
For us, the problem was two-fold:
User has actively revoked access to our app
Makes sense, but get this: 12 hours after revocation, Google stops sending the error message in their response:
“error_description” : “Token has been revoked.”
It's rather misleading because you'll assume that the error message is there at all times which is not the case. You can check whether your app still has access at the apps permission page.
User has reset/recovered their Google password
In December 2015, Google changed their default behaviour so that password resets for non-Google Apps users would automatically revoke all the user's apps refresh tokens. On revocation, the error message follows the same rule as the case before, so you'll only get the "error_description" in the first 12 hours. There doesn't seem to be any way of knowing whether the user manually revoked access (intentful) or it happened because of a password reset (side-effect).
Apart from those, there's a myriad of other potential causes that could trigger the error:
Server clock/time is out of sync
Not authorized for offline access
Throttled by Google
Using expired refresh tokens
User has been inactive for 6 months
Use service worker email instead of client ID
Too many access tokens in short time
Client SDK might be outdated
Incorrect/incomplete refresh token
I've written a short article summarizing each item with some debugging guidance to help find the culprit.
I ran into this same problem despite specifying the "offline" access_type in my request as per bonkydog's answer. Long story short I found that the solution described here worked for me:
https://groups.google.com/forum/#!topic/google-analytics-data-export-api/4uNaJtquxCs
In essence, when you add an OAuth2 Client in your Google API's console Google will give you a "Client ID" and an "Email address" (assuming you select "webapp" as your client type). And despite Google's misleading naming conventions, they expect you to send the "Email address" as the value of the client_id parameter when you access their OAuth2 API's.
This applies when calling both of these URL's:
https://accounts.google.com/o/oauth2/auth
https://accounts.google.com/o/oauth2/token
Note that the call to the first URL will succeed if you call it with your "Client ID" instead of your "Email address". However using the code returned from that request will not work when attempting to get a bearer token from the second URL. Instead you will get an 'Error 400' and an "invalid_grant" message.
I ran into this problem when I didn't explicitly request "offline" access when sending the user to the OAuth "Do you want to give this app permission to touch your stuff?" page.
Make sure you specify access_type=offline in your request.
Details here: https://developers.google.com/accounts/docs/OAuth2WebServer#offline
(Also: I think Google added this restriction in late 2011. If you have old tokens from before then, you'll need to send your users to the permission page to authorize offline use.)
If you're testing this out in postman / insomnia and are just trying to get it working, hint: the server auth code (code parameter) is only good once. Meaning if you stuff up any of the other parameters in the request and get back a 400, you'll need to use a new server auth code or you'll just get another 400.
I encountered the same problem. For me, I fixed this by using Email Address (the string that ends with ...#developer.gserviceaccount.com) instead of Client ID for client_id parameter value. The naming set by Google is confusing here.
We tried so many things, and in the end the issue was that the client had turned
"Less Secure App Access" off in their Google Account settings.
To turn this on:
Go to https://myaccount.google.com/ and manage account
Go to the Security tab
Turn Less secure app access on
I hope this saves someone some time!
My issue was that I used this URL:
https://accounts.google.com/o/oauth2/token
When I should have used this URL:
https://www.googleapis.com/oauth2/v4/token
This was testing a service account which wanted offline access to the Storage engine.
This is a silly answer, but the problem for me was that I failed to realize I already had been issued an active oAuth token for my google user which I failed to store. The solution in this case is to go to the api console and reset the client secret.
There are numerous other answers on SO to this effect for example
Reset Client Secret OAuth2 - Do clients need to re-grant access?
Using a Android clientId (no client_secret) I was getting the following error response:
{
"error": "invalid_grant",
"error_description": "Missing code verifier."
}
I cannot find any documentation for the field 'code_verifier' but I discovered if you set it to equal values in both the authorization and token requests it will remove this error. I'm not sure what the intended value should be or if it should be secure. It has some minimum length (16? characters) but I found setting to null also works.
I am using AppAuth for the authorization request in my Android client which has a setCodeVerifier() function.
AuthorizationRequest authRequest = new AuthorizationRequest.Builder(
serviceConfiguration,
provider.getClientId(),
ResponseTypeValues.CODE,
provider.getRedirectUri()
)
.setScope(provider.getScope())
.setCodeVerifier(null)
.build();
Here is an example token request in node:
request.post(
'https://www.googleapis.com/oauth2/v4/token',
{ form: {
'code': '4/xxxxxxxxxxxxxxxxxxxx',
'code_verifier': null,
'client_id': 'xxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
'client_secret': null,
'redirect_uri': 'com.domain.app:/oauth2redirect',
'grant_type': 'authorization_code'
} },
function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log('Success!');
} else {
console.log(response.statusCode + ' ' + error);
}
console.log(body);
}
);
I tested and this works with both https://www.googleapis.com/oauth2/v4/token and https://accounts.google.com/o/oauth2/token.
If you are using GoogleAuthorizationCodeTokenRequest instead:
final GoogleAuthorizationCodeTokenRequest req = new GoogleAuthorizationCodeTokenRequest(
TRANSPORT,
JSON_FACTORY,
getClientId(),
getClientSecret(),
code,
redirectUrl
);
req.set("code_verifier", null);
GoogleTokenResponse response = req.execute();
There are two major reasons for invalid_grant error which you have to take care prior to the POST request for Refresh Token and Access Token.
Request header must contain "content-type: application/x-www-form-urlencoded"
Your request payload should be url encoded Form Data, don't send as json object.
RFC 6749 OAuth 2.0 defined invalid_grant as:
The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
I found another good article, here you will find many other reasons for this error.
https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35
Solved by removing all Authorized redirect URIs in Google console for the project.
I use server side flow when you use 'postmessage' as redirect URI
I had the same error message 'invalid_grant' and it was because the
authResult['code']
send from client side javascript was not received correctly on the server.
Try to output it back from the server to see if it is correct and not an empty string.
Try change your url for requst to
https://www.googleapis.com/oauth2/v4/token
You might have to remove a stale/invalid OAuth response.
Credit: node.js google oauth2 sample stopped working invalid_grant
Note: An OAuth response will also become invalid if the password used in the initial authorization has been changed.
If in a bash environment, you can use the following to remove the stale response:
rm /Users/<username>/.credentials/<authorization.json>
The code you obtain in the URL after user consent has a very short expiry. Please obtain the code again and attempt to get access token within seconds (you have to hurry) and it should work. I can't find out the expiry period of code but it's literally very short.
In my case it was a callback URL that was different from the original request.
So, callback URL should be the same for auth request and code exchange.
if you are using scribe library, for example to set up the offline mode, like bonkydog suggested.
here is the code:
OAuthService service = new ServiceBuilder().provider(Google2Api.class).apiKey(clientId).apiSecret(apiSecret)
.callback(callbackUrl).scope(SCOPE).offline(true)
.build();
https://github.com/codolutions/scribe-java/
I my case I just didn't read the documentation properly because I was trying to do const { tokens } = await oauth2Client.getToken(accessToken);
every time to get an authorized client instance but for the subsequent requests you only need to include the refresh_token you store after the first user auth.
oauth2Client.setCredentials({
refresh_token: `STORED_REFRESH_TOKEN`
});
This can happen if your redirect_url is not the same as the one you have when creating the token on Google Gloud. So make sure it's correct
in this site
console.developers.google.com
this console board select your project input the oath url.
the oauth callback url will redirect when the oauth success
After considering and trying all of the other ways here, here's how I solved the issue in nodejs with the googleapis module in conjunction with the request module, which I used to fetch the tokens instead of the provided getToken() method:
const request = require('request');
//SETUP GOOGLE AUTH
var google = require('googleapis');
const oAuthConfigs = rootRequire('config/oAuthConfig')
const googleOAuthConfigs = oAuthConfigs.google
//for google OAuth: https://github.com/google/google-api-nodejs-client
var OAuth2 = google.auth.OAuth2;
var googleOAuth2Client = new OAuth2(
process.env.GOOGLE_OAUTH_CLIENT_ID || googleOAuthConfigs.clientId,
process.env.GOOGLE_OAUTH_CLIENT_SECRET || googleOAuthConfigs.clientSecret,
process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl);
/* generate a url that asks permissions for Google+ and Google Calendar scopes
https://developers.google.com/identity/protocols/googlescopes#monitoringv3*/
var googleOAuth2ClientScopes = [
'https://www.googleapis.com/auth/plus.me',
'https://www.googleapis.com/auth/userinfo.email'
];
var googleOAuth2ClientRedirectURL = process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl;
var googleOAuth2ClientAuthUrl = googleOAuth2Client.generateAuthUrl({
access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
scope: googleOAuth2ClientScopes // If you only need one scope you can pass it as string
});
//AFTER SETUP, THE FOLLOWING IS FOR OBTAINING TOKENS FROM THE AUTHCODE
const ci = process.env.GOOGLE_OAUTH_CLIENT_ID || googleOAuthConfigs.clientId
const cs = process.env.GOOGLE_OAUTH_CLIENT_SECRET || googleOAuthConfigs.clientSecret
const ru = process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl
var oauth2Client = new OAuth2(ci, cs, ru);
var hostUrl = "https://www.googleapis.com";
hostUrl += '/oauth2/v4/token?code=' + authCode + '&client_id=' + ci + '&client_secret=' + cs + '&redirect_uri=' + ru + '&grant_type=authorization_code',
request.post({url: hostUrl}, function optionalCallback(err, httpResponse, data) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if(!err) {
//SUCCESS! We got the tokens
const tokens = JSON.parse(data)
oauth2Client.setCredentials(tokens);
//AUTHENTICATED PROCEED AS DESIRED.
googlePlus.people.get({ userId: 'me', auth: oauth2Client }, function(err, response) {
// handle err and response
if(!err) {
res.status(200).json(response);
} else {
console.error("/google/exchange 1", err.message);
handleError(res, err.message, "Failed to retrieve google person");
}
});
} else {
console.log("/google/exchange 2", err.message);
handleError(res, err.message, "Failed to get access tokens", err.code);
}
});
I simply use request to make the api request via HTTP as described here:
https://developers.google.com/identity/protocols/OAuth2WebServer#offline
POST /oauth2/v4/token HTTP/1.1
Host: www.googleapis.com
Content-Type: application/x-www-form-urlencoded
code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=8819981768.apps.googleusercontent.com&
client_secret={client_secret}&
redirect_uri=https://oauth2.example.com/code&
grant_type=authorization_code
For future folks... I read many articles and blogs but had luck with solution below...
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
clientId,
clientSecret,
authCode,
"") //Redirect Url
.setScopes(scopes)
.setGrantType("authorization_code")
.execute();
This blog depicts different cases in which "invalid_grant" error comes.
Enjoy!!!
for me I had to make sure that the redirect_uri is an exact match to the one in the developer console Authorised redirect URIs, that fixed it for me, I was able to debug and know what exactly was the issue after switching from
https://accounts.google.com/o/oauth2/token to https://www.googleapis.com/oauth2/v4/token
I got a proper error:
{"error": "redirect_uri_mismatch", "error_description": "Bad Request"}
I had this problem after enabling a new service API on the Google console and trying to use the previously made credentials.
To fix the problem, I had to go back to the credential page, clicking on the credential name, and clicking "Save" again. After that, I could authenticate just fine.
In my case, the issue was in my code. Mistakenly I've tried to initiate client 2 times with the same tokens. If none of the answers above helped make sure you do not generate 2 instances of the client.
My code before the fix:
def gc_service
oauth_client = Signet::OAuth2::Client.new(client_options)
oauth_client.code = params[:code]
response = oauth_client.fetch_access_token!
session[:authorization] = response
oauth_client.update!(session[:authorization])
gc_service = Google::Apis::CalendarV3::CalendarService.new
gc_service.authorization = oauth_client
gc_service
end
primary_calendar_id = gc_service.list_calendar_lists.items.select(&:primary).first.id
gc_service.insert_acl(primary_calendar_id, acl_rule_object, send_notifications: false)
as soon as I change it to (use only one instance):
#gc_service = gc_service
primary_calendar_id = #gc_service.list_calendar_lists.items.select(&:primary).first.id
#gc_service.insert_acl(primary_calendar_id, acl_rule_object, send_notifications: false)
it fixed my issues with grant type.
For me the issues was I had multiple clients in my project and I am pretty sure this is perfectly alright, but I deleted all the client for that project and created a new one and all started working for me ( Got this idea fro WP_SMTP plugin help support forum) I am not able to find out that link for reference
If you are sanitizing user input (For example, $_GET["code"] in php) Make sure you don't accidentally replace something in the code.
The regex I am using is now /[^A-Za-z0-9\/-]/
Look at this https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988
First you need an access_token:
$code = $_GET['code'];
$clientid = "xxxxxxx.apps.googleusercontent.com";
$clientsecret = "xxxxxxxxxxxxxxxxxxxxx";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "client_id=".urlencode($clientid)."&client_secret=".urlencode($clientsecret)."&code=".urlencode($code)."&grant_type=authorization_code&redirect_uri=". urlencode("https://yourdomain.com"));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close ($ch);
$server_output = json_decode($server_output);
$access_token = $server_output->access_token;
$refresh_token = $server_output->refresh_token;
$expires_in = $server_output->expires_in;
Safe the Access Token and the Refresh Token and the expire_in, in a Database. The Access Token expires after $expires_in seconds. Than you need to grab a new Access Token (and safe it in the Database) with the following Request:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "client_id=".urlencode($clientid)."&client_secret=".urlencode($clientsecret)."&refresh_token=".urlencode($refresh_token)."&grant_type=refresh_token");
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close ($ch);
$server_output = json_decode($server_output);
$access_token = $server_output->access_token;
$expires_in = $server_output->expires_in;
Bear in Mind to add the redirect_uri Domain to your Domains in your Google Console: https://console.cloud.google.com/apis/credentials in the Tab "OAuth 2.0-Client-IDs". There you find also your Client-ID and Client-Secret.
There is a undocumented timeout between when you first redirect the user to the google authentication page (and get back a code), and when you take the returned code and post it to the token url. It works fine for me with the actual google supplied client_id as opposed to an "undocumented email address". I just needed to start the process again.
For me, this was caused by subsequent getToken calls with the same code.
Namely, in NestJS my callback endpoint was decorated with #UseGuards(AuthGuard('google')) and I tried to call getToken in the callback.

Resources