Unable to have concurrent log-ins with Google auth - java

I have a web app where users have to authenticate using Google sign-in. I do this because I need to grab their email address. When they fill out the fields on the page, all that data is stored in a google sheet alongside their email address (for auditing purposes incase something is askew with the data). Unfortunately what's happening is that if user A signs in, and does some work and at the same time user B logs in, when user A submits data, they will be submitting user B's email address (as does user B). In short, the latest person to log in, that email address is used. There is no database and I'm not storing any cookies. When they refresh the page, they have to re-authenticate. I am using Angular 7 and Java. Here is the code that I used:
ngOnInit() {
gapi.load('auth2', () => {
this.auth2 = gapi.auth2.init({
client_id: 'CLIENT_ID_HERE',
// Scopes to request in addition to 'profile' and 'email'
scope: 'https://www.googleapis.com/auth/spreadsheets'
});
});
}
signInWithGoogle(): void {
this.auth2.grantOfflineAccess().then((authResult) => {
this.authCode = authResult['code'];
this.fetchData();
});
}
authCode is bound to the child component so it can be passed as query param to the java code for google auth.
this.seriesService.submitSeriesData(matchList, this.authToken).subscribe(res => {.....);
The google auth java code is so:
private static final String APPLICATION_NAME = "Google Sheets API Java";
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final List<String> SCOPES = Collections.singletonList(SheetsScopes.SPREADSHEETS);
private static final String CLIENT_SECRET_DIR = "/client_secret.json";
private static GoogleTokenResponse tokenResponse = null;
public static String getEmailAddress() throws IOException {
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String email = payload.getEmail();
return email;
}
public static Sheets getSheetsService1(String token, String redirectUri) throws IOException, GeneralSecurityException {
// Exchange auth code for access token
InputStream in = GoogleAuthUtil.class.getResourceAsStream(CLIENT_SECRET_DIR);
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
token,
redirectUri)
.execute();
String accessToken = tokenResponse.getAccessToken();
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Sheets service = new Sheets.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
.setApplicationName("MY APP HERE")
.build();
return service;
}
And the endpoint:
#RequestMapping(value="series/data", method = RequestMethod.POST, consumes="application/json")
public boolean submitSeriesMatchData(#RequestBody(required=true) SubmitStatsDto request) throws IOException, GeneralSecurityException, Exception {
if (service == null) {
service = GoogleAuthUtil.getSheetsService1(request.getToken(), this.redirectUri);
}
......
}
1) User clicks on the google sign in button
2) They select email and auth with google
3) I receive an auth code back and store it in ng.
4) Every REST call is passed said token to auth with google, and every endpoint calls getSheetsService1 which authenticates w/ token. (multiple endpoints, I only showed one above)
5) I get email from that tokenResponse.
Any ideas? This site will not have a database/users/local logins. Thank you.

Related

Gmail API, get new access token after revoke

I'm developing application that provides access to gmail mailbox. I created new project and logged in with few email accounts. When I revoked access token through this endpoint: https://oauth2.googleapis.com/revoke, the number of accounts attached to my project didn't change.
When i try to select account again, google redirects me to uri I specified, but code within url is corrupted and I can't get new access token using code from this url.
Response i get using code from url:
{
"error": "invalid_grant",
"error_description": "Malformed auth code."
}
How can i completely log out from my app?
Credential authorize=getCredentials(gmailMailBox, emailProperties.getGoogleClientSecrets());
authorize.refreshToken();
Unirest.post(emailProperties.getRevokeTokenUrl(authorize.getAccessToken()));
getCredentials method:
Credential authorize = new GoogleCredential.Builder()
.setTransport(GoogleNetHttpTransport.newTrustedTransport())
.setJsonFactory(JacksonFactory.getDefaultInstance())
.setClientSecrets(clientSecrets)
.build()
.setAccessToken(gmailMailBox.getAccessToken())
.setRefreshToken(gmailMailBox.getRefreshToken());
private Credential getCredentials(GmailMailBox gmailMailBox, GoogleClientSecrets clientSecrets)
throws GeneralSecurityException, IOException {
return new GoogleCredential.Builder().setTransport(GoogleNetHttpTransport.newTrustedTransport())
.setJsonFactory(JacksonFactory.getDefaultInstance())
.setClientSecrets(clientSecrets)
.build()
.setAccessToken(gmailMailBox.getAccessToken())
.setRefreshToken(gmailMailBox.getRefreshToken());
}
public class GmailMailBox {
private String accessToken;
private String refreshToken;
private LocalDateTime expiresIn;
}
public GoogleClientSecrets getGoogleClientSecrets() {
GoogleClientSecrets clientSecrets = new GoogleClientSecrets();
GoogleClientSecrets.Details clientSecretsDetails = new GoogleClientSecrets.Details();
clientSecretsDetails.set("client_id", client_id);
clientSecretsDetails.set("project_id", project_id);
clientSecretsDetails.set("auth_uri", auth_uri);
clientSecretsDetails.set("token_uri", token_uri);
clientSecretsDetails.set("auth_provider_x509_cert_url", auth_provider_x509_cert_url);
clientSecretsDetails.set("client_secret", client_secret);
clientSecrets.setWeb(clientSecretsDetails);
return clientSecrets;
}

