How do I use the Google API Explorer to test my own App Engine Endpoints using OAuth? - google-app-engine

I have an Endpoints API deployed on App Engine. I have no problem using the Google API Explorer to make requests to API methods that do NOT require being logged in. The URL I'm using for that is:
https://developers.google.com/apis-explorer/?base=https://[MY_APP_ID].appspot.com/_ah/api
Where I am stuck is calling API methods that require the user to be logged in, such as this one:
#ApiMethod(name = "config.get",
clientIds = {"[MY_CLIENT_ID].apps.googleusercontent.com", "com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID"},
audiences = {"[MY_APP_ID].appspot.com"},
scopes = {"https://www.googleapis.com/auth/userinfo.email"})
public Config getConfig(User user) throws OAuthRequestException {
log.fine("user: " + user);
if (user == null) {
throw new OAuthRequestException("You must be logged in in order to get config.");
}
if (!userService.isUserAdmin()) {
throw new OAuthRequestException("You must be an App Engine admin in order to get config.");
}
...
On the API Explorer there's a switch top right that, when clicked, allows me to specify scopes and authorise. I'm doing that with just the userinfo.email scope checked. It makes no difference. The response I get from my call is:
503 Service Unavailable
- Show headers -
{
"error": {
"errors": [
{
"domain": "global",
"reason": "backendError",
"message": "java.lang.IllegalStateException: The current user is not logged in."
}
],
"code": 503,
"message": "java.lang.IllegalStateException: The current user is not logged in."
}
}
Back when Endpoints was in Trusted Tester phase, I remember there being a manual step in the OAuth2 Playground to get an ID token instead of an access token or some such thing. If that is still required, any mention of that seems to have disappeared from the Endpoints docs now and I see now way to swap out tokens in the API Explorer either.

I see you've got "com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID" in quotes. If that's not a typo in your transcription to Stack Overflow, that's a problem. The value is already a string, so you're just passing in the text com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID (not the actual client ID) as the whitelisted scope. That won't work. Try this instead:
#ApiMethod(name = "config.get",
clientIds = {"[MY_CLIENT_ID].apps.googleusercontent.com", com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID},
audiences = {"[MY_APP_ID].appspot.com"},
scopes = {"https://www.googleapis.com/auth/userinfo.email"})
Edit: isUserAdmin is unsupported within Endpoints, and is likely a secondary cause of error. I'd suggest filing a feature request for supporting this method on the provided User object (we likely won't provide support for the user service itself, so it's separate from OAuth login.)

I don't know when this was introduced, but if you use OAuth2, instead of UserService.isUserAdmin() you can use OAuthServiceFactory.getOAuthService().isUserAdmin(EMAIL_SCOPE) where EMAIL_SCOPE is "https://www.googleapis.com/auth/userinfo.email".
This makes it easy to use the old OpenId or OAUth2:
boolean isAdmin = false;
try {
isAdmin = userService.isUserAdmin());
} catch (IllegalStateException e1) {
try {
isAdmin = OAuthServiceFactory.getOAuthService().isUserAdmin(EMAIL_SCOPE);
} catch (Exception e2) {}
}
The original question was asked several years ago, but maybe this will help others.

Related

How to receive user photos in MS Teams Bot which installed in different organizations?

