I have created a service account and downloaded my JSON Credential on Google Cloud Platform. I need to make REST POST call in .NET to DialogFlow Service API. At this moment, I can do it only with a generated token in PowerShell. Since, I need to do it all from script, I need to generate a JWT to pass as my bearer in my REST call. My Problem is that the generated JWT is not honored by Google.
I get my response in PowerShell based on this doc page and I replicate sample codes from this doc page to create my JWT.
public static string GetSignedJwt(string emailClient, string
dialogueFlowServiceApi, string privateKeyId, string privateKey, string
jsonPath)
{
// to get unix time in seconds
var unixTimeSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
// start time of Unix system
var origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
// adding milliseconds to reach the current time, it will be used for issueAt time
var nowDataTime = origin.AddSeconds(unixTimeSeconds);
// one hour after the current time, it will be used for expiration time
var oneHourFromNow = nowDataTime.AddSeconds(3600);
// holder of signed json web token that we will return at the end
var signedJwt = "";
try
{
// create our payload for Jwt
var payload = new Dictionary<string, object>
{
{"iss", emailClient},
{"sub", emailClient},
{"aud", dialogueFlowServiceApi},
{"iat", nowDataTime},
{"exp", oneHourFromNow}
};
// create our additional headers
var extraHeaders = new Dictionary<string, object>
{
{"kid", privateKeyId}
};
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
signedJwt = encoder.Encode(extraHeaders, payload, privateKey);
}
catch (Exception e)
{
Console.WriteLine(e);
// return null if there has been any error
return null;
}
finally
{
Console.WriteLine(signedJwt);
}
return signedJwt;
}
Notice that, it is needed to be signed in RSA256 by passing public and private keys, as Google did it in Java sample snippet, however, my equivalent in .Net gives me only Object reference not set to an instance of an object when I use that algorithm:
var key = RSA.Create(privateKey);
IJwtAlgorithm algorithm = new RS256Algorithm(null, key);
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
signedJwt = encoder.Encode(extraHeaders, payload, privateKey);
Besides of correct keys, I am using https://dialogflow.googleapis.com/google.cloud.dialogflow.v2beta1.Intents as dialogFlow service API key.
I expect it that my generated JWT gets accepted, however it is rejected by Google.
1) You are using the wrong algorithm
Change this line of code:
IJwtAlgorithm algorithm = new RS256Algorithm(null, key);
To this:
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
2) For the JWT headers:
var additional_headers = new Dictionary<string, object>
{
{ "kid", privateKeyId },
{ "alg", "RS256" },
{ "typ", "JWT" }
};
3) Your JWT Payload does not include a scope. I am not sure which scope you need but here is an example. Add this to the payload before creating the JWT:
string scope = "https://www.googleapis.com/auth/cloud-platform";
var payload = new Dictionary<string, object>
{
{"scope", scope},
{"iss", emailClient},
{"sub", emailClient},
{"aud", dialogueFlowServiceApi},
{"iat", nowDataTime},
{"exp", oneHourFromNow}
};
4) For most Google APIs (not all) you also need to exchange the Signed JWT for a Google OAuth Access Token:
public static string AuthorizeToken(string token, string auth_url)
{
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var content = new NameValueCollection();
// Request a "Bearer" access token
content["assertion"] = token;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
var response = client.UploadValues(auth_url, "POST", content);
return Encoding.UTF8.GetString(response);
}
The Authorization URL for above:
string auth_url = "https://www.googleapis.com/oauth2/v4/token";
Based on what I learned from How to manually decrypt and asp.net core auth cookie
I tried to do the similar. The only difference is that the cookie is set by asp.net core identity. Here is the piece in SetUp.cs:
services.AddIdentity<ApplicationUser, IdentityRole>( options =>
{
options.Cookies.ApplicationCookie.AuthenticationScheme = "Cookies";
options.Cookies.ApplicationCookie.DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(#"c:\shared-auth-ticket-keys\"));
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
And here is how I tried to setup decrypt in homeController.cs:
var cookies = HttpContext.Request.Cookies;
var provider = DataProtectionProvider.Create(new DirectoryInfo(#"c:\shared-auth-ticket-keys\"));
//Get a data protector to use with either approach
var dataProtector = provider.CreateProtector("Identity.Application", "Cookies", "v2");
//Get the decrypted cookie as plain text
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
foreach (var cookie in cookies)
{
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookie.Value);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);
}
But I always get the following error:
The key {******-****} was not found in the key ring.
How to match up the key/dataProtectionProvider?
Is there a possibility to configure OAuth2 AssertionFlow with Facebook in Thinktecture Identity Server v3?
There was a post on leastprivilege.com about implementing AssertionFlow for Microsoft OAuth and AuthorizationServer but I need to integrate with Facebook and, furthermore, AuthorizationServer is marked as deprecated and it's not maintained anymore.
In response to #NathanAldenSr's comment, I publish some code of my working solution.
Server side - custom validator:
public class FacebookCustomGrantValidator: ICustomGrantValidator
{
private readonly IUserService userService;
private const string _FACEBOOK_PROVIDER_NAME = "facebook";
// ...
async Task<CustomGrantValidationResult> ICustomGrantValidator.ValidateAsync(ValidatedTokenRequest request)
{
// check assetion type (you can have more than one in your app)
if (request.GrantType != "assertion_fb")
return await Task.FromResult<CustomGrantValidationResult>(null);
// I assume that fb access token has been sent as a response form value (with 'assertion' key)
var fbAccessToken = request.Raw.Get("assertion");
if (string.IsNullOrWhiteSpace(assertion))
return await Task.FromResult<CustomGrantValidationResult>(new CustomGrantValidationResult
{
ErrorMessage = "Missing assertion."
});
AuthenticateResult authebticationResult = null;
// if fb access token is invalid you won't be able to create Facebook client
var client = new Facebook.FacebookClient(fbAccessToken);
dynamic response = client.Get("me", new { fields = "email, first_name, last_name" });
// create idsrv identity for the user
authebticationResult = await userService.AuthenticateExternalAsync(new ExternalIdentity()
{
Provider = _FACEBOOK_PROVIDER_NAME,
ProviderId = response.id,
Claims = new List<Claim>
{
new Claim("Email", response.email),
new Claim("FirstName", response.first_name),
new Claim("LastName", response.last_name)
// ... and so on...
}
},
new SignInMessage());
return new CustomGrantValidationResult
{
Principal = authebticationResult.User
};
}
}
You can easily test it with OAuth2Client that is also provided by Thinktecture (in Thinktexture.IdentityModel Client Library nuget package).
string fbAccessToken = "facebook_access_token_you_aquired_while_logging_in";
string assertionType = "assertion_fb";
var client = new OAuth2Client(
new Uri("your_auth_server_url"),
"idsrv_client_id",
"idsrv_client_secret");
string idsrvAccessToken = client.RequestAssertionAsync(assetionType, fbAccessToken,).Result;
IdentityServer v3 also supports assertion flow. The samples wiki has two samples on that (called "Custom Grants):
https://github.com/thinktecture/Thinktecture.IdentityServer.v3.Samples/tree/master/source
I have managed to get back a JWT token from Identity Server using OAuth2 and would like to extract the claims from the token.
When I use a token decoder such as https://developers.google.com/wallet/digital/docs/jwtdecoder, I can peek inside the token and it looks fine.
However I am not sure what decrypting to use in c# in order to use the Microsoft JwtSecurityTokenHandler.ValidateToken to get back a claims identity.
In identity server, I am using a symmetric key which I have pasted for reference in my code. The JWT token is also valid.
Would really appreciate some help:
string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vaWRlbnRpdHlzZXJ2ZXIudjIudGhpbmt0ZWN0dXJlLmNvbS90cnVzdC9jaGFuZ2V0aGlzIiwiYXVkIjoidXJuOndlYmFwaXNlY3VyaXR5IiwibmJmIjoxMzk3MTEzMDY5LCJleHAiOjEzOTcxNDkwNjksIm5hbWVpZCI6InN0ZWZhbiIsInVuaXF1ZV9uYW1lIjoic3RlZmFuIiwiYXV0aG1ldGhvZCI6Ik9BdXRoMiIsImF1dGhfdGltZSI6IjIwMTQtMDQtMTBUMDY6NTc6NDguODEyWiIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9jbGllbnQiOiJyZWx5aW5nIHBhcnR5IDMgdGVzdCBjbGllbnQgbmFtZSIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9zY29wZSI6InVybjp3ZWJhcGlzZWN1cml0eSJ9.cFnmgHxrpy2rMg8B6AupVrJwltu7RhBAeIx_D3pxJeI";
string key = "ZHfUES/6wG28LY+SaMtvaeek34t2PBrAiBxur6MAI/w=";
var validationParameters = new TokenValidationParameters()
{
AllowedAudience = "urn:webapisecurity",
SigningToken = new ????
ValidIssuer = #"http://identityserver.v2.thinktecture.com/trust/changethis"
};
var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(token, validationParameters);
What sort of SigningToken should I use for the validationParameters.SigningToken ??
You can use the following website to Decode the token
http://jwt.io/
or here is a code to Decode JWT Token using C#
class Program
{
static void Main(string[] args)
{
string token ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vaWRlbnRpdHlzZXJ2ZXIudjIudGhpbmt0ZWN0dXJlLmNvbS90cnVzdC9jaGFuZ2V0aGlzIiwiYXVkIjoidXJuOndlYmFwaXNlY3VyaXR5IiwibmJmIjoxMzk3MTEzMDY5LCJleHAiOjEzOTcxNDkwNjksIm5hbWVpZCI6InN0ZWZhbiIsInVuaXF1ZV9uYW1lIjoic3RlZmFuIiwiYXV0aG1ldGhvZCI6Ik9BdXRoMiIsImF1dGhfdGltZSI6IjIwMTQtMDQtMTBUMDY6NTc6NDguODEyWiIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9jbGllbnQiOiJyZWx5aW5nIHBhcnR5IDMgdGVzdCBjbGllbnQgbmFtZSIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9zY29wZSI6InVybjp3ZWJhcGlzZWN1cml0eSJ9.cFnmgHxrpy2rMg8B6AupVrJwltu7RhBAeIx_D3pxJeI";
var parts = token.Split('.');
string partToConvert = parts[1];
var partAsBytes = Convert.FromBase64String(partToConvert);
var partAsUTF8String = Encoding.UTF8.GetString(partAsBytes, 0, partAsBytes.Count());
//JSON.net required
var jwt = JObject.Parse(partAsUTF8String);
Console.Write(jwt.ToString());
Console.ReadLine();
}
}
It's a BinarySecretSecurityToken - base64 decode the stringified key to use it.
From my WSDL I have the following service part:
<service name="BAPI_CUSTOMER_DISPLAYService">
<documentation>SAP Service BAPI_CUSTOMER_DISPLAY via SOAP</documentation>
<port name="BAPI_CUSTOMER_DISPLAYPortType" binding="s0:BAPI_CUSTOMER_DISPLAYBinding">
<soap:address location="http://2.3.4.100:8000/sap/bc/soap/rfc"/>
</port>
</service>
then what will be endpoint reference for this?
I am giving it as "http://2.3.4.100:8000/sap/bc/soap/rfc" in my salesforce client and it gives the following error.
"This service requires client certificate for authentication procedure."
I am sure that i need to give user name and password not knowing how i can set them in my client which is a Apex code.
Help is appreciated.
I imported the Enterprise WSDL and used the uri from the loginResult. Here's some code from my project:
LoginResult loginResult = null; // Login Result (save and make static)
SessionHeader sessionHeader = null; // Session Header (save and make static)
SoapClient soapClient = null; // This is the Enterprise WSDL
SecureStatusClient SecureStatusClient = null; // This is my custom #WebService
// Create Login Request
LoginScopeHeader loginScopeHeader = new LoginScopeHeader
{
organizationId = configuration["OrganizationId"],
portalId = configuration["PortalId"]
};
// Call Login Service
string userName = configuration["UserName"];
string password = configuration["Password"];
string securityToken = configuration["SecurityToken"];
using (SoapClient loginClient = new SoapClient())
{
loginResult = loginClient.login(loginScopeHeader, userName, password + securityToken);
if (result.passwordExpired)
{
string message = string.Format("Salesforce.com password expired for user {0}", userName);
throw new Exception(message);
}
}
// Create the SessionHeader
sessionHeader = new SessionHeader { sessionId = loginResult.sessionId };
// Create the SoapClient to use for queries/updates
soapClient = new SoapClient();
soapClient.Endpoint.Address = new EndpointAddress(loginResult.serverUrl);
// Create the SecureStatusServiceClient
secureStatusClient = new SecureStatusServiceClient();
Uri apexUri = new Uri(SoapClient.Endpoint.Address.Uri, "/services/Soap/class/SecureStatusService");
secureStatusClient.Endpoint.Address = new EndpointAddress(apexUri);