Gsuite Directory API 403 Error on API call or Grant Type Error on JWT Generation - jwt

Using Python and creating my own JWT using HTTP/Rest methodology, I simply can't get delegation to work.
On one hand, google JWT troubleshoot documentation says that ISS needs to be the same as the SUB (the service account).
However, on the server to server oauth2 documentation, it says that to impersonate an account, the sub needs to be the account I am attempting to impersonate in the claim.
Needless to say, despite enabling domain-wide delegation, adding the correct scopes, etc, I get nothing back but 403 when attempting to access the user domain utilizing the requests library in python with the following example:
> requests.get("https://www.googleapis.com/admin/directory/v1/users/useremail#/
> google.org",headers={'Authorization':f' Bearer {oauth2tokenhere}'})
Here is an example of my claim:
> claim = { "iss": 'serviceaccountemail',
> 'sub': 'impersonatedaccountemail',
> 'scope': 'https://www.googleapis.com/auth/admin.directory.user.readonly',
> 'exp': ((datetime.datetime.today() + datetime.timedelta(minutes=60)).timestamp()),
> 'iat': ((datetime.datetime.today()).timestamp()),
> 'aud': "https://oauth2.googleapis.com/token"
> }
The above claim will generate a generalized grant error (cute, but not helpful).
If I change the claim and ensure that the sub and the iss are the same, the oauth2token generates, but I get a 403 error when attempting to hit the API.
Here is the server to server oauth2 documentation stating the sub should be the
account the service account is attempting to impersonate.
https://developers.google.com/identity/protocols/OAuth2ServiceAccount
Here is the troubleshooting article outlining the ISS/Sub being the same (although cloud article is the closest relevant topic I could find)
https://cloud.google.com/endpoints/docs/openapi/troubleshoot-jwt
EDIT:
I am utilizing the service account information from the downloaded .json file that is downloaded when creating the service account file.
import json as j
import datetime
import jwt
import requests
#creates the claim, 'secret' (from the private key), and the kid, from the service account file, and returns these values in a tuple.
#the tuple can then be used to make dependable positional argument entries to the parameters of the createJWT function.
def create_claim_from_json(self,objpath,scope=["https://www.googleapis.com/auth/admin.directory.user.readonly" "https://www.googleapis.com/auth/admin.directory.user"]):
with open(f'{objpath}','r') as jobj:
data = j.load(jobj)
claim = {
"iss": str(data['client_id']),
"sub": str(data['client_id']),
"scope": str(scope),
"exp": ((datetime.datetime.today() + datetime.timedelta(minutes=59)).timestamp()),
"iat": ((datetime.datetime.today()).timestamp()),
"aud": "https://oauth2.googleapis.com/token"
}
private_key = data['private_key']
kid = {"kid": f"{data['private_key_id']}"}
return claim, private_key, kid
#assembles the JWT using the claim, secret (Private key from the Service account file), the kid value, and the documented RS256 alg.
#returns the completed JWT object back to be used to send to the oauth2 endpoint
#the JWT will be used in the function call retrieve_oauth2token.
def createJWT(self, claim, secret, kid, alg='RS256'):
encoded_jwt = (jwt.encode(claim, secret, alg, kid)).decode('UTF-8')
return encoded_jwt
#Using the JWT created in memory, sends the JWT to the googleapi oauth2 uri and returns a token
def retrieve_oauth2token(self, jwt):
oauth2 = requests.post(f'https://oauth2.googleapis.com/token?grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant- type%3Ajwt-bearer&assertion={jwt}')
oauth2=oauth2.json()
return oauth2 #['access_token'], oauth2['token_type']

The documentation has a clear overview, did you follow the steps as described in the addendum? I am missing some parts of your code. But you did not mention using a service account (json) key. And the documentation also show that you have to use the (delegated) service account as both iss and sub. Furthermore, you need to use a kid. This is how it is done:
payload = {
'iss': '123456-compute#developer.gserviceaccount.com',
'sub': '123456-compute#developer.gserviceaccount.com',
'aud': 'https://firestore.googleapis.com/',
'iat': time.time(),
'exp': iat + 3600
}
additional_headers = {'kid': PRIVATE_KEY_ID_FROM_JSON}
signed_jwt = jwt.encode(payload, PRIVATE_KEY_FROM_JSON, headers=additional_headers, algorithm='RS256')
url = "URL OF THE API TO CALL"
header = {'Authorization': f'Bearer {signed_jwt}'}
resp = requests.get(url, headers=header)
Note: you can find PRIVATE_KEY_FROM_JSON in the private_key_id field of your service account JSON credentials file.

Related

connect to REST endpoint using OAuth2

I am trying to explore different options to connect to a REST endpoint using Azure Data Factory. I have the below python code which does what I am looking for but not sure if Azure Data Factory offers something out of the box to connect to the api or a way to call a custom code.
Code:
import sys
import requests
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
import json
import logging
import time
logging.captureWarnings(True)
api_url = "https://webapi.com/api/v1/data"
client_id = 'client'
client_secret = 'secret'
client = BackendApplicationClient(client_id=client_id)
oauth = OAuth2Session(client=client)
token = oauth.fetch_token(token_url='https://webapi.com/connect/accesstoken', client_id=client_id, client_secret=client_secret)
client = OAuth2Session(client_id, token=token)
response = client.get(api_url)
data = response.json()
When I look at the REST linked service I don't see many authentication options
Could you please point to me on what activities to use to make OAuth2 working in Azure Data Factory
You would have to use a WebActivity to call using POST method and get the authentication token before getting data from API.
Here is an example.
First create an Web Activity.
Select your URL that would do the authentication and get the token.
Set Method to POST.
Create header > Name: Content-Type Value: application/x-www-form-urlencoded
Configure request body for HTTP request.
..
Format: grant_type=refresh_token&client_id={client_id}&client_secret=t0_0CxxxxxxxxOKyT8gWva3GPU0JxYhsQ-S1XfAIYaEYrpB&refresh_token={refresh_token}
Example: grant_type=refresh_token&client_id=HsdO3t5xxxxxxxxx0VBsbGYb&client_secret=t0_0CqU8oA5snIOKyT8gWxxxxxxxxxYhsQ-S1XfAIYaEYrpB&refresh_token={refresh_token
I have shown above for example, please replace with respective id and secret when you try.
As an output from this WebActivity, you would receive a JSON string. From which you can extract the access_token to further use in any request header from further activities (REST linked service) in the pipeline depending on your need.
You can get the access_token like below. I have assigned it to a variable for simplicity.
#activity('GetOauth2 token').output.access_token
Here is an example from official MS doc for Oauth authentication implementation for copying data.

How can I a Google Api restful endpoint using service key?

I'm using postman to memic a restful api call and trying to access google sheets API end point. When I try to access my endpoint it returns:
{
"error": {
"code": 403,
"message": "The request is missing a valid API key.",
"status": "PERMISSION_DENIED"
}
}
which is fair enough as I did not use my API key. I created a service account and got a json file, but I plan to access using a rest endpoint so need to pass token in header but I'm not sure how.
I looked at the json file and wasn't sure what to extract in order to pass it for my rest call.
Has anyone been able to do this successfully?
Before calling Google Services from Postman, you would need to re-create the flow for getting an access token form service account credentials :
build and encode the JWT payload from the data from credentials files (to populate aud, iss, sub, iat and exp)
request an access token using that JWT
make the request to the API using this access token
You can find a complete guide for this flow is located here: https://developers.google.com/identity/protocols/oauth2/service-account#authorizingrequests
Here is an example in python. You will need to install pycrypto and pyjwt to run this script :
import requests
import json
import jwt
import time
#for RS256 you may need this
#from jwt.contrib.algorithms.pycrypto import RSAAlgorithm
#jwt.register_algorithm('RS256', RSAAlgorithm(RSAAlgorithm.SHA256))
token_url = "https://oauth2.googleapis.com/token"
credentials_file_path = "./google.json"
#build and sign JWT
def build_jwt(config):
iat = int(time.time())
exp = iat + 3600
payload = {
'iss': config["client_email"],
'sub': config["client_email"],
'aud': token_url,
'iat': iat,
'exp': exp,
'scope': 'https://www.googleapis.com/auth/spreadsheets'
}
jwt_headers = {
'kid': config["private_key_id"],
"alg": 'RS256',
"typ": 'JWT'
}
signed_jwt = jwt.encode(
payload,
config["private_key"],
headers = jwt_headers,
algorithm = 'RS256'
)
return signed_jwt
with open(credentials_file_path) as conf_file:
config = json.load(conf_file)
# 1) build and sign JWT
signed_jwt = build_jwt(config)
# 2) get access token
r = requests.post(token_url, data= {
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": signed_jwt.decode("utf-8")
})
token = r.json()
print(f'token will expire in {token["expires_in"]} seconds')
at = token["access_token"]
print(at)
Note the value of the scope: https://www.googleapis.com/auth/spreadsheets
Probably, you can do all the above flow using Google API library depending on what
programming language you prefer
The script above will print the access token :
ya29.AHES67zeEn-RDg9CA5gGKMLKuG4uVB7W4O4WjNr-NBfY6Dtad4vbIZ
Then you can use it in Postman in Authorization header as Bearer {TOKEN}.
Or using curl :
curl "https://sheets.googleapis.com/v4/spreadsheets/$SPREADSHEET_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Note: you can find an example of using service account keys to call Google translate API here

Generate access token using JWT

I've been given access to an okta token endpoint. I would like to use this service to request a token. I was given a url, client id, client secret, scope and grant type. I can use postman to make a POST call to the url (/v1/token) and pass the above info (client id, client secret, scope and grant type) and I get an access token back.
I can easily make this call in java with RestTemplate or equivalent, but I would like to use an API that would manage the token for me.
I've found JJWT. All the examples I see out there show me how to create a JWT using JJWT. What I would like to do is to get my access token, but I'm not sure how to do that. I mean i get that JJWT is an API to create JWT, but then how can I use the JWT to get my access token?
Any help/clarification/direction is much appreciated.
We using JWT with the node.js, to create new Token jwt.sign(data, key) takes at least to an argument, the fist must be some credential like userId, email..., the second will be key to verify later. to verify the token is it valid we use jwt.verify(), the first argument is token (where the jwt.sing() give you) and the second is the key (where you provide when creating);
example:
Creating JWT token:
var jwt = require('jsonwebtoken');
cosnt token = jwt.sign({ email: 'test#test.com', userId: '993333' }, 'secretkey');
verifying Token:
try {
const decodedToken = jwt.verify(token, 'secretkey');
}
catch(err) {
throw new Error(err)
}
// once verified
conosole.log(decodedToken)
I found this post how to create and verify token using java, thanks!

Auth 0 configuration audience

I just found out that I have a problem with auth0 and it relates to the auth0 configuration audience. So when I explicitly write the audience, the JWT verification failed with error The provided Algorithm doesn't match the one defined in the JWT's Header. When I don't write the audience, everything will work fine, except now everytime the token expire and user click on login link it skip the login process and immediately logged in with the previous credential. I don't want this to happen, I want user to still authenticate themselves again after token expire, just like when I write the audience.
So what is audience and why does it affect the behaviour like this?
And How can I fix it to get the behaviour I wanted?
Below is the configuration of the Auth0
auth0 = new auth0.WebAuth({
clientID: environment.auth0ClientId,
domain: environment.auth0Domain,
responseType: 'token id_token',
//Below is the audience I'm talking about
audience: '${constants.MY_APP}/userinfo',
redirectUri: `${constants.ORIGIN_URL}/auth`,
scope: 'openid email'
});
I need to know how I can make the JWT to be verified correctly as well as make the login behaviour correctly when the JWT expire.
Auth0 can issue two types of tokens: opaque and JWT.
When you specify the audience parameter, you will receive a JWT token. JWTs differ from opaque tokens in that they are self-contained and therefore you verify them directly in your application.
In this case, the JWT you have received is signed with an algorithm different to that which you've defined in your verification logic. You can decode the JWT using https://jwt.io and you can see which algorithm it was signed with in the alg attribute of the header.
You can also find out the signing algorithm your API uses in the Auth0 dashboard. Go APIs, click your API, click the Settings tab and then scroll to Token Setting. You will see it listed as the Signing Algorithm.
Judging by the error message, you are using the java-jwt library, in which case you will need change the signing algorithm accordingly per the steps outlined here: https://github.com/auth0/java-jwt#verify-a-token
For HS256:
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}
Where secret is your API's Signing Secret.
For RS256, it's a little more involved. You first need to decode the token to retrieve the kid (key ID) from the header:
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
//Invalid token
}
You then need to construct a JwkProvider using the jwks-rsa-java library:
JwkProvider provider = new UrlJwkProvider("https://your-domain.auth0.com/");
Jwk jwk = provider.get(jwt.getKeyId());
Finally, you can use the public key retrieved from the JWKS and use it to verify the token:
RSAPublicKey publicKey = (RSAPublicKey) jwk.getPublicKey();
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception) {
//Invalid signature/claims
}
Keep in mind that it's preferred to use RS256 over HS256 for the reasons outlined here: https://auth0.com/docs/apis#signing-algorithms
You may also find this article useful for detailed information on verifying tokens: https://auth0.com/docs/api-auth/tutorials/verify-access-token