According to the documentation https://learn.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-beta - in order to get the user photo we need to perform HTTP request https://graph.microsoft.com/beta/users/{userId}/photo/$value providing the access token and user id.
Using the client credentials flow I can receive the access token for a specifiс organization (tenant id), for example:
public static String getAppAccessToken(String[] scopes) {
ConfidentialClientApplication cca;
try {
cca = ConfidentialClientApplication.builder(applicationId, ClientCredentialFactory.createFromSecret(applicationSecret))
.authority("https://login.microsoftonline.com/<<tenantId>>/")
.build();
} catch (MalformedURLException e) {
return null;
}
Set<String> scopeSet = Set.of(scopes);
ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(
scopeSet)
.build();
CompletableFuture<IAuthenticationResult> future = cca.acquireToken(clientCredentialParam);
return future.join().accessToken();
}
But the access token received in such way allows to receive photos only for users of the organization, which has the same tenant id with the tenant id of the bot app registered in Azure Active Directory App Registration.
Is it possible to receive the photos of the users from other organizations where my bot is installed, using client credentials flow token?
In this case I receive:
{
"error": {
"code": "UnknownError",
"message": "{\"error\":{\"code\":\"NoPermissionsInAccessToken\",\"message\":\"The token contains no permissions, or permissions can not be understood.\",\"innerError\":{\"oAuthEventOperationId\":\"d90f2331-a22a-44d5-889e-c0c14ea9129e\",\"oAuthEventcV\":\"n+NecjM040KWn+2G+e5oFQ.1\",\"errorUrl\":\"https://aka.ms/autherrors#error-InvalidGrant\",\"requestId\":\"0b6bb579-94a7-47ac-8ff9-26ee893e5cb0\",\"date\":\"2021-01-28T00:57:52\"}}}",
"innerError": {
"date": "2021-01-28T00:57:52",
"request-id": "0b6bb579-94a7-47ac-8ff9-26ee893e5cb0",
"client-request-id": "0b6bb579-94a7-47ac-8ff9-26ee893e5cb0"
}
}
}
I'm certainly no expert on auth, but here are two suggestions to look in to, in case this can help:
"https://login.microsoftonline.com/<>/" -> use "organizations" instead of tenant id
check what scopes you're using (you don't list what you're trying, so it might be this)
Just try using "https://login.microsoftonline.com/common" endpoint and see if you can get this working. Please read more on that in detail here.
You can even send REST request to individual tenant's get the access token and access the information accordingly. Please be aware of having scopes while sending request.

ADAL.js - Obtaining Microsoft Graph Access Token with id_token

