Google Smart home report state error 403 - google-api

I'm reporting states for devices using http post with a jwt generated using service account. Below is the payload for jwt
{
"iss": "<service-account-email>",
"scope": "https://www.googleapis.com/auth/homegraph",
"aud": "https://accounts.google.com/o/oauth2/token",
"iat": <current-time>,
"exp": <current-time-plus-one-hour>
}
after this I sign the jwt using the private key for my service account using the python library google.auth.crypt.RSASigner.from_service_account_file(path)
and generate the jwt token. I am further using this token to obtain the access token from https://accounts.google.com/o/oauth/token, which is also successful.
After obtaining the access token I am making a post request to
https://homegraph.googleapis.com/v1/devices:reportStateAndNotification?key=api_key
with headers
{"Authorization": "Bearer <token>", "X-GFE-SSL": "yes", "Content-Type": "application/json"}
and json data
{ "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", "agent_user_id": "1234", "payload": { "devices": { "states": { "1458765": { "on": true }, "4578964": { "on": true, "isLocked": true } } } } }
But this gives me
{'error': {'code': 403, 'message': 'The request is missing a valid API key.', 'status': 'PERMISSION_DENIED'}}
I followed the steps from
https://developers.google.com/actions/smarthome/report-state
is there anything i'm doing wrong? or am I missing any steps?
UPDATE:
I added the api key to the uri now it gives me another error in response
{'error': {'code': 403, 'message': 'The caller does not have permission', 'status': 'PERMISSION_DENIED'}}
how do I resolve this issue?

In order to report the state to the Home Graph you have to:
Create a Service Account for the token creation from your SmartHomeApp project:
Go to APIs & Services => Click on Credentials
Click on Create credentials => Click on Service account key
Fill with your data creating a new New service account => Select the role Service Accounts > Service Account Token Creator
Download the JSON file with your keys and certificate
Get a valid signed JWT token:
credentials = service_account.Credentials.from_service_account_file(service_account_file, scopes="https://www.googleapis.com/auth/homegraph")
now = int(time.time())
expires = now + 3600 # One hour in seconds
payload = {
'iat': now,
'exp': expires,
'aud': "https://accounts.google.com/o/oauth2/token",
'scope': SCOPE,
'iss': credentials.service_account_email
}
signed_jwt = google.auth.jwt.encode(credentials.signer, payload)
Obtain a valid Access token:
headers = {"Authorization": "Bearer {}".format(signed_jwt.decode("utf-8")), "Content-Type": "application/x-www-form-urlencoded"}
data = {"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion": signed_jwt}
access_token = requests.post("https://accounts.google.com/o/oauth2/token", data=data, headers=headers).get("access_token")
Send the reported states:
headers = {"Authorization": "Bearer {}".format(access_token), "X-GFE-SSL": "yes"}
data = {"requestId": request_id, "agent_user_id": agent_user_id, "payload": {"devices": {"states": states}}}
requests.post("https://homegraph.googleapis.com/v1/devices:reportStateAndNotification", data=json.dumps(data), headers=headers)
NOTE: in order to work these snippets require to import google.auth.jwt, from google.oauth2 import service_accountand import requests from google-auth, google-auth-httplib2 and requests packages.

Related

Django Rest Framework JWT Auth

