How to generate a JWT for a Cloud Function HTTP trigger? - java

What I'm trying to do is generate a JWT for calling the HTTP trigger on a GCP Cloud Function I've deployed.
I've already deployed my function with 'allUsers' and verified it works, but I want it to be more secure, so I need to attach a JWT to my HTTP request.
I'm following this code and the following snippets are mostly from that.
I think I am close, but not quite there yet. In all of the samples etc below I've changed my project name to PROJECT_NAME.
First I created a service account named testharness-sa and downloaded its key file. I have an env var GOOGLE_APPLICATION_CREDENTIALS pointing to that file when I run my tests. Then I ran the following command:
gcloud functions add-iam-policy-binding gopubsub \
--member='serviceAccount:testharness-sa#PROJECT_NAME.iam.gserviceaccount.com' \
--role='roles/cloudfunctions.invoker'
This gave me a confirmation by listing all the current bindings on my cloud function, including testharness-sa.
The core of my code is this:
private String getSignedJwt() {
GoogleCredentials credentials = GoogleCredentials
.getApplicationDefault()
.createScoped(Collections.singleton(IAM_SCOPE));
long now = System.currentTimeMillis();
RSAPrivateKey privateKey = (RSAPrivateKey) credentials.getPrivateKey();
Algorithm algorithm = Algorithm.RSA256(null, privateKey);
return JWT.create()
.withKeyId(credentials.getPrivateKeyId())
.withIssuer(credentials.getClientEmail())
.withSubject(credentials.getClientEmail())
.withAudience(OAUTH_TOKEN_AUDIENCE)
.withIssuedAt(new Date(now))
.withExpiresAt(new Date(now + EXPIRATION_TIME_IN_MILLIS))
.withClaim("target_audience", clientId)
.sign(algorithm);
}
This gives me a signed JWT. As I understand things, this is used to call GCP to get me a final JWT I can use to call my cloud function.
Once I generate the signed JWT I use it like this:
String jwt = getSignedJwt();
final GenericData tokenRequest = new GenericData()
.set("grant_type", JWT_BEARER_TOKEN_GRANT_TYPE)
.set("assertion", jwt);
final UrlEncodedContent content = new UrlEncodedContent(tokenRequest);
final HttpRequestFactory requestFactory = httpTransport.createRequestFactory();
final HttpRequest request = requestFactory
.buildPostRequest(new GenericUrl(OAUTH_TOKEN_URI), content)
.setParser(new JsonObjectParser(JacksonFactory.getDefaultInstance()));
HttpResponse response = request.execute();
...
So it gets the signed JWT and makes up a request to get the final JWT and then... fails. The error is "Invalid JWT: Failed audience check"
It looks like I have a bad parameter, so let's look at my parameters (they are actually constants, not parameters):
private static final String IAM_SCOPE = "https://www.googleapis.com/auth/iam";
//"https://www.googleapis.com/auth/cloud-platform";
private static final String OAUTH_TOKEN_URI = "https://oauth2.googleapis.com/token";
//"https://www.googleapis.com/oauth2/v4/token";
private static final String OAUTH_TOKEN_AUDIENCE = "https://us-central1-PROJECT_NAME.cloudfunctions.net/gopubsub";
//"https://www.googleapis.com/token";
private static final String JWT_BEARER_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
private static final long EXPIRATION_TIME_IN_MILLIS = 3600 * 1000L;
I've added other variants as comments against the constants.
Based on the error message it looks like the audience value I'm using is wrong.
This page suggests the audience should be the service account email, and I think I've seen elsewhere that it ought to be the URL of the cloud function, but neither of those work for me.
I do know this can work because I can issue this command:
gcloud auth print-identity-token
which gives me the final JWT (as long as I have GOOGLE_APPLICATION_CREDENTIALS pointing at my json file).
I can paste that JWT into a curl command and invoke the HTTP trigger successfully, and if I leave the JWT out it fails, so I know the JWT is being checked.
But so far I don't know how to do the equivalent of gcloud auth print-identity-token from my Java code.
Anyone know?