I am attempting to integrate Azure AD login and Graph API into my angular2 website.
I have successfully implemented an ADAL login and redirect, built around a useful blog post here
From this I retrieved an id_token parameter that my adalservice can access. Currently this is acheived through a simple context.login() and catching the token in the redirect.
When I use this token to try and access Microsoft Graph, I receive an InvalidAuthenticationToken response stating Access Token validation failure.
I'm new to this stuff, so it could be that my call is intrinsically wrong, or that I lack certain permissions in AD, or my app reg lacks permissions. I've seen that I potentially need to request an access token with sufficient scope, yet I can find any examples of this.
Has anyone used this adalService library to obtain tokens for use with Graph API?
I found a solution to my problem.
I was using the wrong token. I had to acquire a token specifically for Graph API. This meant I would have to first log in and then call this.context.acquireToken() like below:
this.context.acquireToken("https://graph.microsoft.com", function (error, id_token) {
if (error || !id_token) {
console.log('ADAL error occurred: ' + error);
}
else {
this.graphAccessToken = id_token;
//Call graph API
}
}.bind(this)
);
It seems like it's essential that this process have 2 calls. Maybe someone can shed some light on whether I can immediately obtain a token with scope for the Graph API on login. Perhaps by setting required permissions for the app in Azure AD.
Just to have a clarity for all, updating the end to end solution here again.
In case you do not have the base starter code, refer to this link Adal-JS Tutorial. This post only concerns with the customization involved.
Step 1: Configure the AdalService
(only new code is shown, other methods remain as it is)
export class AdalService {
public get graphAccessToken() {
return sessionStorage[new AppConstants().User_Graph_Token];
}
public retrieveTokenForGraphAPI() {
this.context.acquireToken('https://graph.microsoft.com', function (error, graph_token) {
if (error || !graph_token) {
console.log('ADAL error occurred: ' + error);
} else {
// Store token in sessionStorage
sessionStorage[new AppConstants().User_Graph_Token] = graph_token;
return;
}
}.bind(this)
);
}
}
The code should have existing handlers for id_token callback and corresponding configuration in the routing. If not, please refer to link above for the initial code.
Now the requirement is retrieve the access_token once the id_token is retrieved. The access_token has additional field for "puid" which describes identifier for claims. This will be the step 2.
Step 2: Update LoginComponent
ngOnInit() {
if (!this.adalService.isAuthenticated) {
console.log('LoginComponent::Attempting login via adalService');
this.adalService.login();
} else {
if (this.adalService.accessTokenForGraph == null) {
console.log('LoginComponent::Login valid, attempting graph token retrieval');
this.adalService.retrieveTokenForGraphAPI();
}
}
Now the token is retrieved and stored for later use.
Step 3: Update Routing for 'access_token' callback
Similar to the 'id_token' callback, we need to add additional callback route for the access_token. The callback components will remain same. Their code is as described in the main link. Note that *access_token" endpoint is MS provided, hence be careful not to change the name.
{ path: 'access_token', component: OAuthCallbackComponent, canActivate: [OAuthCallbackHandler] },
{ path: 'id_token', component: OAuthCallbackComponent, canActivate: [OAuthCallbackHandler] }
Step 4: Use the token wherever required
const bearer = this.adalService.graphAccessToken();

Validate AppEngine Endpoints Client IDs while using custom Authenticator

Earlier our client side apps used Google Sign-In.
Now we are moving to custom auth, as we plan on having the user's phone number as the only identity (instead of a Google Account). But after implementing the custom Authenticator, the client IDs are not being checked and I am able to make API calls from anywhere.
When only Google Sign-in was being used at the client side, the client ID was being validated and I was not able to make API calls from any clients other than the ones authorized.
How do I verify the Client IDs while using custom authenticator?
Code for the Api Endpoint
#Api(name = "apiSubscriber",
clientIds = {
Constants.webClientId,
Constants.androidClientId,
Constants.iOSClientId
},
authenticators = {com.google.api.server.spi.auth.EndpointsAuthenticator.class,
CustomAuth.class},
audiences = {Constants.androidAudience},
)
public class ApiSubscriber {
#ApiMethod
public Subscriber getSubscriberData(User user){
if(user!=null){
//fetches subscriber data
}
}
//... Other ApiMethods
}
Code for Custom Authenticator
public class CustomAuth implements Authenticator {
#Override
public User authenticate(HttpServletRequest request) {
String phoneNumber = request.getHeader("phoneNumber");
String token = request.getHeader("Authorization");
if(checkToken(phoneNumber,token)){
return new User(phoneNumber);
}
return null;
}
private boolean checkToken(String phoneNumber, String token){
//Checks if authorization token is valid
}
}
Unfortunately at this time, it does not appear that you can restrict your Endpoints API to a client and not use Google Sign in.
When using Google's oAuth2 authentication some magic voodoo happens (not exactly sure what) and apps get restricted to the ClientId's that you specify.
However, when you stop using that authentication method, I have found (to my dear disappointment), that it does not work anymore.
See my question here where you can read about my tests and some additional things that may give you more information: Authenticating your client to Cloud Endpoints without a Google Account login
I don't sure is it a problem, but you have some bugs in code you provided.
authenticators = {com.google.api.server.spi.auth.EndpointsAuthenticator.class,
CustomAuth.class,
instead of comma must be bracket. Also, imho, you need only CustomAuth class here.
audiences = {Constants.androidAudience},
comma is redundant.
Second. You don't required to use custom Authenticator. You can send token and phone number as concatenated parameter or two parameters to your service method and check it there.

Unauthenticated call to Endpoint working for an API method with authentication

I am facing issue with Endpoints authentication.
This is the api code I am using
#ApiMethod(name = "myapiname.myapimethod", scopes = { Constants.EMAIL_SCOPE }, clientIds = {
Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.API_EXPLORER_CLIENT_ID }, audiences = { Constants.ANDROID_AUDIENCE })
public Result myapimethod(User user) throws OAuthRequestException, IOException {
// some work
return new Result();
}
In API explorer, it shows that the method requires Authorization, but it is getting successfully executed even without authorizing the request in API explorer.
Any help will be highly appreciated.
Thanks in advance.
Adding an User parameter alone won't check if the endpoint request is authenticated, we need to check that ourselves refer the documentation https://developers.google.com/appengine/docs/java/endpoints/auth#Java_Adding_a_user_parameter_to_methods_for_auth
`If an incoming client request has no authorization token or an invalid one, user is null. In your code, you need to check whether user is null and do ONE of the following, depending on the condition:
If the user is present, perform the authorized action.
If the user is null, throw an OAuthRequestException.
Alternatively, if the user is null, perform some action for an unauthorized client access if some sort of unauthorized access is desired.`

