Google Authentication with a Backend Server required Scopes - java

I am following these instructions (https://developers.google.com/identity/sign-in/android/backend-auth) for getting an ID token to be sent to my Backend but when I set String scopes = "audience:server:client_id:" + Service.SERVER_CLIENT_ID; (Yes the SERVER_CLIENT_ID is not the Android Client ID) I fail to get a token and throws this error.
E/Login: com.google.android.gms.auth.GoogleAuthException: Unknown
However when I use the following scope instead
String scopes = "oauth2:profile email";
I successfully get 'a' token but it's not as long as I expected it to be and I'm afraid it might be wrong.
My questions are...
1) Why doesn't the scopes = "audience:server:client_id:" + SERVER_CLIENT_ID; used in the guide work?
2) Is the token I get from using String scopes = "oauth2:profile email"; a safe one for verifying a user on a Backend?
The code is below.
#Override
protected String doInBackground(Void... params) {
String accountName = Plus.AccountApi.getAccountName(googleApiClient);
Account account = new Account(accountName, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
//String scopes = "oauth2:profile email";
String scopes = "audience:server:client_id:" + Service.SERVER_CLIENT_ID; // Not the app's client ID.
Log.d(TAG, "Account Name: " + accountName);
Log.d(TAG, "Scopes: " + scopes);
try {
userIdToken = GoogleAuthUtil.getToken(getApplicationContext(), account, scopes);
return userIdToken;
} catch (IOException e) {
Log.e(TAG, "IOError retrieving ID token.", e);
return null;
} catch (UserRecoverableAuthException e) {
startActivityForResult(e.getIntent(), RC_SIGN_IN);
return null;
} catch (GoogleAuthException e) {
Log.e(TAG, "GoogleAuthError retrieving ID token.", e);
return null;
}
}

When you set the scope to oauth2:profile email you are returned an access token, which is different from an id token.
An access token can be used to access Google APIs, an id token is a JWT that contains identity information about the user that is digitally signed by Google. The formats are different. If you try to authorize an access token using the sample code provided for id tokens you'll get an invalid error.
If you look at the documentation for GoogleAuthUtil.getToken() you'll see that GoogleAuthException is a fatal exception usually caused by a client error such as invalid scope or invalid client.
https://developers.google.com/android/reference/com/google/android/gms/auth/GoogleAuthUtil#getToken(android.content.Context, android.accounts.Account, java.lang.String, android.os.Bundle)
Make sure that you have set up both an App and Webserver oAuth2 ID in Google Developer console and that the package name in your manifest matches the package name you provide along with the SHA fingerprint when creating the App ID. Use the Webserver ID as SERVER_CLIENT_ID.
I uploaded some sample code to Github. https://github.com/kmosdev/google-signin-backend-auth
I started with Google's sample sign-in app and modified it to add backend auth. Further details are in the Readme.
Another thing to check is that you have the correct permissions in your manifest file, but I believe you'd get a different error if this was wrong:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />

Related

Firebase: Failed to verify the signature of Firebase ID token

When I try to verify the Firebase jwt token in my Spring Boot backend application, I get the following error:
Failed to verify the signature of Firebase ID token. See
https://firebase.google.com/docs/auth/admin/verify-id-tokens for
details on how to retrieve an ID token.
In the client (Flutter) I log the jwt as follows:
GoogleSignInAccount googleSignInAccount = await _googleSignIn.signIn();
GoogleSignInAuthentication googleSignInAuthentication = await googleSignInAccount.authentication;
AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
UserCredential authResult = await _auth.signInWithCredential(credential);
_user = authResult.user;
logger.i(await _user.getIdToken()); // Print jwt
I send the jwt that gets logged to my backend through the Authorization header as a bearer token.
Using Spring security (it doesn't matter), I just perform the following check:
FirebaseToken decoded = FirebaseAuth.getInstance().verifyIdToken(token);
My firebase app init config is pretty standard (env variable pointing to config.json is set):
#Primary
#Bean
public void firebaseInit() throws IOException {
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.build();
if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options);
}
}
After debugging, following method throws in class RSASignature (package package sun.security.rsa):
#Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
if (publicKey == null) {
throw new SignatureException("Missing public key");
}
try {
if (sigBytes.length != RSACore.getByteLength(publicKey)) {
throw new SignatureException("Signature length not correct: got " +
sigBytes.length + " but was expecting " +
RSACore.getByteLength(publicKey));
}
sigBytes length is 113, whereas it expects to be 256.
Perhaps I'm doing something wrong though...
My God... the logger that I used in dart decided to just cap the jwt string so the jwt was incomplete.
Now I get a message 'forbidden' happy joy. But the previous error has been resolved.
Edit 'Forbidden' was consequence of a minor Spring Boot issue (adding roles to authorities).
It now works as expected.

Java Dropbox HttpServletRequest ( java.lang.UnsupportedOperationException: Not supported yet.)

in my java SWING app, the user can transfer files to his DropBox space, I have successfully implemented the library and I'm trying to get the user's access token without having to copy and paste it into my app.
According to the documentation, I have to use HttpServletRequest to get what I want, but I get an exception, as written in the title.
public class LoginServlet implements HttpServletRequest {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// read form fields
String token = request.getParameter("data-token");
// get response writer
PrintWriter writer = response.getWriter();
// build HTML code
String htmlRespone = "<html>";
htmlRespone += "<h2>token: " + token + "<br/>";
htmlRespone += "</html>";
// return response
writer.println(htmlRespone);
response.sendRedirect(Main.redirectUri);
}
#Override
public HttpSession getSession(boolean bln) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public HttpSession getSession() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
and in Main class
LoginServlet request = new LoginServlet();
// Fetch the session to verify our CSRF token
HttpSession session = request.getSession(true);
String sessionKey = "dropbox-auth-csrf-token";
DbxSessionStore csrfTokenStore = new DbxStandardSessionStore(session, sessionKey);
DbxRequestConfig config = new DbxRequestConfig("User"); //Client name can be whatever you like
DbxAppInfo appInfo = new DbxAppInfo(App key, App secret);
DbxWebAuth webAuth = new DbxWebAuth(config, appInfo);
DbxWebAuth.Request authRequest = DbxWebAuth.newRequestBuilder()
.withRedirectUri(redirectUri, csrfTokenStore)
.build();
String url = webAuth.authorize(authRequest);
Desktop.getDesktop().browse(new URL(url).toURI());
DbxAuthFinish authFinish;
try {
authFinish = webAuth.finishFromRedirect(redirectUri, csrfTokenStore, request.getParameterMap());
} catch (DbxWebAuth.BadRequestException ex) {
//log("On /dropbox-auth-finish: Bad request: " + ex.getMessage());
//response.sendError(400);
return;
} catch (DbxWebAuth.BadStateException ex) {
// Send them back to the start of the auth flow.
//response.sendRedirect(redirectUri);
return;
} catch (DbxWebAuth.CsrfException ex) {
//log("On /dropbox-auth-finish: CSRF mismatch: " + ex.getMessage());
//response.sendError(403, "Forbidden.");
return;
} catch (DbxWebAuth.NotApprovedException ex) {
// When Dropbox asked "Do you want to allow this app to access your
// Dropbox account?", the user clicked "No".
return;
} catch (DbxWebAuth.ProviderException ex) {
//log("On /dropbox-auth-finish: Auth failed: " + ex.getMessage());
//response.sendError(503, "Error communicating with Dropbox.");
return;
} catch (DbxException ex) {
//log("On /dropbox-auth-finish: Error getting token: " + ex.getMessage());
//response.sendError(503, "Error communicating with Dropbox.");
return;
}
String accessToken = authFinish.getAccessToken();
// Save the access token somewhere (probably in your database) so you
// don't need to send the user through the authorization process again.
// Now use the access token to make Dropbox API calls.
DbxClientV2 client = new DbxClientV2(config, accessToken);
//...
full stack
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:873)
Caused by: java.lang.UnsupportedOperationException: Not supported yet.
at cloud.LoginServlet.getSession(LoginServlet.java:404)
at cloud_new.Main.main(Main.java:103)
the row at cloud_new.Main.main(Main.java:103)
is this HttpSession session = request.getSession(true);
Note
This is not a solution for the main java question relate to some http error.
This is a proposed approach based or inspired in how heroku auth and google container registry auth works.
Also dropbox and other platforms does not offer a kind of special admin api_key to consume the final user resources. Due to that a web login in a real browser is required and this cannot be easily replaced or emulated with web_views or internal browsers in android or ios. Source
Basic idea
Develop a web application which exposes two functionalities: receive the redirect from dropbox authorization platform and a rest endpoint to query if user is authenticated.
Assumptions
web called my-oauth2-helper.com
Flow
user starts the desktop application.
desktop application queries to my-oauth2-helper.com/validate/auth sending user email.
my-oauth2-helper.com/validate/auth returns a field which indicate that user does not have a valid authentication and other field with the dropbox login url.
dropbox login url which has the classic oauth2 fields previously configured in dropbox developer console: client_id, redirect_uri, etc.
desktop application uses this login url to open a browser tab.
User is prompted with a valid dropbox web login, enter its credentials and accept the consent page.
Dropbox following the oauth2 protocol, redirects the user to a
my-oauth2-helper.com/redirect which is the classic field called redirect_uri
my-oauth2-helper.com/redirect receive the auth_code and exchange it for a valid access_token for this user and store it in a kind of database. After that could shows a message like: "Now, you can close this page and returns to the desktop application"
Since user was prompted with dropbox web login, the desktop application started to queries at regular intervals if user has a valid authentication to the endpoint my-oauth2-helper.com/token sending the email as request parameter
After valid access_token generation , the my-oauth2-helper.com/token endpoint returns a valid access_token for this user.
This access_token is ready to use in order to consume any dropbox api feature, which user accepted in consent page.