I found the answer was not related to scope, despite the error message. It is actually related to the clientId value I was passing (and didn't really mention in my question, though it is there in the code). I was using a value I found in the service account json file, a really long string of digits. That was the problem. It needed to be the URL of my HTTP trigger. So these are the constants I ended up with:
private static final String IAM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
private static final String OAUTH_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token";
private static final String OAUTH_TOKEN_AUDIENCE = "https://www.googleapis.com/oauth2/v4/token";
private static final String JWT_BEARER_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
private static final long EXPIRATION_TIME_IN_MILLIS = 3600 * 1000L;
In fact it still works if I remove all references to IAM_SCOPE, so I conclude that doesn't matter. The piece of code that pulls it together now looks like this:
return JWT.create()
.withKeyId(credentials.getPrivateKeyId())
.withIssuer(credentials.getClientEmail())
.withSubject(credentials.getClientEmail())
.withAudience(OAUTH_TOKEN_AUDIENCE)
.withIssuedAt(new Date(now))
.withExpiresAt(new Date(now + EXPIRATION_TIME_IN_MILLIS))
.withClaim("target_audience", targetURL)
.sign(algorithm);
Specifically the change is the withClaim() call which now contains the URL I want to call. With that in place the code returns the JWT I was hoping for and which does, in fact, work when I call the secured HTTP trigger URL. Hope that helps someone else.

Related

How to connect Dynamics CRM 2016 instance running on Azure Cloud through Java?

I want to integrate my java application with Dynamics CRM.
for that i need to Develop Java method to connect Dynamics CRM 2016
instance running on Azure Cloud, and authenticate the calling request
from Java.
I want to Create Lead record with lead entity fields including option
sets fields. New Lead record will be mapped to a default CRM
user/service account configured for integration purpose.
Any sample code for connecting to REST endpoint will
be helpful.
As rightly suggested by #ankuser, there are lot of resouces available to query through Dynamics CRM api. Important point to remember that Microsoft offer ADAL Azure Active Directory Authentication Library for Java to help you with the bearer token.
Bearer token you would be needing it to call dynamics api:
Here is sample code to get the token:
private final static String CLIENT_ID = "00000000-0000-0000-0000-000000000000";
//CRM URL
private final static String RESOURCE = "https://org.crm.dynamics.com";
//O365 credentials for authentication w/o login prompt
private final static String USERNAME = "administrator#org.onmicrosoft.com";
private final static String PASSWORD = "password";
//Azure Directory OAUTH 2.0 AUTHORIZATION ENDPOINT
private final static String AUTHORITY =
"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000";
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(RESOURCE,
CLIENT_ID,
USERNAME,
PASSWORD, null);
result = future.get();
} finally {
service.shutdown();
}
String token = result.getAccessToken();
Additional reference , you can browse through below code repo:
https://github.com/jlattimer/CrmWebApiJava
For the detailed step to step guide , please refer :
https://www.fmtconsultants.com/connect-java-application-crm-simple-java-console-application/
Hope it helps.
You can do so. Dynamics have exposed REST API for use and this API is no different than any.
Here you can find all the details about API.
I just googled and found this nice article of connecting to Dynamics crm (online) via REST

To sign into this application the account must be added to the <TenantID> directory