Explanations on JWT Tokens structure

I'm trying to understand JWT tokens usage but I'm getting lost on the theorycal part.
I have some questions about JWT Tokens structure, in order to make this question a sort of documentation for new users, providing it with a logic order, I will write them in bold below as long as I list a sample JWT content.
I will also summary them in the end of the question
A "classic" JWT token is composed as follows:
[HEADER].[PAYLOAD].[SIGNATURE]
In detail:
HEADER
{
"alg": "HS256",
"typ": "JWT"
}
Which contains the following fields:
ALG = Encryption algorythm (using the default HS256 could be fine)
TYP = simply tells that it's a JWT
PAYLOAD
{
"sub": "1234567890",
"name": "MrJohnDoe",
"iat": 1516239022
}
SUB = Is an OPTIONAL parameter. It's the subject of the token. (credits: see #cassiomolin answer)
According to: Where to store user id in jwt, it looks like you can use it to store your user ID in it.
NAME = The username
IAT = Token creation date and time, expressed in unix timestamp. (Thanks to #jps and #JeanRostan in the comments below)
SIGNATURE
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
SECRET = Unique key known just by the server. To not confuse with the current user password, which should never be used for this! (thanks to #jps in the comments below)
What is sub? Can you please provide me an example of what it could be in a common usage?
The sub claim identifies the principal that is the subject of the JWT. In other other, it can hold the username of the user who you issued the token to.
From the RFC 7519:
4.1.2. "sub" (Subject) Claim
The sub (subject) claim identifies the principal that is the
subject of the JWT. The claims in a JWT are normally statements
about the subject. The subject value MUST either be scoped to be
locally unique in the context of the issuer or be globally unique.
The processing of this claim is generally application specific. The
sub value is a case-sensitive string containing a StringOrURI
value. Use of this claim is OPTIONAL.