I am developing a small web application wherein I am integrating with Google+ Domain API's.
I am using OAuth2 authentication.I have generated client_id and client_secret for my web application
from Google API console.
Using Google+ Domain API's, I am able to generate the access token.
Generating authorization URL
List<String> SCOPE = Arrays.asList(
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/plus.circles.read",
"https://www.googleapis.com/auth/plus.stream.write");
//Sets up Authorization COde flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(new NetHttpTransport(),
new JacksonFactory(),
"xxx","yyy",SCOPE).setApprovalPrompt("force").setAccessType("offline").build();
//Builds the uthorization URL
String url = flow.newAuthorizationUrl().setRedirectUri(<REDIRECT_URI>).build();
out.println("<div id='googleplus'></div><a href='"+url+"' rel='external' ><img src='googleplus.jpg'></a> <b>Configure</b></div>");
session.setAttribute("CodeFlow", flow);
After authorization
GoogleAuthorizationCodeFlow flow=(GoogleAuthorizationCodeFlow)session. getAttribute("CodeFlow");
//After authorization,fetches the value of code parameter
String authorizationCode=request.getParameter("code");
//Exchanges the authorization code to get the access token
GoogleTokenResponse tokenResponse=flow.newTokenRequest(authorizationCode).
setRedirectUri(<REDIRECT_URI>).execute();
GoogleCredential credential = new GoogleCredential.Builder().setTransport(new NetHttpTransport()).setJsonFactory(new JacksonFactory())
.setClientSecrets("xxx", "yyy")
.addRefreshListener(new CredentialRefreshListener(){
public void onTokenErrorResponse(Credential credential, TokenErrorResponse errorResponse) throws java.io.IOException{
System.out.println("Credential was not refreshed successfully. "
+ "Redirect to error page or login screen.");
}
#Override
public void onTokenResponse(Credential credential, TokenResponse tokenResponse)
throws IOException {
System.out.println("Credential was refreshed successfully.");
System.out.println("Refresh Token :"+tokenResponse.getRefreshToken());
}
}).build();
//Set authorized credentials.
credential.setFromTokenResponse(tokenResponse);
credential.refreshToken();
Fetching circle information:
PlusDomains plusDomains = new PlusDomains.Builder(
new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName("DomainWebApp")
.setRootUrl("https://www.googleapis.com/")
.build();
PlusDomains.Circles.List listCircles=plusDomains.circles().list("me");
listCircles.setMaxResults(5L);
System.out.println("Circle URL:"+listCircles.buildHttpRequestUrl());
CircleFeed circleFeed=listCircles.execute();
System.out.println("Circle feed:"+circleFeed);
List<Circle> circles =circleFeed.getItems();
while (circles != null) {
for (Circle circle : circles) {
out.println("Circle name : "+circle.getDisplayName()+" Circle id : "+circle.getId());
}
// When the next page token is null, there are no additional pages of
// results. If this is the case, break.
if (circleFeed.getNextPageToken() != null) {
// Prepare the next page of results
listCircles.setPageToken(circleFeed.getNextPageToken());
// Execute and process the next page request
circleFeed = listCircles.execute();
circles = circleFeed.getItems();
} else {
circles = null;
}
}
I get the below error:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Forbidden",
"reason" : "forbidden"
} ],
"message" : "Forbidden"
}
com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
Note: I have also enabled Google+ Domain API in my Google API Console.
REDIRECT_URI ="http://localhost:8080/DomainWebApp/oauth2callback" since it's a web app.
Any Suggestions?
The first thing to check is that the application is making the call on behalf of a Google Apps user. If the user account is, for example, an #gmail account, the request will not be allowed. The Google+ Domains API only works for Google Apps domain users, and only for requests within their domain.
Related
Was just wondering if it's possible for Single Sender Validation to be completed without having to login to Sendgrid as part of the process (e.g. click-through without login). For context, sometimes the people who "own" a mail address that we want to use for sending don't have access to Sendgrid, and we'd like them to be able to validate it. I think they can't by design, but wanted to confirm.
Looking at the API documentation, it looks like you can use the token sent in the validation email to complete the validation process, but I'm not sure if there's any way to effectively make use of that to redirect the user back to a process we control. There's another post that mentions the same kind of challenge, but thought I'd ask again as there wasn't anything definitive.
Is there a simple way to have the user who receives the validation redirect back to something other than sendgrid directly?
Thanks in advance!
The only alternative to logging in is to use the SendGrid API.
First, you either request the verification using the UI, or you use the Create Verified Sender Request API to start the verification for the single sender.
Then, the verification email will be sent to the specified email address which contains the verification URL. Usually, this URL will redirect you the the actual URL containing the verification token, as mentioned in the SO post you linked.
Once you get the verification token, you can use the Verify Sender Request API, passing in the verification token, to verify the single sender.
Note: All these APIs require a SendGrid API key.
So technically, you could have an application that prompts your user for their email address to verify, then uses the SendGrid API to start the verification which sends the verification email, then ask the user to go to their email inbox and copy in their verification link, then let the user paste in the URL from which you can extract the verification token, and use the API to verify. While the user didn't have to log in, it still requires some manual work.
However, the inputting of the email address and the checking the email inbox can also be done programmatically, so this process can be 100% automated, although it takes quite a bit of programming.
Here's a C# sample:
using System.Net;
using Microsoft.AspNetCore.WebUtilities;
using SendGrid;
namespace VerifySender;
internal class Program
{
public static async Task Main(string[] args)
{
var configuration = new ConfigurationBuilder()
.AddUserSecrets<Program>(optional: true)
.Build();
var apiKey = configuration["SendGrid:ApiKey"]
?? Environment.GetEnvironmentVariable("SENDGRID_API_KEY")
?? throw new Exception("SendGrid API Key not configured.");
var client = new SendGridClient(apiKey);
// replace this JSON with your own values
const string data = """
{
"nickname": "Orders",
"from_email": "orders#example.com",
"from_name": "Example Orders",
"reply_to": "orders#example.com",
"reply_to_name": "Example Orders",
"address": "1234 Fake St",
"address2": "PO Box 1234",
"state": "CA",
"city": "San Francisco",
"country": "USA",
"zip": "94105"
}
""";
var response = await client.RequestAsync(
method: SendGridClient.Method.POST,
urlPath: "verified_senders",
requestBody: data
);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Failed to request sender verification. HTTP status code {response.StatusCode}.");
Console.WriteLine(await response.Body.ReadAsStringAsync());
Console.WriteLine(response.Headers.ToString());
}
Console.WriteLine("Enter verification URL:");
var verificationUrl = Console.ReadLine();
var token = await GetVerificationTokenFromUrl(verificationUrl);
response = await client.RequestAsync(
method: SendGridClient.Method.GET,
urlPath: $"verified_senders/verify/{token}"
);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Failed to verify sender. HTTP status code {response.StatusCode}.");
Console.WriteLine(await response.Body.ReadAsStringAsync());
Console.WriteLine(response.Headers.ToString());
}
}
private static async Task<string> GetVerificationTokenFromUrl(string url)
{
/*
* url could be three different types:
* 1. Click Tracking Link which responds with HTTP Found and Location header to url type 2.
* 2. URL containing the verification token:
* https://app.sendgrid.com/settings/sender_auth/senders/verify?token=[VERIFICATION_TOKEN]&etc=etc
* 3. URL prompting the user to login, but contains url 2. in the redirect_to parameter:
* https://app.sendgrid.com/login?redirect_to=[URL_TYPE_2_ENCODED]
*/
const string verificationBaseUrl = "https://app.sendgrid.com/settings/sender_auth/senders/verify";
const string loginBaseUrl = "https://app.sendgrid.com/login";
if (url.StartsWith(verificationBaseUrl))
{
var uri = new Uri(url, UriKind.Absolute);
var parameters = QueryHelpers.ParseQuery(uri.Query);
if (parameters.ContainsKey("token"))
{
return parameters["token"].ToString();
}
throw new Exception("Did not find token in verification URL.");
}
if (url.StartsWith(loginBaseUrl))
{
var uri = new Uri(url, UriKind.Absolute);
var parameters = QueryHelpers.ParseQuery(uri.Query);
if (parameters.ContainsKey("redirect_to"))
{
url = $"https://app.sendgrid.com{parameters["redirect_to"]}";
return await GetVerificationTokenFromUrl(url);
}
throw new Exception("Did not find token in verification URL.");
}
var clientHandler = new HttpClientHandler();
clientHandler.AllowAutoRedirect = false;
using var httpClient = new HttpClient(clientHandler);
var response = await httpClient.GetAsync(url);
if (response.StatusCode == HttpStatusCode.Found)
{
var uri = response.Headers.Location;
return await GetVerificationTokenFromUrl(uri.ToString());
}
throw new Exception("Did not find token in verification URL.");
}
}
Take note of the comments inside of GetVerificationTokenFromUrl. Since I don't trust the user to copy the URL from the email without clicking on it, I added support for three types of URL:
Click Tracking Link which responds with HTTP Found and Location header to url type 2.
URL containing the verification token: https://app.sendgrid.com/settings/sender_auth/senders/verify?token=[VERIFICATION_TOKEN]&etc=etc
URL prompting the user to login, but contains url 2. in the redirect_to parameter: https://app.sendgrid.com/login?redirect_to=[URL_TYPE_2_ENCODED]
Here's the full source code on GitHub.
I have managed to establish a login screen using Javascript google API as described in https://developers.google.com/identity/sign-in/web/server-side-flow. using the function: auth2.grantOfflineAccess()
The API returns the following authorization code:
{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}
How do I exchange the authorization code to access token and a refresh token in an ASP.NET server?
Basically you need to post it back to google to get an access token and a refresh token.
POST https://accounts.google.com/o/oauth2/token
code={CODE}&client_id={ClientId}&client_secret={ClientSecret}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code
Here is a server-side code for for google sign in:
UserCredential credential;
string[] scopes = new string[] {
YouTubeService.Scope.YoutubeUpload
};
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "<CLIENT-ID>",
ClientSecret = "<CLIENT-SECRET>"
},
Scopes = scopes,
DataStore = new FileDataStore("Store")
});
TokenResponse token = flow.ExchangeCodeForTokenAsync(videoUploadOptions.userId, videoUploadOptions.authorizationCode, videoUploadOptions.baseUrl, CancellationToken.None).Result;
credential = new UserCredential(flow, Environment.UserName, token);
The auth2 google response will be stored at the token object. the variable transferred from the client are: videoUploadOptions.userId, videoUploadOptions.authorizationCode and videoUploadOptions.baseUrl.
All the credentials will be stored at the credential object.
I am using a spring boot as my backend application.
I have stored our client's access_token, refresh_token, and access_id in my postgresql database.
Here is my code trying to get the new access token if token expired.
public void refreshGoogleIdToken(GoogleAuthEntity googleAuthEntity) {
LOGGER.debug("GoogleAuthService.refreshGoogleIdToken()");
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(googleAuthClientId, googleAuthClientSecret)
.build();
credential.setAccessToken(googleAuthEntity.getAccessToken());
credential.setRefreshToken(googleAuthEntity.getRefreshToken());
try {
if (credential.refreshToken()) {
Long newExpireTime = credential.getExpirationTimeMilliseconds();
String newAccessToken = credential.getAccessToken();
String newRefreshToken = credential.getRefreshToken();
LOGGER.debug("NewAccessToken: " + newAccessToken);
LOGGER.debug("NewRefreshToken: " + newRefreshToken);
LOGGER.debug("NewExpireTime: " + newExpireTime);
}
} catch (IOException e) {
LOGGER.debug("GoogleAuthService.refreshGoogleIdToken() - IOException");
e.printStackTrace();
}
}
Google return 400 error, and the description is: 400 Bad Request
{
"error" : "invalid_grant",
"error_description" : "Bad Request"
}
What mistake that I have make?
Thanks
I have been using OAuth2 with spring framework and have only encountered this error "Invalid grant" in case if refresh token is invalid, expired, revoked or does not match the redirection uri used in the authorization request, or is issued to another client
For your situation I think you should delete the stored refresh token / rectify it and debug your code again. This may be due to wrong token info stored in your PostgreSQL database while testing.
How should client know that access token has expired, so that he makes a request with refresh token for another access token?
If answer is that server API will return 401, then how can API know that access token has expired?
I'm using IdentityServer4.
Your api should reject any call if the containing bearer token has already been expired. For a webapi app, IdentityServerAuthenticationOptions will do the work.
But your caller Web application is responsible for keeping your access_token alive. For example, if your web application is an ASP.Net core application, you may use AspNetCore.Authentication.Cookies to authenticate any request. In that case, you can find the information about the token expiring info through OnValidatePrincipal event.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
//ExpireTimeSpan = TimeSpan.FromSeconds(100),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
if (timeElapsed > timeRemaining)
{
//Get the new token Refresh the token
}
}
}
}
I have added a full implementation about how to get a new access token using refresh token in another StackOverflow answer
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.