I am trying to authenticate and app and get bearer token for further use. I get the error which is title of this thread.
Another thread describes same thing except that my code is in Java. the workaround is to use certificate method.
"To sign into this application the account must be added to the domain.com directory"
Can someone please describe detailed steps for this workaround :- certificate method
Or how can i fix the below code with any other method
Or is any other method to achieve this whole task
Here is my code
private final static String AUTHORITY = "https://login.microsoftonline.com/<tenantId>/OAuth2/Authorize";
private final static String CLIENT_ID = "<Client_Id>";
private final static String CLIENT_SECRET = "<Secret>";
public static void main(String args[]) throws Exception {
try (BufferedReader br = new BufferedReader(new InputStreamReader(
System.in))) {
String username = CLIENT_ID;
String password = CLIENT_SECRET;
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
"https://graph.microsoft.com", CLIENT_ID, username, password,
null);
result = future.get();
}
finally {
service.shutdown();
}
}
I have registered my app in AAD app registration. The Client_Id is App Id and Secret is a key in the above code
In case, you want to make the code work in it's current form without any workaround, check for following things -
Make sure you have the correct tenantId GUID specified in the first line of code.
Steps to get tenantid -
Login to Azure Portal, Navigate to your Azure AD, Go to properties like in screenshot below and Directory ID should give you the GUID.
private final static String AUTHORITY = "https://login.microsoftonline.com/<tenantId>/OAuth2/Authorize";
Make sure that Username you are using, is for a user that belongs to your AzureAD tenant.
One possible reason could be if you're using a Microsoft account like xyz#outlook.com or hotmail.com etc. Try using an account that is created in this Azure AD like xyz#yourtenantdomain.onmicrosoft.com or any other verified domain that your tenant uses.
Future<AuthenticationResult> future = context.acquireToken("https://graph.microsoft.com", CLIENT_ID, username, password, null);
If you want to use certificate credentials to authenticate your app, you could refer to this article.
Steps:
1.Uploading the certificate file
2.Updating the application manifest
Code sample(it uses C#, you could refer):
Authenticating to Azure AD in daemon apps with certificates
It also shows how you can create a self-signed certificate using the New-SelfSignedCertificate Powershell command. You can also take advantage and use the app creation scripts to create the certificates, compute the thumbprint, and so on.

Validating Azure AD Signature in Java

I am a reasonably experienced Java developer (4-5 years) but new to Azure AD and its capabilities, so I apologize in advance for a potentially basic question. I have struggled to find any Microsoft documentation or Stack Overflow questions covering this topic in Java (vast majority being in C#) and from my understanding, C# has more Azure AD libraries than Java, hence the solution in C# will not necessarily be the solution in Java.
I am trying to complete a authentication POC based on a scenario in-which there is an existing Azure AD system, full of users, that I want to leverage as an authentication point. My Java application will collect the users username and password (I understand this is deprecated and non-ideal, but for legacy reasons required) and makes a call using the Microsoft adal4j library to an Azure endpoint which I can get to successfully return a JWC access token (in addition to a refresh and ID token).
This is my existing code snippet that retrieves the JWC access token.
private static AuthenticationResult getAccessTokenFromUserCredentials(String username, String password, String
AUTHORITY, String CLIENT_ID) throws Exception {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
"https://graph.windows.net", CLIENT_ID, username, password,
null);
result = future.get();
} finally {
service.shutdown();
}
if (result == null) {
System.out.println("ex)");
}
return result;
}
public void azureAuthenticate(String authority, String clientID, String username, String password){
AuthenticationResult result = null;
try {
result = getAccessTokenFromUserCredentials(username, password, authority, clientID);
DecodedJWT accessToken = JWT.decode(result.getAccessToken());
//Want to verify the validity of this access token
} catch (Exception ex) {
ex.printStackTrace();
}
}
My code is largely based on this Microsoft documentation
After receiving the token, I need to be able to validate its authenticity (I understand the business logic side of confirming its claims, but I am confused as to how to verify the signature is legitimate).
Thank you in advance for any help, I am happy to provide any clarifications needed.
The access token from the Azure AD is a JSON Web Token(JWT) which is signed by Security Token Service in private key. A JWT token is a non-encrypted digitally signed JSON payload which contains different attributes (claims) to identify the user. The signature is the last part of the JWT and needs to be used for verification of the payload. This signature was generated with the algorithm described in the header(RS256 if issued from AAD) to prevent unauthorised access.Please refer to this document for more details about JWT token .
To validate signature , firstly we should retrieve and cache the singing tokens (public key) :1)The first call is to the discovery endpoint. It's URL is formed as '/.well-known/openid-configuration' .2) Then you will find lots of metadata here including the issuer value and the jwks_uri endpoint address to get the keys to validate the token's signature .
Token signing is implemented according to JSON Web Key spec. Using Key ID and X.509 certificate thumbprint values from the token's header (kid and x5t parameters respectively) and then find the appropriate public key in the obtained collection of keys to verify the signature. I am not familiar with java ,but you could refer to this thread which includes code sample for how to validate the signature in Java .

Goo.gl link shortener with Scribe - authentication issues