Send email via console app over Microsoft Graph API using user credentials

I've adapted the Java console application letting users sign-in with username/password to call Microsoft Graph API on behalf of them, but instead of retrieving basic user data to send emails.
However, while the original example works fine (I am getting user email, see commented code below), I am getting this error when sending emails:
Graph service exception Error code: NoPermissionsInAccessToken
Error message: The token contains no permissions, or permissions can not be understood.
For this operation Mail.Send scope needs to be defined in Azure portal and it should be sufficient to use without Admin consent.
I use these Microsoft dependencies:
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph</artifactId>
<version>1.6.0</version>
</dependency>
This is the actual code:
public class MicrosoftGraphMailer {
private final static String CLIENT_APP_ID = "{real-client-app-id}";
private final static String AUTHORITY = "https://login.microsoftonline.com/{real-tenant-id}/";
public static void main(String[] args) throws Exception {
String user = "{real-user}";
String password = "{real-password}";
String token = getUserPasswordAccessToken(user, password).accessToken();
System.out.println(token);
SimpleAuthProvider authProvider = new SimpleAuthProvider(token);
IGraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
Message message = new Message();
message.subject = "My subject";
ItemBody body = new ItemBody();
body.contentType = BodyType.HTML;
body.content = "<h1>My HTML body</h1>";
message.body = body;
List<Recipient> toRecipientList = new LinkedList<>();
Recipient toRecipient = new Recipient();
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = "{real-recipient}";
toRecipient.emailAddress = emailAddress;
toRecipientList.add(toRecipient);
message.toRecipients = toRecipientList;
graphClient.me()
.sendMail(message, false)
.buildRequest()
.post();
/*
String mail = graphClient.me()
.buildRequest()
.get().mail;
System.out.println(mail);
*/
}
private static IAuthenticationResult getUserPasswordAccessToken(String user, String password) throws Exception {
PublicClientApplication app = PublicClientApplication.builder(CLIENT_APP_ID).authority(AUTHORITY).build();
Set<String> scopes = new HashSet<>(Arrays.asList("Mail.Send"));
UserNamePasswordParameters userNamePasswordParam = UserNamePasswordParameters.builder(
scopes, user, password.toCharArray())
.build();
return app.acquireToken(userNamePasswordParam).get();
}
private static class SimpleAuthProvider implements IAuthenticationProvider {
private String accessToken = null;
public SimpleAuthProvider(String accessToken) {
this.accessToken = accessToken;
}
#Override
public void authenticateRequest(IHttpRequest request) {
request.addHeader("Authorization", "Bearer " + accessToken);
}
}
}
Basically I need console daemon app for sending emails on behalf of me without any user interaction. Credentials will be stored outside the app. I don't need permissions to send emails on behalf of arbitrary user.
usually that error message only occurs if its not sending a token to graph or a valid token to graph. To debug, I would first get the token and decode it by either pasting it in jwt.ms or something to see if the required scopes are in the token as expected.
I also wonder if not requesting the user.read scope causes issues for mail.send
Also, you said admin consent need not be given, but in your case it does. because if the admin doesn't consent, then the user must consent. but since you are doing this headless, there is no opportunity for the user to consent. meaning no one consented for this application to send mail as you.

Java webservice client with ADFS SAML authentication