How to identify and verifying tokenId from google API?

I can't find any solution for as i think very general issue...
I use google API for LogIn, and according to this article i can get tokenId using this method
private String getTokenId(GoogleSignInResult result) {
String tokenId = null;
GoogleSignInAccount googleSignInAccount = result.getSignInAccount();
if (googleSignInAccount != null) {
tokenId = googleSignInAccount.getIdToken();
}
return tokenId;
}
it is standard method, but why this method retern back token id which the contains 857 char of String? And i can't verify it with standard link
https://www.googleapis.com/oauth2/v3/tokeninfo?access_token= <857 char>;
and i get back this response
{
"error_description": "Invalid Value"
}
I am sure if google provide method to get token id there is have to be a method to verify it...
What am i doing wrong?

Regarding google +domain api ............ getting an error 403 forbidden while listing the circle using installed application

i have just listing down the circle name in my google+domain api but getting an error of
i have used installed application-> other option while making the application in google developer console
I am developing a small installed 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 Installed application from Google API console. Using Google+ Domain API's, I am able to generate the access token.
Also I am using ---some----#gmail.com using gmail account
my code is as :-
enter code here
private static final String CLIENT_ID = "xyxxxxxxxx something in there";
private static final String CLIENT_SECRET = "Fhh1LYQ__UTso48snXHyqSQ2";
public static void main(String[] args) throws IOException {
// List the scopes your app requires:
try{
List<String> SCOPE = Arrays.asList(
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/plus.stream.write",
"https://www.googleapis.com/auth/plus.circles.read");
final String REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
new NetHttpTransport(),
new JacksonFactory(),
CLIENT_ID, // This comes from your Developers Console project
CLIENT_SECRET, // This, as well
SCOPE)
.setApprovalPrompt("force")
// Set the access type to offline so that the token can be refreshed.
// By default, the library will automatically refresh tokens when it
// can, but this can be turned off by setting
// dfp.api.refreshOAuth2Token=false in your ads.properties file.
.setAccessType("offline").build();
// This command-line se`enter code here`rver-side flow example requires the user to open the
// authentication URL in their browser to complete the process. In most
// cases, your app will use a browser-based server-side flow and your
// user will not need to copy and paste the authorization code. In this
// type of app, you would be able to skip the next 5 lines.
// You can also look at the client-side and one-time-code flows for other
// options at https://developers.google.com/+/web/signin/
String url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
System.out.println("Please open the following URL in your browser then " +
"type the authorization code:");
System.out.println(" " + url);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String code = br.readLine();
// End of command line prompt for the authorization code.
GoogleTokenResponse tokenResponse = flow.newTokenRequest(code)
.setRedirectUri(REDIRECT_URI).execute();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(new NetHttpTransport())
.setJsonFactory(new JacksonFactory())
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.addRefreshListener(new CredentialRefreshListener() {
#Override
public void onTokenResponse(Credential credential, TokenResponse tokenResponse) {
// Handle success.
System.out.println("Credential was refreshed successfully.");
}
#Override
public void onTokenErrorResponse(Credential credential,
TokenErrorResponse tokenErrorResponse) {
// Handle error.
System.err.println("Credential was not refreshed successfully. "
+ "Redirect to error page or login screen.");
}
})
// You can also add a credential store listener to have credentials
// stored automatically.
//.addRefreshListener(new CredentialStoreRefreshListener(userId, credentialStore))
.build();
// Set authorized credentials.
credential.setFromTokenResponse(tokenResponse);
// Though not necessary when first created, you can manually refresh the
// token, which is needed after 60 minutes.
credential.refreshToken();
// Create a new authorized API client
PlusDomains service = new PlusDomains.Builder(new NetHttpTransport(), new JacksonFactory(), credential).setApplicationName("Get-me").setRootUrl("https://www.googleapis.com/").build();
PlusDomains.Circles.List listCircles = service.circles().list("me");
listCircles.setMaxResults(5L);
CircleFeed circleFeed = listCircles.execute();
List<Circle> circles = circleFeed.getItems();
// Loop until no additional pages of results are available.
while (circles != null) {
for (Circle circle : circles) {
System.out.println(circle.getDisplayName());
}
// 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;
}
}
}catch(Exception e)
{
System.out.println("Exception "+e);
}
}
Its an INSTAlled Application -> other Options........ in google developer console
i get the below error:---
Exception com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Forbidden",
"reason" : "forbidden"
} ],
"message" : "Forbidden"
}
Note: I have also enabled Google+ Domain API in my Google API Console.
REDIRECT_URI ="urn:ietf:wg:oauth:2.0:oob" since it's a Installed app. Any Suggestions?
Please help me out guys
See this other answer: the Google+ Domains API is only "for people who use Google Apps at college, at work, or at home." It appears that Google does not currently allow apps to list circles for regular Google+ accounts.

