I'm trying to authorise the API for Buffer (the social media scheduling app). I'm following the instructions here https://buffer.com/developers/api/oauth
The first step of the authorisation, redirects the user to an authorisation page using a request of the type:
GET https://bufferapp.com/oauth2/authorize?
client_id=...&
redirect_uri=...&
response_type=code
Once authorise the page redirects to the redirect_uri with the authorisation code as a query parameter:
http://example.com/back?code=1/mWot20jTwojsd00jFlaaR45
That code is then to be used in a POST request to obtain an access token:
POST https://api.bufferapp.com/1/oauth2/token.json
POST Data
client_id=...&
client_secret=...&
redirect_uri=...&
code=...&
grant_type=authorization_code
However, the authorisation code has a 30sec valid life.
When I do the authorisation manually and then a POST request with the auth_code I receive like so:
my_data = {
'client_id': my_client_id,
'client_secret': my_client_secret,
'redirect_uri': my_redirect_uri,
'code': auth_code,
'grant_type': 'authorization_code',
}
token = requests.post('https://api.bufferapp.com/1/oauth2/token.json', data=my_data)
I get an "invalid grant" error.
I looked up the error in relation to other APIs (Buffer API doesn't seem to be used that much) and one cause for it might be an expired auth_code. Which wouldn't be surprising given the short shelf life.
What's a good way to automate the authorisation bit to avoid expiration of the auth_code?
Or is it something else I'm doing wrong here?
Many thanks in advance for your help
Buffer is an OAuth 2.0 provider.
You have to use a OAuth 2 Workflow, read about
Requests-OAuthlib: OAuth 2 Workflow
The following sections provide some example code that demonstrates some of the possible OAuth2 flows you can use with requests-oauthlib. We provide four examples: one for each of the grant types defined by the OAuth2 RFC. These grant types (or workflows) are the Authorization Code Grant (or Web Application Flow), the Implicit Grant (or Mobile Application Flow), the Resource Owner Password Credentials Grant (or, more succinctly, the Legacy Application Flow), and the Client Credentials Grant (or Backend Application Flow).
Related
I am trying just to access my own LinkedIn profile data.
Steps followed:
Setup a new application
Get CLIENT_ID and CLIENT_SECRET
But, where to get the USER_TOKEN, USER_SECRET as mentioned here "You can simply use the 4 credentials that are provided to you in your LinkedIn application as part of an OAuth 1.0a flow and immediately access your data."? I've tried without it, and it doesn't work.
The code is given in under the Developer Authentication headline here.
The OAuth 1.0a flow has been deprecated by LinkedIn (https://engineering.linkedin.com/blog/2018/12/developer-program-updates), so there is no way to get a user token and secret without going through the OAuth 2 flow described here:
https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?context=linkedin/compliance/context
I'm trying to use the Lyft rides python API to access Lyft data. Specifically, I'm trying to access the ride estimate endpoint .
from lyft_rides.auth import ClientCredentialGrant
from lyft_rides.session import Session
from lyft_rides.client import LyftRidesClient
auth_flow = ClientCredentialGrant(client_id=MY_ID, client_secret=MY_SECRET, scopes="public")
session = auth_flow.get_session()
client = LyftRidesClient(session)
response = client.get_cost_estimates(start_latitude=start_lat, start_longitude=start_long, end_latitude=end_lat, end_longitude=end_long)
However, the surge rate in the response data is always 0, even during surge hours, and I've diagnosed that it's because I'm not utilizing the 3-legged authentication.
From the lyft developer docs,
3-Legged flow for accessing user-specific endpoints.
To make ride
requests or otherwise access user data, the user must grant you
access. Users who don't have a Lyft account will be prompted to create
a new account if they are directed through the following flow.
From the python docs,
Authorization
If you need access to a Lyft user’s account in order to make requests
on their behalf, you will go through a “3-legged” flow. In this case,
you will need the user to grant access to your application through the
OAuth 2.0 Authorization Code flow. See Lyft API docs.
The Authorization Code flow is a two-step authorization process. The
first step is having the user authorize your app and the second
involves requesting an OAuth 2.0 access token from Lyft. This process
is mandatory if you want to take actions on behalf of a user or access
their information.
from lyft_rides.auth import AuthorizationCodeGrant
auth_flow = AuthorizationCodeGrant(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_PERMISSION_SCOPES,
)
auth_url = auth_flow.get_authorization_url()
Navigate the user to the auth_url where they can grant access to your
application. After, they will be redirected to a redirect_url with the
format REDIRECT_URL?code=UNIQUE_AUTH_CODE. Use this redirect_url to
create a session and start LyftRidesClient.
session = auth_flow.get_session(redirect_url)
client = LyftRidesClient(session)
credentials = session.oauth2credential
Keep credentials information in a secure data store and reuse them to
make API calls on behalf of your user. The SDK will handle the token
refresh for you automatically when it makes API requests with a
LyftRidesClient.
Question
I'm trying to automate the python request within a script. Given that the 3rd leg of the authentication requires manually visiting a url and obtaining a code, is it possible to do this through a script?
[Full Disclosure: I'm one of Lyft's Developer Advocates]
The only way to get that data is by requesting therides.request scope is through the 3-legged OAuth flow (sorry about that). However, you only need to request this external authorization once if you ask for the offline scope as part of the initial authorization. If you have that scope requested initially, you can use refresh_tokens as outlined here and not get prompted for the external URL:
https://developer.lyft.com/docs/authentication#section-step-5-refreshing-the-access-token
If you're only using this script locally I'd recommend going through this authorization once and then building in refresh token logic into your script if your token has expired. Hope that helps!
I'm trying to connect to Mojio REST api authenticated with OAuth2 with Python server code. Here's the 'manual' from Mojio: https://docs.moj.io/#/document/view/doc_oauth
I'm doing the second option there 'Authorization Code'. I don't understand what is the 'redirect_uri' parametr for. What should I pass there when I'm developing on localhost?
Thanks
You might want to consider using the "Resource Owner Password Credentials Grant" vs normal UI based oAuth workflows. This allows you to capture the username and password in your application and authenticate with those credentials instead of a UI (which can be challenging if you are on a server). Of course this depends on your specific requirements.
To login, via "Resource Owner" flow...
HTTP POST
Uri:
https://accounts.moj.io/oauth2/token
Body:
grant_type=password&username=USERNAME&password=PASSWORD&redirect_uri=REDIRECT_URI&client_id=CLIENTID&client_secret=CLIENTSECRET&scope=SCOPE
Content-Type:
application/x-www-form-urlencoded
This token you get back will be short lived, I would recommend getting a refresh token (longer lifetime):
To get a refresh token:
HTTP POST
Uri:
https://accounts.moj.io/oauth2/token
Body:
grant_type=refresh_token&refresh_token=API_TOKEN_YOU_GOT_FROM_LOGIN&redirect_uri=REDIRECT_URI&client_id=CLIENTID&client_secret=CLIENTSECRET
Content-Type:
application/x-www-form-urlencoded
I'm trying to setup endpoints api (with google app engine, python), but I'm having some trouble getting user profile info. API is working, I can create entities through API Explorer on my localhost.
My goal is to allow user to register for my app by providing just an email, and authorizing the app to get the reset of the info from their profile. I have this endpoints method:
#User.method(http_method="POST",
auth_level=endpoints.AUTH_LEVEL.REQUIRED,
allowed_client_ids=[
endpoints.API_EXPLORER_CLIENT_ID
],
scopes=[
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/plus.me',
],
user_required=True,
request_fields=('email',),
response_fields=('id',),
name="register",
path="users")
def UserRegister(self, instance):
logging.info(os.getenv( 'HTTP_AUTHORIZATION' ))
# 'Beared __TOKEN__'
logging.info(endpoints.users_id_token._get_token(None))
# '__TOKEN__'
instance.put()
return instance
This works fine, I receive authorization token and user is created in datastore, but I can't figure out how to get the profile info. If I enter the token in OAuth2 API (through API Explorer):
POST https://www.googleapis.com/oauth2/v2/tokeninfo?access_token=__TOKEN__
I get token info with some data I need { "user_id": "__ID__", "verified_email": true, ...}, and if I use user_id in +API:
GET https://www.googleapis.com/plus/v1/people/__ID__
I can get the rest of the data I need (name, image, etc).
What do I need to do to achieve this in my UserRegister() method? I'd prefer to return just entity ID and do the rest of registration asynchronously, but that's another issue, I'll figure it out (; Just need some guidance how to call other endpoints from my code...
EDIT:
I've managed to figure out how to call other APIs (code on Gist), now only have one issue with Plus API:
I did some queries and eventually got anonymous quota error. Then I added key parameter and set it to WEB_CLIENT_ID or SERVICE_ACCOUNT:
WEB_CLIENT_ID is OAuth2 Client ID (type: Web Application) from console.developers.google.com/apis/credentials,
SERVICE_ACCOUNT is default App Engine service account - MY_APP#appspot.gserviceaccount.com...
and now I'm getting following error:
HttpError: <HttpError 400 when requesting https://www.googleapis.com/plus/v1/people/__VALID_USER_ID__?key=__WEB_CLIENT_ID__or__SERVICE_ACCOUNT__&alt=json returned "Bad Request">
When I use +API explorer I get results as expected:
REQUEST:
https://www.googleapis.com/plus/v1/people/__VALID_USER_ID__?key={YOUR_API_KEY}
RESPONSE:
200 OK + json data for user...
Anyone knows why is this happening?
Why am I getting BadRequest response?
Problem with BadRequest was that I didn't send authorization token... I did try to send it as access_token, but seams like +api docs are outdated - it should be oauth_token. When I included this parameter issue was resolved:
build('plus', 'v1').people().get(userId=user_id, key=SERVICE_ACCOUNT, oauth_token=token).execute()
HINT: Use http://localhost:8001/_ah/api/discovery/v1/apis/, and discoveryRestUrl property it has to see real properties of your API - this is where I found the answer.
oauth_token can be obtained like this:
token = os.getenv('HTTP_AUTHORIZATION').split(" ")[1]
# or like in my question:
token = endpoints.users_id_token._get_token(None)
I'd suggest HTTP_AUTHORIZATION variable, because users_id_token docs state that it's a:
Utility library for reading user information from an id_token.
This is an experimental library that can temporarily be used to extract
a user from an id_token. The functionality provided by this library
will be provided elsewhere in the future.
How to call other API Endpoints?
This is also an answer to my first question:
from googleapiclient.discovery import build
service = build('plus', 'v1')
request = service.people().get(userId=user_id, key=SERVICE_ACCOUNT, oauth_token=token)
response = request.execute()
data = dict(self.response.POST)
Code that worked for me is here.
NOTE: WEB_CLIENT_ID obtained from https://console.developers.google.com/apis/credentials (OAuth2 Client ID of type Web Application) will NOT work in this case. I had to use SERVICE_ACCOUNT - I didn't try to generate one through console, default service account I got from App Engine worked fine.
...things are much clearer now that I got this working. Hope it will help someone else (;
I'm trying to figure how to properly implement Google+ Sign In (https://developers.google.com/+/web/signin/) in my AngularJS app to secure the REST API that it depends on.
I have 2 components to my application: the AngularJS app and a Python Flask REST API. I want to keep the REST API stateless. I see that there is the "client-side flow" and the "hybrid server-side flow", but I'm not sure which one to use.
I was imagining this flow in my head:
1) User signs in through the AngularJS app and receives a token from Google
2) User passes the token along with every REST API request
3) The API server verifies the token with Google before processing the request and returns an error if the token is invalid
Would I just use the client-side flow then and have my server call some Google API to verify token it receives with every request?
I think satellizer would be a great solution.
Satellizer is a simple to use, end-to-end, token-based authentication module for AngularJS with built-in support for Google, Facebook, LinkedIn, Twitter authentication providers, plus Email and Password sign-in method. You are not limited to the sign-in options above, in fact you can add any OAuth 1.0 or OAuth 2.0 provider by passing provider-specific information during the configuration step.
Installation
bower install satellizer --save
Usage
"Google+" in your case
angular.module('MyApp', ['satellizer']).config(function($authProvider) {
$authProvider.google({
clientId: '631036554609-v5hm2amv4pvico3asfi97f54sc51ji4o.apps.googleusercontent.com'
});
});
Controller
angular.module('MyApp').controller('LoginCtrl', function($scope, $auth) {
$scope.authenticate = function(provider) {
$auth.authenticate(provider);
};
});
Template
<button ng-click="authenticate('google')">Sign in with Google</button>
Full Documentation you can find it here https://github.com/sahat/satellizer
and for server side "python" in your case you'll be able to see an example here https://github.com/sahat/satellizer/tree/master/examples/server/python