We are trying to connect to webservice (from Java) that has ADFS SAML authentication.
All the examples I have seen, use Basic Authentication over HTTPS. (I am just using HttpsURLConnection to make a request for now, not using anything like Axis or JAX-WS)
I am not sure how to approach ADFS SAML authentication. Here's what I understand so far (don't know much about SAML):
I make one request, pass username/password and get the
authentication token back
Save the authentication token
Pass the token as some SOAP attribute in my calls where I invoke an
actual operation on the webservice
No idea under which attribute would I put this authentication token though
Is my above approach correct? If so, is there some library that I can use that does all this?
If not how can I go about doing this manually?
Please let me know if there are other or better ways of going about this.
If you are trying to build native app then can use below code. i has tried to use power bi rest apis. once you gets token you can use that in api calls.
public class PublicClient {
private final static String AUTHORITY = "https://login.microsoftonline.com/common";
private final static String CLIENT_ID = "XXXX-xxxx-xxx-xxx-xxxX";
private final static String RESOURCE = "https://analysis.windows.net/powerbi/api";
public static void main(String args[]) throws Exception {
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
System.out.print("Enter username: ");
String username = br.readLine();
System.out.print("Enter password: ");
String password = br.readLine();
AuthenticationResult result = getAccessTokenFromUserCredentials(
username, password);
System.out.println("Access Token - " + result.getAccessToken());
System.out.println("Refresh Token - " + result.getRefreshToken());
System.out.println("ID Token Expires on - " + result.getExpiresOn());
}
}
private static AuthenticationResult getAccessTokenFromUserCredentials(
String username, String password) throws Exception {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
RESOURCE, CLIENT_ID, username, password, null);
result = future.get();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException(
"authentication result was null");
}
return result;
}
}

Invalid channel Id return by youtube service api

