I am using signpost with google appengine (java) to send a status update to twitter.
OAuthConsumer consumer = new DefaultOAuthConsumer("AAA",
"BBB");
consumer.setTokenWithSecret("CCC", "DDD");
URL url = new URL("http://api.twitter.com/1.1/statuses/update.json?status=abc");
HttpURLConnection request;
request = (HttpURLConnection) url.openConnection();
consumer.sign(request);
request.setRequestMethod("POST");
request.connect();
InputStream response = request.getInputStream();
int a = response.read();
while (a!=-1){
resp.getOutputStream().write(a);
a = response.read();
}
You can see hear I am putting the post parameters in the url like a get request, someone else mentioned this worked for them, I have also tried to put them in the body of the message like you are supposed to do. But I always get {"errors":[{"message":"Could not authenticate you","code":32}]}. My tokens and secret tokens are all correct, but if they weren't I would get a different error of {"errors":[{"message":"Invalid or expired token","code":89}]}.
I think signpost isn't doing the oauth process on the whole body (including the parameters) so it is giving the error.
Any ideas?
Turns out signpost is not a great library for app-engine. A few bugs in it prevent it from working without certin errors. Scribe is a better Oauth tool to be used with Appengine Java.
Related
I am working on a web application with ReactJs frontend and Django backend. In this app, I will need to send calender notifications to the users and overall need a user authentication feature for which I planned to use google oauth. From react, I am able to log in the user and get the access tokens but since they expire in an hour, I planned to get the authorization code and use it to get the refresh/access tokens from the backend whenever a user logs in/needs to send API request. The issue is that I am not able to find any good resource on how to get refresh tokens from the backend given I have the authorization code. Most of the HTTP based methods I have found are very outdated and I have searched some of the google documentation but have not found anything worthwhile. Since I could not find any package that would handle this, I have been trying to send POST request to the URL mentioned here (https://developers.google.com/identity/protocols/oauth2/web-server#python_1) but only get a 400 response. Below are 2 methods I have tried.
to_send={'code':user_data['code'], 'client_id': cl_id , 'client_secret': cl_secret,
'redirect_uri':'http://localhost:3000/', 'grant_type':'authorization_code'}
test=requests.post('https://oauth2.googleapis.com/token', data=to_send)
print(test)
credentials = service_account.Credentials.from_service_account_file('path/key.json')
scoped_credentials = credentials.with_scopes(['https://www.googleapis.com/auth/drive.metadata.readonly'])
authed_session = AuthorizedSession(scoped_credentials)
response = authed_session.request('POST', 'https://oauth2.googleapis.com/token', data = to_send)
print(response)
Ive also tried other things like creating a flow object.
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( JSON_PATH, scopes=API_SCOPE)
flow.redirect_uri = REDIRECT_URL
flow.fetch_token(code=user_data['code'])
credentials = flow.credentials
where user_data['code'] is the authorization token and I get the error
oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant) Bad Request
I am trying to migrate from Channel API in GAE to firebase. To do this, first, I am trying to setup a local development environment. I cloned the sample app from GAE samples. (Link to sample)
When I ran this, I get the following error, when the web client is trying to authenticate with the firebase DB.The error is in the console.
Screenshot of the error
i.e token authentication failed.Clearly, this points to the fact that generated JWT is not correct.
To be sure, I have done the following:
Created a service account in Google cloud console.
Downloaded the JSON and pointed to this JSON in the environment variable "GOOGLE_APPLICATION_CREDENTIALS"
Put the code snipped from the firebase into WEB-INF/view/firebase_config.jspf file
The code to generate the token is as follows ( from FirebaseChannel.java )
public String createFirebaseToken(Game game, String userId) {
final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
final BaseEncoding base64 = BaseEncoding.base64();
String header = base64.encode("{\"typ\":\"JWT\",\"alg\":\"RS256\"}".getBytes());
// Construct the claim
String channelKey = game.getChannelKey(userId);
String clientEmail = appIdentity.getServiceAccountName();
System.out.println(clientEmail);
long epochTime = System.currentTimeMillis() / 1000;
long expire = epochTime + 60 * 60; // an hour from now
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("iss", clientEmail);
claims.put("sub", clientEmail);
claims.put("aud", IDENTITY_ENDPOINT);
claims.put("uid", channelKey);
claims.put("iat", epochTime);
claims.put("exp", expire);
System.out.println(claims);
String payload = base64.encode(new Gson().toJson(claims).getBytes());
String toSign = String.format("%s.%s", header, payload);
AppIdentityService.SigningResult result =
appIdentity.signForApp(toSign.getBytes());
return String.format("%s.%s", toSign,
base64.encode(result.getSignature()));
}
Instead of Step #2, have also tried 'gcloud auth application-default login' and then running after unsetting the environment variable - resulting in the same issue
Appreciate any help in this regard.
After further research, I found out additional info which may help others facing the same issue. I did the following to be able to run the sample locally.
Its important to ensure that the service account in appengine has the permissions to access resources. Chose "Editor" as the role for permissions (other levels may also work, but I chose this) for the default appengine service account. This will ensure that you do not run into "Unauthorized" error.
My application was enabled for domain authentication and did not use Google authentication. The sample however has been created for Google authentication. I had to make changes to the sample code to send the userId as part of the URL and removed the code that referred to UserServiceFactory.
The console error did show up even now, but the application worked fine. This error probably can be ignored. In the deployed environment, however, this error does not show up.
I would appreciate if Google/Firebase engineers update this answer with official responses, or update the sample documentation appropriately as currently this information does not seem to be mentioned anywhere.
I'm building a RESTful API with the Restlet framework and need it to work with cross domain calls (CORS) as well as basic authentication.
At the moment I'm using the CorsFilter which does the job of making my webservice support CORS requests. But, when I try to use this with a simple ChallengeAuthenticator with HTTP Basic Authentication it won't work as I want it to (from a web site).
When I access the webservice directly via Chrome it works as intended, but when I try it in a small web application written in angularjs (jquery/javascript) and try to access the webservice it does not.
Basically what happens is that when a OPTIONS request is sent to my webservice it will not respond with the headers: 'Access-Control-Allow-Origin', 'Access-Control-Allow-Credentials', etc. as it should. Instead it is sending a respond with HTTP status code 401 saying that the authentication failed.. Is this because the authenticator is overriding the CorsFilter somehow?
My createInboundRoot method can be seen below.
#Override
public Restlet createInboundRoot() {
ChallengeAuthenticator authenticator = createAuthenticator();
RoleAuthorizer authorizer = createRoleAuthorizer();
Router router = new Router(getContext());
router.attach("/items", ItemsServerResource.class);
router.attach("/items/", ItemsServerResource.class);
Router baseRouter = new Router(getContext());
authorizer.setNext(ItemServerResource.class);
authenticator.setNext(baseRouter);
baseRouter.attach("/items/{itemID}", authorizer);
baseRouter.attach("", router);
// router.attach("/items/{itemID}", ItemServerResource.class);
CorsFilter corsFilter = new CorsFilter(getContext());
corsFilter.setNext(authenticator);
corsFilter.setAllowedOrigins(new HashSet(Arrays.asList("*")));
corsFilter.setAllowedCredentials(true);
return corsFilter;
}
(The authorizer and authenticator code is taken from the "official" restlet guide for authorization and authentication)
I've tried alot of changes to my code but none which given me any luck. But I noticed that when setting the argument "optional" in ChallengeAuthenticator to true (which "Indicates if the authentication success is optional") the CorsFilter does its job, but obviously the ChallengeAuthenticator does not care about authenticating the client and lets anything use the protected resources..
Has anyone had a similar problem? Or have you solved this (CORS + Authentication in Restlet) in any other way?
Thanks in advance!
I think that it's a bug of the Restlet CORS filter. As a matter of fact, the filter uses the method afterHandle to set the CORS headers. See the source code: https://github.com/restlet/restlet-framework-java/blob/4e8f0414b4f5ea733fcc30dd19944fd1e104bf74/modules/org.restlet/src/org/restlet/engine/application/CorsFilter.java#L119.
This means that the CORS processing is done after executing the whole processing chain (authentication, ...). So if your authentication failed, you will have a status code 401. It's actually the case since CORS preflighted requests don't send authentication hints.
For more details about using CORS with Restlet, you could have a look at this link: https://templth.wordpress.com/2014/11/12/understanding-and-using-cors/. This can provide you a workaround until this bug was fixed in Restlet itself.
I opened an issue in Github for your problem: https://github.com/restlet/restlet-framework-java/issues/1019.
Hope it helps,
Thierry
The CorsService (in 2.3.1 coming tomorrow) contains also a skippingResourceForCorsOptions property, that answers directly the Options request without transmitting the request to the underlying filters and server resources.
I'm working with this stack:
Core API RESTful with Rails 4 and Devise 3.2
Another app/stance with Backbone
I have read many articles, manuals, stackoverflow topics, google random results, blogs, etc, but are all very deprecated.
Using a practical approach (tl;dr here) I just need get a real session between Devise 3 and Backbone in different server stances and holding it, like two separate projects. Remote login, you know.
I'm really stuck with that so I would greatly appreciate your suggestions.
Thank you guys.
Personally I have the same situation in my project with Angular instead of Backbone as a front-end and Rails 4 API with Devise. I will try to sum things up for you in the assumption that I got your question right.
To work correctly with the sessions in your scenario you need to be sure that:
Browsers handle communication correctly (i.e. they don't mess with your data because requests do not comply with CORS policies)
and, your requests get through Rails CSRF protection
Please, read this article about CORS. If you are not familiar with CORS the article should provide necessary background for my answer. Some info about CSRF protection is here
Here is your scenario step-by-step:
Backbone.js sends GET request such as http://yourserver/signin
Rails Server sends session cookie that will be stored in the browser and CSRF token, which can be stored somewhere within your Backbone application.
Backbone.js sends POST request with user credentials (name, password) and CSRF token in headers and current unauthorized session in cookies. It is crucial that request contains session information. Otherwise it will be granted different CSRF token on Rails side and you will get WARNING: Can't verify CSRF token authenticity message.
Backbone.js gets authorized session back if the credentials are correct.
Here is what can be done to get it working:
Rails backend should respond correctly to requests from front-end. Which means it should:
Respond to OPTIONS requests (preflight requests)
Send correct CORS headers
Able to communicate CSRF token with the front-end
Front end should:
Able to send requests with credentials
Obtain and use correct CSRF token
The simplest way to teach your Rails back-end to respond to CORS requests is to use
rack-cors gem. This will also provide correct CORS headers.
config.middleware.insert_before Warden::Manager, Rack::Cors do
allow do
origins '*' # it's highly recommended to specify the correct origin
resource '*',
:headers => :any,
:methods => [:get, :post, :options], # 'options' is really important
# for preflight requests
:expose => ['X-CSRF-Token'] #allows usage of token on the front-end
end
end
Last thing on a backend side is to provide CSRF token. Custom Devise controller should handle this task perfectly.
class SessionsController < Devise::SessionsController
after_action :set_csrf_header, only: [:new, :create, :destroy]
#...
protected
def set_csrf_header
response.headers['X-CSRF-Token'] = form_authenticity_token
end
end
Note that you need CSRF token when you send first GET request (new), when you submit credentials through POST request (create) and when you sign out of your application by sending DELETE request (destroy). If you don't send CSRF token on sign out you won't be able to sign in without reloading the page.
And somewhere in config/routes.rb don't forget to specify that you are now using custom controller:
/config/routes.rb
devise_for :users, :controllers => {:sessions => "sessions"}
Now, to the front-end. Please, have a look at this script that overrides standard Backbone.sync and handles communication with Rails server.
It is almost good with couple of corrections needed:
beforeSend: function( xhr ) {
if (!options.noCSRF) {
// we dont have csrf-token in the document anymore
//var token = $('meta[name="csrf-token"]').attr('content');
// New Line #1
// we will get CSRF token from your application.
// See below for how it gets there.
var token = YourAppName.csrfToken;
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
// New Line #2
// this will include session information in the requests
xhr.withCredentials = true;
}
//..some code omitted
//................
// Trigger the sync end event
var complete = options.complete;
params.complete = function(jqXHR, textStatus) {
// New Lines #3,4
// If response includes CSRF token we need to remember it
var token = jqXHR.getResponseHeader('X-CSRF-Token')
if (token) YourAppName.csrfToken = token;
model.trigger('sync:end');
if (complete) complete(jqXHR, textStatus);
};
}
I'm not sure this qualifies as a complete answer to your question, but at least it is something to start from. It might not be the best way, but it is the way. Let me know if you have any questions.
I am trying to call Directory APIs from my GAE application in JSP. The application is already running on AppSpot. I'd like to retrieve all organizational units that a user belong to. Unfortunately I get 404 code while making the request and I have no idea why.
ArrayList<String> scopes = new ArrayList<String>();
scopes.add("https://www.googleapis.com/auth/admin.directory.user");
AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
AppIdentityService.GetAccessTokenResult accessToken = appIdentity.getAccessToken(scopes);
URL url = new URL("https://www.googleapis.com/admin/directory/v1/users/myuser#mygoogleappsdomain.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Authorization", "OAuth " + accessToken.getAccessToken());
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
out.print("OK");
}
else {
out.print(connection.getResponseCode());
}
As you can imagine this code snippet prints 404. Basically I am following an example that is available on the GAE documentation. What am i doing wrong? Thank you.
EDIT: If I just call one of the following URLs I get a 403 status code. Is there anything wrong with my OAuth authentication?
https://www.googleapis.com/admin/directory/v1/users?domain=mydomain
https://www.googleapis.com/admin/directory/v1/users
Your code only provides app identity. You will also need to get authorisation from user to get access to their directory info.
If you follow the link you provided you get to the point that states: All requests to the Directory API must be authorized by an authenticated user.
So you will need to send your users through a OAuth 2 authentication + authorization procedure, where you will ask them for Directory API access. If you only need a read-only access to list of users then you will need to request a https://www.googleapis.com/auth/admin.directory.user.readonly scope.