I have an issue posting/getting any requests after JWT login. (I am using the djangorestframework-jwt library) The user succesfully logs in, the app returns the json token but when I use this token for the next requests I get
{
"detail": "Authentication credentials were not provided."
}
settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
JWT_AUTH = {
'JWT_ALLOW_REFRESH': True,
'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=1),
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
Login View
def post(self, request, format=None):
if not request.data:
return Response({'Error': "Please provide username/password"}, status=status.HTTP_400_BAD_REQUEST)
username = request.data['username']
password = request.data['password']
try:
user = User.objects.get(email=username, password=password)
except User.DoesNotExist:
return Response({'Error': "Invalid username/password"}, status=status.HTTP_400_BAD_REQUEST)
if user:
payload = {
'id': user.id,
'email': user.email,
'first_name': user.first_name
}
jwt_token = jwt.encode(payload, "SECRET_KEY") # to be changed
return Response({'token': jwt_token}, status=status.HTTP_200_OK)
and then every other view contains
authentication_class = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
I have tested both with postman and curl but I get the same error. Not sure if there is an error with the header format or if there is smth else I'm missing. Any help would be greatly appreciated!
EDIT:
I have changed my settings to
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
but now I get that
{
"detail": "Error decoding signature."
}
EDIT: I think the issue is that jwt_token = jwt.encode(payload, 'SECRET_KEY') might return a token that is not recognised...if i use the token generated by obtain_jwt_token then i can query any endpoint. Could anyone explain this?
EDIT: so I changed to jwt_token = jwt_encode_handler(payload) and the settings file contains the JWT_SECRET_KEY (when i verify the token i receive after login on jwt, it is indeed the right token with the right payload and secret) but it's still not recognised "detail": "Invalid signature."
I managed to solve my problem. When authenticating a user I was checking against a custom user table that I had created earlier, which was different from the django's auth_user table. I changed django's settings to use my custom users table and then using the token from the authentication worked for the other requests as well.
AUTH_USER_MODEL = 'main.User'
The problem is you encode the JWT by using "SECRET KEY" and decode using another key. The default secret key used by jwt is settings.SECRET_KEY. But when you create a custom jwt token you uses a string as secret "SECRET_KEY". But you use default jwt verification which automatically uses settings.SECRET_KEY.
So add 'JWT_SECRET_KEY': settings.SECRET_KEY, in to your JWT settings and use this key for encode and decode.
Ex:
jwt_token = jwt.encode(payload, settings.SECRET_KEY)
or you can override the jwt verification and create your own.

Directory API returns 403 forbidden

i'm trying to use the directory API by using a service account that I've enabled his Domain-wide Delegation and off course also authorized this service from the admin console using the json file credetials downloaded when creating the service account.
I've also enabled the admin sdk from the google developers console
and i'm using the googleapi library
in order to get access token for the service account
import * as google from 'googleapis';//google sdk for api+Oauth
//creating JWT auth client for service account:
const jwtClient = new google.auth.JWT(
client_email,
null,
private_key,
scope, // (included the "https://www.googleapis.com/auth/admin.directory.user" scope)
null,
);
let tokens
jwtClient.authorize( (err, tokens)=> {
if (err) {
console.log(err);
return;
} else {
tokens = tokens
}
// Make an authorized request to list of domain users.
let url = `https://www.googleapis.com/admin/directory/v1/users?domain=mydomain`;
let headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${tokens.access_token}`
}
request.get({
url: url,
json: true,
headers: headers,
}, (err, res, body: {}) => {
this.handleResponse(err, res, body, resolve, reject);
});
});
})
}
the tokens are retrived succefully but when sending the users list request i'm receiving 403 "Not Authorized to access this resource/api"
on the other hand when using the google explorer api with the same params it work's
Looks like you didn't provide a subject when constructing the JWT object (in the line after scope, in your code). You should provide the email address of an admin there, so that you get a token impersonating that admin. Otherwise, you're acting as the service account itself, that doesn't have access to your domain's directory (and can never have access - that's why you must impersonate an admin).

How to properly set Authorization to consume Wordpress Rest API (Oauth1) with Nativescript[2.5.4]?

I'm trying to consume the WP Rest API with Nativescript.
The WP API and the Authorization with OAUTH1 is already well setup and tested with POSTMAN.
Nativescript Login function to consume rest is already setup too and work without OAUTH.
Now I'm trying to Login with OAUTH here the code :
authorizationString = 'OAuth oauth_consumer_key="T2yXcbN28Ufj",
oauth_token="AmEVr5oSNmbKyZKccFjtmnSk",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1492267438",
oauth_nonce="rGFJG2",
oauth_version="1.0",
oauth_signature="Ru%2BaSvsZn2liesd2ENy8GeNsdHY%3D"';
login(user: User){
console.log("Try to login User : " + user.email);
let headers = new Headers({"Authorization": authorizationString});
let body = JSON.stringify({
username: user.username,
email: user.email,
password: user.password
});
headers.append("Content-Type", this.contentType);
return this.http.post(
this.api,
body,
{headers: headers}
)
.map( response => response.json() )
.do(data => {
//Do work!!
})
.catch(this.handleErrors);
}
But i got an error, this error means the autorization is not well formatted or sent :
"data": {
"code": "rest_cannot_access",
"message": "Only authenticated users can access the REST API.",
"data": {
"status": 401
}
}
How to properly use oauth1 with Nativescript?
I just switched to OAUTH2 by using a opensource plugin :
https://github.com/wlcdesigns/WP-OAuth2-Server-Client
and it's more easy to use with the authorization : Bearer

Okta Authentication works but Get User by Id gives Invalid Token Provided

I have a Django app that authenticates using Okta:
headers = {
'Authorization': 'SSWS {}'.format(<okta api token>),
'Accept': 'application/json',
'Content-Type': 'application/json'
}
authentication_payload = {
'username': <username>,
'password': <password>
}
response = requests.post(
<okta auth endpoint>,
headers=headers,
data=json.dumps(authentication_payload)
)
This works successfully. From the response content I am able to get the User Id:
content = json.loads(r.content.decode('utf-8'))
okta_user_id = content['_embedded']['user']['id']
I then use the okta_user_id to create the endpoint to get the okta user by id:
okta_user_endpoint = https://<org>.okta.com/api/v1/users/<okta_user_id>
I then use the same headers from the authentication call, with the same api token, and try to get the user by id:
user_response = requests.get(
okta_user_endpoint,
headers=headers
)
But this is unsuccessful. I get a 401 error with the following content:
{
"errorCode":"E0000011",
"errorSummary":"Invalid token provided",
"errorLink":"E0000011",
"errorCauses":[]
}
Seems straight forward with an invalid token, but if the token is invalid how am I able to successfully make the authentication call? And if the token if valid for the authentication call why is it not working to get the user by id?
Okta recently changed the way that the /authn endpoint works. The /authn endpoint no longer requires an authentication token. This was done in order to support single-page applications.
It looks like your application will need to be able to fetch user information on an arbitrary user. In that case, using an Okta API token makes sense.
However, if you were making that call from a single-page application, you would want to make a request to the /users/me API endpoint.

Google Calender Oauth with offline access

I am trying to access the Google Calendar API using VBScript and Oauth offline access. I managed to get a refresh token and an access token, and now I want to get to the step where I actually use the API, using the following code:
sUrl="https://www.googleapis.com/calendar/v3/users/me/calendarList"
sRequest = "access_token="&accesstoken
response=HTTPPost (sUrl, sRequest)
Function HTTPPost(sUrl, sRequest)
set oHTTP = CreateObject("MSXML2.ServerXMLHTTP")
oHTTP.open "POST", sUrl,false
oHTTP.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
oHTTP.setRequestHeader "Content-Length", Len(sRequest)
oHTTP.send sRequest
HTTPPost = oHTTP.responseText
End Function
The response I get is :
{
"error": {
"errors": [
{
"domain": "global",
"reason": "authError",
"message": "Invalid Credentials",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Invalid Credentials"
}
}
This is even though the access token is valid - If I change sURL to https://www.googleapis.com/oauth2/v1/tokeninfo and run the script I get the following response showing a valid token:
{
"issued_to": "...",
"audience": "...,
"scope": "https://www.googleapis.com/auth/calendar",
"expires_in": 2568,
"access_type": "offline"
}
And also if I paste the URL https://www.googleapis.com/calendar/v3/users/me/calendarList?access_token=... into a browser I get a valid response from Google listing the user's calendars
401: Invalid Credentials
Invalid authorization header. The access token you're using is either
expired or invalid.
Access tokens are only good for 1 hour. After the hour is up the access token has expired and you need to use the refresh token to request a new one.
The raw code to get a new acesss token looks something like this. the key is grant_type=refresh_token this tells the authentication server you are requesting a new token.
https://accounts.google.com/o/oauth2/token
client_id={ClientId}.apps.googleusercontent.com&client_secret={ClientSecret}&refresh_token=1/ffYmfI0sjR54Ft9oupubLzrJhD1hZS5tWQcyAvNECCA&grant_type=refresh_token
Your code uses HTTP POST but use an HTTP GET request is required as specified here: https://developers.google.com/google-apps/calendar/v3/reference/calendarList/list. Also, you can provide the access token in the access_token parameter (and not in the oauth_token parameter as suggested in the other answer), but another option is to provide it in an Authorization header as in:
Authorization: Bearer <token>
As an example, a token that returns from:
curl -v https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=<token>`
the following introspection result:
{
"issued_to": "<...>.apps.googleusercontent.com",
"audience": "<...>.apps.googleusercontent.com",
"user_id": "<...>",
"scope": "<> https://www.googleapis.com/auth/calendar",
"expires_in": 3527,
"email": "<...>",
"verified_email": true,
"access_type": "offline"
}
can be used in against the Calendar API URL:
curl -v -H "Authorization: Bearer <>" https://www.googleapis.com/calendar/v3/users/me/calendarList
so that it gives me a list of JSON calendar data:
{
"kind": "calendar#calendarList",
"etag": "\"1420511621463000\"",
"nextSyncToken": "00001420511621463000",
"items": [
{
"kind": "calendar#calendarListEntry",
"etag": "\"1418127331044000\"",
...
I managed to figure this out - the calendarList API requires sending an empty body, therefore I change the code to:
set oHTTP = CreateObject("MSXML2.ServerXMLHTTP")
oHTTP.open "GET", sUrl&srequest,false
oHTTP.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
oHTTP.send
getcal = oHTTP.responseText

Resources