Google OAuth, handle a revoked authorization

I've been using Google OAuth to let users authorize access to the Calendar Service for my Web Application. After a successful 3-legged auth flow, I was storing all user's credentials in a common file on the app Server. The next time the app needs to use the service, it will check if the credentials exist, and if yes, it will assume they are valid
code works like that
#Override
public void _authorize(String userId) throws IOException {
// Check if user has already authorised the service.
Credential credents = flow.loadCredential(userId);
// Checking if the given user is not authorized
if (credents == null) {
//Create credentials now. user will be redirected to authorise
try {
//Creating a LocalServer Receiver
// Getting the redirect URI
// Creating a new authorization URL
// Setting the redirect URI
// Building the authorization URL
// Receiving authorization code
// Exchanging it for an access token
// Storing the credentials for later access
credents = flow.createAndStoreCredential(response, id);
} finally {
// Releasing resources
}
} else {
// Assume the credentials are valid. so there's nothing left to do here, let's get that client
//Update: Nooooooot! the user might have revoked the authorization, so credents != null BUT they are invalid
//TODO: handle an Exception here, and manage the revoked credentials
}
// Setting up the calendar service client
client = new com.google.api.services.calendar.Calendar.Builder(httpTransport, jsonFactory, credents).setApplicationName(APPLICATION_NAME)
.build();
}
This works fine, as long as the user never changes his mind. But if the user decides to manually revoke the authorization using the Google Account security options, the com.google.api.services.calendar.Calendar retrieval will Fail.
My question is :
Is there a way to check if the credentials are still valid, before trying to use them ?
Else, I can only guess that the failure to get the client object, is the only way to have my portal realize that the credentials are no more valid ?
What should I do about the invalid/revoked credentials ? should I just call flow.createAndStoreCredential and they are going to be overwritten? Or do I have to delete the old ones first ? (how ?)
You can use the refreshToken() method for this. See example:
// Fetch credential using the GoogleAuthorizationCodeFlow
GoogleAuthorizationCodeFlow authorizationCodeFlow;
Credential credential = authorizationCodeFlow.loadCredential(userId);
if (credential != null) {
try {
// refresh the credential to see if the refresh token is still valid
credential.refreshToken();
System.out.println("Refreshed: expires in: " + credential.getExpiresInSeconds());
} catch (TokenResponseException e) {
// process exception here.
// This will catch the Exception.
// This Exception contains the HTTP status and reason etc.
// In case of a revoke, this will throw something like a 401 - "invalid_grant"
return;
}
} else {
// No credential yet known.
// Flow for creating a new credential here
}
EDIT
If you indeed have an invalid refresh token and you want to renew it, then you need to repeat the steps that you did in the first place to get the credentials. So:
genererate a new authorization URL
redirect the user to it
user accepts the consent screen
catch the authorization code from the redirect back to your app
request a new token from Google using the authorization code
create and store a new Credential using the response from Google
No need to delete the old credential. But if you want to explicitly do so, it is possible.
Something like:
// This userId is obviously the same as you used to create the credential
String userId = "john.doe";
authorizationCodeFlow.getDataStore().delete(userId);
You can use the endpoint https://www.googleapis.com/oauth2/v1/tokeninfo to determine if an OAuth2 token is still valid. More information is available in the OAuth2 guide.
Answer to the first question:
When using the Service object for retrieving calendar items from Google Calendar, the token are automatically verified. When they are invalid, they will be refreshed automatically, and stored in the datastore you provided to the flow.
this can also be done manually. A token is valid for 3600 seconds (one hour). When retrieving a token you get this value with the timestamp when it was issued. You could manually determine if a token is valid. If it is not valid call the following async method.
await credents.RefreshtokenAsync(CancellationToken.None);
This function gets you fresh tokens, and stores them in the datastore you provided.
You could check token with tokeninfo and if token is not valid:
- remove credential from datastore
- invoke new auth
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
UserService userService = UserServiceFactory.getUserService();
if (userService.isUserLoggedIn()) {
User user = userService.getCurrentUser();
log.info(String.format("LoggedUser: %s %s", user.getEmail(), user.getUserId()));
Credential credential = this.getCredential();
Tokeninfo tokenInfo = OAuth2Utils.getTokenInfo(credential, null);
if (tokenInfo != null)
log.info(String.format("Token expires in: %d", tokenInfo.getExpiresIn()));
else {
OAuth2Utils.deleteCredential(user.getUserId());
response.sendRedirect(request.getRequestURI()); // recall this servlet to require new user authorization
return;
}
}
public static Tokeninfo getTokenInfo(Credential credential, String accessToken) {
Oauth2 service = new Oauth2.Builder(new NetHttpTransport(), Constant.JSON_FACTORY, credential).setApplicationName(Constant.APP_NAME).build();
Tokeninfo tokenInfo = null;
try {
tokenInfo = service.tokeninfo().setAccessToken( accessToken == null ? credential.getAccessToken() : accessToken ).execute();
} catch (IOException e) {
log.warning("An error occurred: " + e);
}
return tokenInfo;
}

Categories

Resources