I am trying to get a list of my youtube channels from a java app using the com.google.api.services.youtube.YouTube class. .
First of all I have enabled the Service Account credentials (https://console.developers.google.com > Credentials ) and I have enabled the following apis :
-YouTube Analytics API
-YouTube Data API
-Analytics API
To make a call to the Youtube service I create a Credential object using the following code.
/** Global instance of the HTTP transport. */
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static Credential authorize() throws Exception
{
List<String> scopes = new ArrayList<String>();
scopes.add("https://www.googleapis.com/auth/youtube");
scopes.add("https://www.googleapis.com/auth/yt-analytics.readonly");
scopes.add("https://www.googleapis.com/auth/youtube.readonly");
scopes.add("https://www.googleapis.com/auth/youtubepartner-channel-audit");
GoogleCredential credential = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountPrivateKeyFromP12File(new File("C://file.privatekey.p12"))
.setServiceAccountId("xxxx-yyyyyyyyyyyyyy#developer.gserviceaccount.com")
.setServiceAccountScopes(scopes)
.build();
return credential;
}
After that I call the Youtube service to get my channels
/** Global instance of the HTTP transport. */
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
/** Global instance of the JSON factory. */
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
/** Global instance of Youtube object to make general YouTube API requests. */
private static YouTube youtube;
/** Global instance of YoutubeAnalytics object to make analytic API requests. */
private static YouTubeAnalytics analytics;
public String getDefaultChannelId(){
try{
Credential credential = authorize();
// YouTube object used to make all non-analytic API requests.
youtube = new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("API Project")
.build();
YouTube.Channels.List channelRequest = youtube.channels().list("id,snippet");
channelRequest.setMine(true);
channelRequest.setMaxResults(50L);
channelRequest.setFields("items(id,snippet/title,contentDetails,status,kind,etag,auditDetails)");
ChannelListResponse channels = channelRequest.execute();
System.out.println(channels.getItems());
// List of channels associated with user.
List<Channel> listOfChannels = channels.getItems();
// Grab default channel which is always the first item in the list.
Channel defaultChannel = listOfChannels.get(0);
String channelId = defaultChannel.getId();
return channelId;
}catch(Exception ex){
ex.printStacktrace();
}
}
The authorization code seems to work without any problem. The problem is with the getDefaultChannelId() method which returns a channel with id UC9i22sTxrX0IQk4AkT_Og3w .
I tried to navigate using the browser to my youtube channel using tha url : http://www.youtube.com/channel/UC9i22sTxrX0IQk4AkT_Og3w but the channel does not exist..
The line I used to print the channels results "System.out.println(channels.getItems());" displays the following json string.
[{"etag":"\"BDC7VThyM9nfoSQm1_kOyhtJTEw/yJvLzly7DMctrvFV5drOtgksadM\"","id":"UC9i22sTxrX0IQk4AkT_Og3w","kind":"youtube#channel","snippet":{"title":""}}
For some reason the youtube service does not return the right list of channels for the specific credential object.
But why????
You can not use Service Account with Youtube API : https://developers.google.com/youtube/v3/guides/moving_to_oauth#service_accounts. Youtube API is supposed to raise an error with service account but apparently it doesn't.
You should instead create a client ID of type Installed Application then retrieve a refresh token for your user (https://developers.google.com/accounts/docs/OAuth2InstalledApp) and use this token to create credentials.
Here is an example (store clientId, clientSecret, refreshToken and accessToken wherever you like, as soon as you keep it secret, token response should be cached) :
private String clientId;
private String clientSecret;
private String refreshToken;
private String accessToken;
private TokenResponse tokenResponse;
private Credential createCredential() {
final GoogleCredential.Builder builder = new GoogleCredential.Builder();
builder.setTransport(HTTP_TRANSPORT);
builder.setJsonFactory(JSON_FACTORY);
builder.setClientSecrets(clientId, clientSecret);
builder.addRefreshListener(new CredentialRefreshListener() {
#Override
public void onTokenResponse(final Credential credential,
final TokenResponse tokenResponse) throws IOException {
this.tokenResponse = tokenResponse;
}
#Override
public void onTokenErrorResponse(final Credential credential,
final TokenErrorResponse tokenErrorResponse)
throws IOException {
this.tokenResponse = null;
// trace error
}
});
return builder.build();
}
private YouTube getYoutube() {
final Credential credential = createCredential();
if (this.tokenResponse == null) {
credential.setAccessToken(this.accessToken);
credential.setRefreshToken(this.refreshToken);
} else {
credential.setFromTokenResponse(this.tokenResponse);
}
final YouTube youtube = new YouTube.Builder(HTTP_TRANSPORT,
JSON_FACTORY, credential).setApplicationName("*****").build();
return youtube;
}

How to get userinfo with google-api after succesfully been authenticated?

I try to get the userinfo after successfully authenticate with a gmail account (tok is a valid token):
GoogleCredential credential2 = new GoogleCredential.Builder()
.setTransport(TRANSPORT).setJsonFactory(JSON_FACTORY)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.setRequestInitializer((new HttpRequestInitializer() {
#Override
public void initialize(HttpRequest request)
throws IOException {
request.getHeaders().setAuthorization("Bearer ".concat(tok));
}
}))
.build();
Oauth2 userInfoService = new Oauth2.Builder(TRANSPORT,
JSON_FACTORY, credential2.getRequestInitializer())
.setApplicationName(APPLICATION_NAME).build();
Userinfo userInfo = userInfoService.userinfo().get().execute();
logger.warn("User email: {}", userInfo.getEmail());
logger.warn("User gender: {}", userInfo.getGender());
logger.warn("User complet name: {} - {}", userInfo.getFamilyName(), userInfo.getName());
But logs display 'null' for all fields, the json data returned contains only the id:
{
"id": "113695880661351193041"
}
What i'm supposed to do ? Add a special scope to do this? I tried it several times without success, just by adding scope=https://www.googleapis.com/auth/userinfo.profile as url parameter, maybe that's wrong ?
Hope someone can help or know how to add scopes to my request and get the correct response from this service.
This works for me :
Credential credential = OAuth2Utils.newFlow().loadCredential(userId);
Oauth2 service = new Oauth2.Builder(OAuth2Utils.HTTP_TRANSPORT, OAuth2Utils.JSON_FACTORY, credential).setApplicationName("appname").build();
UserInfo userInfo = service.userinfo().get().execute();
These are some of the properties that are returned :
userInfo.getBirthday()
userInfo.getFamilyName()
userInfo.getGender()
userInfo.getGivenName()
userInfo.getHd()
userInfo.getLink()
The utility class OAuth2Utils referred in the code :
/** Global instance of the HTTP transport. */
public final static HttpTransport HTTP_TRANSPORT = new UrlFetchTransport();
/** Global instance of the JSON factory. */
public final static com.google.api.client.json.JsonFactory JSON_FACTORY = new com.google.api.client.json.jackson2.JacksonFactory();
public static GoogleAuthorizationCodeFlow newFlow() throws IOException {
return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, getClientCredential(),
Arrays.asList(SCOPES)).setCredentialStore(new OAuth2CredentialStore()).setAccessType("offline")
.setApprovalPrompt("force").build();
}

Categories