How do I test Cloud Endpoints with Oauth on devserver

My app uses Oauthed Cloud Endpoints and is working fine in production.
My problem is that on the local devserver, my User user is always set to example#example.com, even though I've gone through the usual auth, access code, etc etc etc and have a valid authed user.
I get that example#example.com is useful to test oauth endpoints before I have oauth working properly, but since my app is working I'd rather see the actual user there.
To be specific, my endpoint method is
#ApiMethod(name = "insertEmp"), etc
public Emp insertEmp(User user, Emp emp) {
System.out.println(user.getEmail()); // (A) log "appengine" email
System.out.println(OAuthServiceFactory.getOAuthService().getCurrentUser().getEmail(); // (B) log authed email
...
When deployed, everything is fine, and both (A) and (B) log the authenticated user (my.email#gmail.com).
When testing on my local devserver, (A) always logs "example#example.com", even though I have gone through the Oauth sequence and have a valid, authenticated user, and (B) logs my.email#gmail.com. So I can do hi-fidelity testing, I need the User to be the real authenticated user.
So in simple terms, how do I get (A) and (B) to be the same?
It seems it can't be done. I've ended up coding around it by putting the following code at the top of my Endpoint methods.
if ("example#example.com".equalsIgnoreCase(user.getEmail()) {
user = new User(OAuthServiceFactory.getOAuthService().getCurrentUser().getEmail(),"foo");
}
So now, even on devserver, the User email matches the Oauth email.
This is not so easy. You'll have to make your settings in the APIs Console. Here you will be able to add "localhost" (http://localhost/) Then you can authenticate, through Google, even though you are running you application on your localhost for development.
I have used it extensively, and it works OK
Links: https://code.google.com/apis/console/
Just remember the ID's you use here is completely independent of you appengine ID.
Took me a few hours to figure that one out.
The thing is that when you are doing the authentication in local, you are not doing it through the Google servers so authenticating your user is something that actually is not happening in local.
Google always provides the example#example.com user when you try to simulate the log in, it happens basically in all the services, like when you provide a log in through your Google Account in any web site (for instance using GWT and App Engine).
What can be different in your site if you test with your real user or you consider example#example.com user as your user?
In your endpoint API you need this
ApiMethod ( name="YourEndPointName", path="yourPath",
clientIds={"YourId.apps.googleusercontent.com"},
scopes = { "https://www.googleapis.com/auth/userinfo.profile" })
Then in the called method, you will have a User object from the GAPI.
Use this to get the actual email from the google User object like this
public myEndPointMethod( Foo foo, User user ){
email = user.getEmail();
}
I replaced the Oauth2 user (example#example.com) with user from UserFactory and it works fine. I use this method to validate user for all API authenticated API requests.
public static User isAuthenticated(User user) throws OAuthRequestException{
if(user == null){
throw new OAuthRequestException("Please login before making requests");
}
if(SystemProperty.environment.value() ==
SystemProperty.Environment.Value.Development && "example#example.com".equalsIgnoreCase(user.getEmail()) ) {
//Replace the user from the user factory here.
user = UserServiceFactory.getUserService().getCurrentUser();
}
return user;
}
Using the go runtime I have resorted to this function to obtain a User that is functional on both the dev server and production:
func currentUser(c context.Context) *user.User {
const scope = "https://www.googleapis.com/auth/userinfo.email"
const devClient = "123456789.apps.googleusercontent.com"
allowedClients := map[string]bool{
"client-id-here.apps.googleusercontent.com": true,
devClient: true, // dev server
}
usr, err := user.CurrentOAuth(c, scope)
if err != nil {
log.Printf("Warning: Could not get current user: %s", err)
return nil
}
if !allowedClients[usr.ClientID] {
log.Printf("Warning: Unauthorized client connecting with the server: %s", usr.ClientID)
return nil
}
if (usr.ClientID == devClient) {
usr = user.Current(c) // replace with a more interesting user for dev server
}
return usr
}
This will use the dev server login information entered using http://localhost:8080/_ah/login
It's not possible.
I use another endpoint to replace user_id in current session.

Resources