I am trying to use a URL shortener, with Scribe, based on this example.
However, I want to make sure I can track the visits which my short URL gets, which means it must be unique. To create unique links, I need to authenticate with Google, as per this.
Signed in
Your links are automatically added to goo.gl where you can track their use.
A unique short URL is created each time a long URL is shortened.
Signed out
Your links won’t show up on your goo.gl page.
The same short URL is reused each time a long URL is shortened by you or someone else.
In the example, the oAthRequest is not signed with the oAuthService. I have updated this so that it can sign the request and send it (as a signed in user).
Here is my code:
private static final String API_KEY = "XXXXXXXX";
private static final String API_URL = "https://www.googleapis.com/urlshortener/v1/url";
private static final String API_URL_WITH_KEY = API_URL + "?key=" + API_KEY;
public TrackableLink createTrackableLink(String longUrl) {
OAuthService oAuthService = new ServiceBuilder()
//Google Api Provider - Google's URL Shortener API is part of Google Platform APIs
.provider(GoogleApi.class)
/*
Using "anonymous" as API Key & Secret because Google's URL Shortener service
does not necessarily requires App identification and/or User Information Access
*/
.apiKey("anonymous")
.apiSecret("anyonymous")
//OAuth 2.0 scope for the Google URL Shortener API
.scope("https://www.googleapis.com/auth/urlshortener")
//build it!
.build();
Token requestToken = oAuthService.getRequestToken();
OAuthRequest request = new OAuthRequest(Verb.POST, API_URL_WITH_KEY);
request.addHeader("Content-Type", "application/json");
request.addPayload(new JSONObject().put(RESPONSE_LONG_URL, longUrl)
.toString());
oAuthService.signRequest(requestToken, request);
Response response = request.send();
JSONObject json = new JSONObject(response.getBody());
String shortUrl = json.getString(RESPONSE_SHORT_URL);
TrackableLink tl = new TrackableLink(longUrl, shortUrl);
return tl;
}
I replace the "anonymous" details with my values from the Google API website, and I get this exception:
Can't extract token and secret from this: 'Consumer is not registered: 7629638329XXXXXXXXXXX.apps.googleusercontent.com
I'm not sure exactly what I am doing wrong here. I have tried almost every combination of values for the key/secret from the various keys the google console gives me, could this be caused by something else other than something up with the API key?
Any ideas why I am getting the consumer is not registered error? On my Google account, I have enabled the API.

uri.getQueryParameter("oauth_verifier") is always null

I am new to OAuth and I'm developing an Android app that requires it and I'm using the signpost library.
I believe I have it all working up to the point where I can get the token using
String token = uri.getQueryParameter("oauth_token");
Which returns correctly however the very next line
String verifier = uri.getQueryParameter("oauth_verifier");
returns null. I have debugged and inspected uri which is initialized as
Uri uri = this.getIntent().getData();
And it does not appear to have a key "oauth_verifier"
The verifier being null causes an OAuthExpectationFailedException when I try the ofllowing line
provider.retrieveAccessToken(consumer, verifier);
Can anyone help me figure out why the verifier is null I am using a callback not OOB.
I have seen in other questions and guides the likes of this in the response:
dat=myapp://twitter?oauth_token=tJpJHOOwoTGMwdvHyYbfX2tyHKOp0Y2kdRRZf3sM&
oauth_verifier=xc49oM8eVVmK46ZSLz2RMT2uqXn3SxrMxf5ZAMXaD2Y
Mine is similar but without the ouath_verifier key.
Here is where I send the initial intent which works and I get a return
String clientKey = "xxxxxxx";
String clientSecret = "yyyyyyyyy";
CommonsHttpOAuthConsumer consumer = new CommonsHttpOAuthConsumer(clientKey,clientSecret);
provider = new CommonsHttpOAuthProvider(URL_Request_Token,
URL_Access_Token,
URL_Authorize";
provider.setOAuth10a(true);
try {
String authUrl = provider.retrieveRequestToken(consumer,CALLBACK_URI.toString());
String token = consumer.getToken();
String secret = consumer.getTokenSecret();
this.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)));
}
Any help is much appreciated
A problem I had while developing a twitter interaction sample is that I forgot to set a callback url from the twitter app console.
If you don't do that, your application won't be redirected and so it won't be able to grap the verifier token.
PROMO MODE ON
However, if you feel particularly brave, I've been working on this library PostManLib lately and I am looking for beta testers. It should handle all the async oauth interaction using the well know scribe library under the hood.
PROMO MODE OFF

Categories