Generating a Signed JWT for a Google Service account - Validation FAILS - google-cloud-platform

I'm following Google's documentation, on how to create a JWT for a specific service account.
The document instructs on how to manually create a JWT, as well as how to compute the signing process, based on JWS guidelines.
I followed the exact procedure described by Google, but no matter what i do, the generated JWT cannot be validated by jwt.io, which fails with a Invalid Signature.
Follow the guidelines, i created the service account, and a specific key, which contains the Private Key details:
{
"type": "service_account",
"project_id": "myProject",
"private_key_id": "1212121212",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5Cg7FW0NGwLeD\nrpc0r1Ayta23GxVw0KCA+d/TjSyuZ3lmKiObz9EGJpHSHbX4yrODA6FvYixUrAKm\nUSSMvFLUpYM2xoEgAKnwd6XVgdnjwk7wnIIsEdyjMCbews1orr6Ze+LIPkV2WF4d\nHSAqRqJrERR1Gb9gxKC/WQhMvCotp7zFTqLcUI3eUhR3tIgpLwZFpIxXZjOTwWoB\n6bWxOe39Suft1GYAR0prFcLmXtfw43B+9gVcMLOHBBTcxojBXkQ2bhjp7dGqvlUz\n3nO/1bqbzvd5I6bQif+tjLEceyIUbE/rJ6PgW9SVtfktrQIQQ9VGtAUya4IYSEL8\nJaXZxs6jAgMBAAECggEAFe1+3J0OYZcQPZb2AjSi+1oTm6GmWSJ6ssNpu9x8pq+f\nxPSfbaUjRGhTsCOnNIlzhnDACRQIOYHSJTrJFbMc2b2XdBPyqgPfdPNTf/QNtHOK\nqUbSwj2Ho6sJdYJ+QbaGOGgO8uM2QL+uFM3RHvwUiT2SlWHsukny3ATFUAVIYPUj\nxr0m6QKBgQDiH6vL1plGsIFVWR5M\nESsZdADubhDOtml6r81aKLXJPK9LeHwJOAgTFfZHJD4D4e7KSQfYlbf9tRE7c2PE\ntcj6BVrHdtYRqaXY+Q7BW2mXRb7IJKtVxZzljPY0HcDjpZ7UqXUB/sVbxT/zbt4B\n9lIegpLJyd6RpzYhjIDv8OIaTwKBgQDRfMLsTg0+nTzmmIurmD1IhdPa7KvhGMDn\nXSs0zRR4IRC2BCn5LHYYD4cgO+mmGWxcQZREQ220W3uXwRbSTJZT6ZtzP40AXx86\nTRop5NBZYDkdJ1t9qhi2aU//5mwn5ubC/42fBCwqmRXr0nOtLhKtEONRmGGyl7hk\nWXWII2z1bQKBgAMoNArVhTBSeIvLgbvIJZTmZLjvenaYX2KiH7jZhqg3mRoyUuvA2glpo9ARzB7ut\nR5LXq5GAwOBIzMhtZWTyE53O9jI5+8g/RB7WlUx\nsZt5bkf42zhsJwZnfV480Hx8GhnCnhGcTVjJbbN5AoGBAInRfNcLpgPtHWiQ5r9W\nANd+XDLpjIUQfh+0NaQeYPG7DM59oPRqUDs/BSp21nTmSnNC537H0OHlCScpmc7G\ncpj+/jtLIhTN0IwKosaH3mJpQ3AcUI7IooFKgYrC/bwCUQ5xX7CwqaOzTKf3MtX1\nngd7mPWTFkRDxCkCnvfUfcem\n-----END PRIVATE KEY-----\n",
"client_email": "test#email",
"client_id": "121212",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/myProject-dev.iam.gserviceaccount.com"
}
I'm using .net 5, and the .net implementation for manually creating and signing a JWT, would be:
private string GetToken()
{
var header = "{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"ed36c257c59ebabb47b456828a858aa5fcda12xx\"}";
var claims = "{\"sub\":\"10217931234509168826\",\"email\":\"test#email.com\",\"iss\":\"https:\\//accounts.google.com\",\"aud\":\"MyAudience",\"exp\":1665530643,\"iat\":1665527043}";
var b64header = Convert.ToBase64String(Encoding.UTF8.GetBytes(header))
.Replace('+', '-').Replace('/', '_').Replace("=", "");
var b64claims = Convert.ToBase64String(Encoding.UTF8.GetBytes(claims))
.Replace('+', '-').Replace('/', '_').Replace("=", "");
var payload = b64header + "." + b64claims;
var message = Encoding.UTF8.GetBytes(payload);
var sig = Convert.ToBase64String(SignData(message))
.Replace('+', '-').Replace('/', '_').Replace("=", "");
return payload + "." + sig;
}
private static byte[] SignData(byte[] message)
{
var privateKeyContent = "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5Cg7FW0NGwLeD\nrpc0r1Ayta23GxVw0KCA+d/TjSyuZ3lmKiObz9EGJpHSHbX4yrODA6FvYixUrAKm\nUSSMvFLUpYM2xoEgAKnwd6XVgdnjwk7wnIIsEdyjMCbews1orr6Ze+LIPkV2WF4d\nHSAqRqJrERR1Gb9gxKC/WQhMvCotp7zFTqLcUI3eUhR3tIgpLwZFpIxXZjOTwWoB\n6bWxOe39Suft1GYAR0prFcLmXtfw43B+9gVcMLOHBBTLm6QKBgQDiH6vL1plGsIFVWR5M\nESsZdADubhDOtml6r81aKLXJPK9LeHwJOAgTFfZHJD4D4e7KSQfYlbf9tRE7c2PE\ntcj6BVrHdtYRqaXY+Q7BW2mXRb7IJKtVxYPG7DM59oPRqUDs/BSp21nTmSnNC537H0OHlCScpmc7G\ncpj+/jtLIhTN0IwKosaH3mJpQ3AcUI7IooFKgYrC/bwCUQ5xX7CwqaOzTKf3MtX1\nngd7mPWTFkRDxCkCnvfUfcem\n-----END PRIVATE KEY-----";
var rsa = RSA.Create();
var privateKey = privateKeyPem.Replace("-----BEGIN PRIVATE KEY-----", string.Empty).Replace("-----END PRIVATE KEY-----", string.Empty);
privateKey = privateKey.Replace("\n", string.Empty);
privateKey = privateKey.Replace("\r\n", string.Empty);
var privateKeyBytes = Convert.FromBase64String(privateKey);
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out int _);
return rsa.SignData(message, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
I don't like this manual approach, so i used the .net Cryptography classes to create and sign the JWT Token:
private string GetToken2()
{
// keeping only the payload of the key
var privateKeyPem = "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5Cg7FW0NGwLeD\nrpc0r1Ayta23GxVw0KCA+d/TjSyuZ3lmKiObz9EGJpHSHbX4yrODA6FvYixUrAKm\nUSSMvFLUpYM2xoEgAKnwd6XVgdnjwk7wnIIsEzlJR5t9tWKLd1VL1133w6jigLv5kDzWQTLAoGBAL0B\n7fS672RBBgOgOtRVhWV7qYvq4aE0bkfRXfxD1GYWnzc6RoyUuvA2glpo9ARzB7ut\nR5LXq5GAwOBIzMhtZWzMZv7ypctiB5DYo/SMiBc7pAxTyE53O9jI5+8g/RB7WlUx\nsZt5bkf42zhsJwZnfV480Hx8GhnCnhGcTVjJbbN5AoGBAInRfNcLpgPtHWiQ5r9W\nANd+XDLpjIUQfh+0NaQeYPG7DM59oPRqUDs/BSp21nTmSnNC537H0OHlCScpmc7G\ncpj+/jtLIhTN0IwKosaH3mJpQ3AcUI7IooFKgYrC/bwCUQ5xX7CwqaOzTKf3MtX1\nngd7mPWTFkRDxCkCnvfUfcem\n-----END PRIVATE KEY-----\n";
var privateKey = privateKeyPem.Replace("-----BEGIN PRIVATE KEY-----", string.Empty).Replace("-----END PRIVATE KEY-----", string.Empty);
privateKey = privateKey.Replace("\n", string.Empty);
privateKey = privateKey.Replace(Environment.NewLine, string.Empty);
var privateKeyRaw = Convert.FromBase64String(privateKey);
// creating the RSA key
using var provider = new RSACryptoServiceProvider();
provider.ImportPkcs8PrivateKey(new ReadOnlySpan<byte>(privateKeyRaw), out _);
var rsaSecurityKey = new RsaSecurityKey(provider);
// Generating the token
var now = DateTime.UtcNow;
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, "10217931234509168826"),
new Claim("email", "myProject-dev.iam.gserviceaccount.com"),
};
var handler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken
(
"https://accounts.google.com",
"MyAudience",
claims,
now.AddMilliseconds(-30),
now.AddMinutes(60),
new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256)
);
return handler.WriteToken(token);
}
The Google documentation indicates how to sign the JWT:
Sign the UTF-8 representation of the input using SHA256withRSA (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function) with the private key obtained from the Google API Console.
Both the implementation strictly follow the google documentation guidelines, but the generated JWT also fails to validate. I tried several representations of the private key, i.e., replacing the '\n', leaving the '\n', etc, etc, but it always fail.
Both implementations seems correct, but something is missing!
Any ideas on what is missing ?? Thank you in advance.
--------------------- EDIT 1 ---------------------------
To guarantee that the Primary Key data is being correctly handled, and avoid string replacement and encoding, i used the BouncyCastle library which is a lightweight cryptography API, to handle the Primary Key. So i load the JSON KEY file directly, and use BouncyCastle to load the RSA Parameters:
private RSAParameters GetPrivateKeyRSAParameters()
{
var path = "c:\\myproject-key-3433434.json";
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
var credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(stream);
RSAParameters rsaParams;
using (var tr = new StringReader(credentialParameters.PrivateKey))
{
var pemReader = new PemReader(tr);
if (pemReader.ReadObject() is not AsymmetricKeyParameter key)
{
throw new Exception("Could not read private key");
}
var privateRsaParams = key as RsaPrivateCrtKeyParameters;
rsaParams = DotNetUtilities.ToRSAParameters(privateRsaParams);
}
return rsaParams;
}
This way, instead of relying in handling the Primary Key as text, everything is handled by BouncyCastle.
For code block 1)
var rsa = RSA.Create();
rsa.ImportParameters(GetPrivateKeyRSAParameters());
For code block 2)
var rsaSecurityKey = new RsaSecurityKey(GetPrivateKeyRSAParameters());
So now i have the guarantee that the Primary Key data is being correctly handled, but the end result is the same, the resulting token has always an "Invalid Signature".
--------------------- EDIT 2 ---------------------------
Google has an example on how to do this in JAVA, in this document.
public static String generateJwt(final String saKeyfile, final String saEmail,
final String audience, final int expiryLength)
throws FileNotFoundException, IOException {
Date now = new Date();
Date expTime = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiryLength));
JWTCreator.Builder token = JWT.create()
.withIssuedAt(now)
.withExpiresAt(expTime)
.withIssuer(saEmail)
.withAudience(audience)
.withSubject(saEmail)
.withClaim("email", saEmail);
FileInputStream stream = new FileInputStream(saKeyfile);
ServiceAccountCredentials cred = ServiceAccountCredentials.fromStream(stream);
RSAPrivateKey key = (RSAPrivateKey) cred.getPrivateKey();
Algorithm algorithm = Algorithm.RSA256(null, key);
return token.sign(algorithm);
}
Replicating the code to .net, i assume it would be as:
private string GetToken5(string path)
{
var now = DateTime.UtcNow;
var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, "10217931236909168826") };
var handler = new JwtSecurityTokenHandler();
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
var serviceAccountCredential = ServiceAccountCredential.FromServiceAccountData(stream);
var token = new JwtSecurityToken
(
"https://accounts.google.com",
"Audience",
claims,
now.AddMilliseconds(-30),
now.AddMinutes(60),
new SigningCredentials(new RsaSecurityKey(serviceAccountCredential.Key), SecurityAlgorithms.RsaSha256)
);
token.Header.Add("kid", "955104a37fa903e232339e83edb29b0c45");
return handler.WriteToken(token);
}
But, this also doesn't work.
There's still something missing ...

Coding wise, the last snippet is a correct way for generating a custom JWT for Google Auth.
The problem is that when a Service Account is created, it has a specific PUBLIC KEY which is used to verify the Token.
When creating the token with the Issuer as "https://accounts.google.com", when verifying the token, another public key was used, thus the validation failed.
The solution was to implement our own .well-known/openid-configuration Endpoint, and matching the Token claims with the information provided by this endpoint.

Related

AWS KMS signature returns Invalid Signature for my JWT

I am trying to generate a simple JWT, using ES256 using KMS. Everything looks fine to the naked eye. But I get "Invalid signature" when I test it through jwt.io. The code is quite simple:
public async Task<string> GenerateJwt(object payload)
{
var encodedHeader = Base64Encoder.EncodeBase64Url(JsonSerializer.Serialize(_header));
var encodedPayload = Base64Encoder.EncodeBase64Url(JsonSerializer.Serialize(payload));
var signatureData = Encoding.ASCII.GetBytes(encodedHeader + "." + encodedPayload);
var signature = await _signingService.Sign(signatureData);
var encodedSignature = Base64Encoder.ReplaceSpecialUrlCharacters(Convert.ToBase64String(signature));
return encodedHeader + "." + encodedPayload + "." + encodedSignature;
}
The SigningService looks something like this:
public async Task<byte[]> Sign(byte[] signatureData)
{
using var memoryStream = new MemoryStream(signatureData, 0, signatureData.Length);
var signRequest = new SignRequest()
{
KeyId = _signingKeyId,
Message = memoryStream,
SigningAlgorithm = SigningAlgorithmSpec.ECDSA_SHA_256
};
SignResponse signResponse = await _keyManagementService.SignAsync(signRequest);
return signResponse.Signature.ToArray();
}
_keyManagementService is an IAmazonKeyManagementService from AWSSDK.KeyManagementService 3.5.2.6
In KMS the key is set up like this:
Key Spec: ECC_NIST_P256
Key Usage: Sign and verify
Signing algorithms: ECDSA_SHA_256
Public key in KMS (using localstack)
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj7Cy/Gbx3jnuPdanuBSjuUmdnr7tQ/BcOytDlFoHWdNA1scc6RwNwqnNbmRE0BmwnlFNDVGxWU5oycTig8p0KQ==
Example on generated output
eyJhbGciOiJFUzI1NiJ9.eyJ0ZXN0MSI6MSwidGVzdDIiOiJ0d28iLCJ0ZXN0MyI6ZmFsc2V9.MEYCIQCiatnRhYGBKgdJj9LECe7mJ4bhhkVTvFSgpVI3Dm14pwIhAOrHAu0vqKvVwdgpAhaU7KOhiIBZdcEOuzfXrdXldCFQ
If it sign it with the built in tools (by just replacing the signatureData row above) with the same input I get it to validate in jwt.io.
var ecdsa = ECDsa.Create();
ecdsa.GenerateKey(ECCurve.NamedCurves.nistP256);
var signatureData = ecdsa.SignData(signatureData, HashAlgorithmName.SHA256);
Any input would be welcome as it feels like I've tested everything...
The problem was that KMS returns the signature in another format:
DER-encoded object as defined by ANS X9.62–2005
While the JWT should in the format R || S according to https://www.rfc-editor.org/rfc/rfc7515#appendix-A.3.1
So what I did as to add a convert method using BouncyCastle and called that before converting it to base64url:
private byte[] ConvertSignature(byte[] signature)
{
var asn1 = Asn1Object.FromByteArray(signature) as DerSequence;
if (asn1 == null)
return Array.Empty<byte>();
if (asn1.Count < 2)
return Array.Empty<byte>();
var r = asn1[0] as DerInteger;
var s = asn1[1] as DerInteger;
if (r == null || s == null)
return Array.Empty<byte>();
return Array.Empty<byte>()
.Concat(r.Value.ToByteArrayUnsigned())
.Concat(s.Value.ToByteArrayUnsigned())
.ToArray();
}

How to google oauth to an api? My example is not working

I am trying to do this article for google cloud build
https://cloud.google.com/endpoints/docs/openapi/service-account-authentication
I am guessing to use the service account email I generated the key from in that example AND for Audient, I put "" (which is probably the reason it's not working?). I have no idea and can't find what in the world to put for audience.
In addition to code below, I tried setting audience to 'https://cloudbuild.googleapis.com' which also did not work
My code is the following...
public class GenToken {
public static void main(String[] args) throws IOException {
Duration d = Duration.ofDays(365);
String tok = generateJwt("/Users/dean/workspace/order/java/googleBuild/orderly-gcp-key.json",
"mycloudbuilder#order-gcp.iam.gserviceaccount.com", "", d.toSeconds());
System.out.println("tok="+tok);
URL url = new URL("https://cloudbuild.googleapis.com/v1/projects/order-gcp/builds");
makeJwtRequest(tok, "GET", url);
}
public static String generateJwt(final String saKeyfile, final String saEmail,
final String audience, final long expiryLength)
throws FileNotFoundException, IOException {
Date now = new Date();
Date expTime = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiryLength));
// Build the JWT payload
JWTCreator.Builder token = JWT.create()
.withIssuedAt(now)
// Expires after 'expiraryLength' seconds
.withExpiresAt(expTime)
// Must match 'issuer' in the security configuration in your
// swagger spec (e.g. service account email)
.withIssuer(saEmail)
// Must be either your Endpoints service name, or match the value
// specified as the 'x-google-audience' in the OpenAPI document
.withAudience(audience)
// Subject and email should match the service account's email
.withSubject(saEmail)
.withClaim("email", saEmail);
// Sign the JWT with a service account
FileInputStream stream = new FileInputStream(saKeyfile);
ServiceAccountCredentials cred = ServiceAccountCredentials.fromStream(stream);
RSAPrivateKey key = (RSAPrivateKey) cred.getPrivateKey();
Algorithm algorithm = Algorithm.RSA256(null, key);
return token.sign(algorithm);
}
/**
* Makes an authorized request to the endpoint.
*/
public static String makeJwtRequest(final String signedJwt, String method, final URL url)
throws IOException, ProtocolException {
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod(method);
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Authorization", "Bearer " + signedJwt);
InputStreamReader reader = new InputStreamReader(con.getInputStream());
BufferedReader buffReader = new BufferedReader(reader);
String line;
StringBuilder result = new StringBuilder();
while ((line = buffReader.readLine()) != null) {
result.append(line);
}
buffReader.close();
return result.toString();
}
}
The orderly-gcp-key.json has these attributes in it
{
"type": "service_account",
"project_id": "myproj",
"private_key_id": "xxxxxxxx",
"private_key": "-----BEGIN PRIVATE KEY-----\nasdfsd\n-----END PRIVATE KEY-----\n",
"client_email": "build-ci-mine#myproj.iam.gserviceaccount.com",
"client_id": "1167333552",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/build-ci-mine%40myproj.iam.gserviceaccount.com"
}
oops, my edit didn't get posted :(. Here is the error
Exception in thread "main" java.io.IOException: Server returned HTTP response code: 401 for URL: https://cloudbuild.googleapis.com/v1/projects/orderly-gcp/builds
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1919)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1515)
at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:250)
at com.orderlyhealth.auth.websecure.GenToken.makeJwtRequest(GenToken.java:71)
at com.orderlyhealth.auth.websecure.GenToken.main(GenToken.java:26)
I hope that I better understood!!
When you try to reach a Google API, you have to use an access token. I have 2 code snippets for you.
Use Google Http client
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
HttpRequestFactory factory = new NetHttpTransport().createRequestFactory(new HttpCredentialsAdapter(credentials));
HttpRequest request = factory.buildGetRequest(new GenericUrl("https://cloudbuild.googleapis.com/v1/projects/gbl-imt-homerider-basguillaueb/builds"));
HttpResponse httpResponse = request.execute();
System.out.println(CharStreams.toString(new InputStreamReader(httpResponse.getContent(), Charsets.UTF_8)));
Use pure java connection
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
HttpURLConnection con = (HttpURLConnection) new URL("https://cloudbuild.googleapis.com/v1/projects/gbl-imt-homerider-basguillaueb/builds").openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Authorization", "Bearer " + credentials.refreshAccessToken().getTokenValue());
InputStreamReader reader = new InputStreamReader(con.getInputStream());
BufferedReader buffReader = new BufferedReader(reader);
String line;
StringBuilder result = new StringBuilder();
while ((line = buffReader.readLine()) != null) {
result.append(line);
}
buffReader.close();
System.out.println(result.toString());
You can rely on the platform environment. In local, perform a gcloud auth application-default login to set your credential as default default credential. On GCP, the component identity (the default service account or the service account that you define when you create the component), is used thanks to the method GoogleCredentials.getApplicationDefault();
Your dependency management need this (here in maven)
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>0.20.0</version>
</dependency>
Does this solve your issue?

Has anyone got a guide on how to upgrade from PowerBi Embeded v2 to v3? Or a tutorial for v3?

This appears to be a nightmare, sure its easy to upgrade the nuget package to 3.11 I think the latest is, but then nothing at all compiles. So you fix the compile errors, and then it doesn't work. I'm getting an error when it tries to create the PowerBI client.
Getting the token and also creating the client appears to be totally different to v2.
This is my code:
public PowerBiConfig GetPowerBiConfig(string reportId)
{
var result = new PowerBiConfig();
try
{
if (!Guid.TryParse(reportId, out var _))
{
result.ErrorMessage = $"Invalid report guid: {reportId}";
return result;
}
var credential = new UserPasswordCredential(_powerBiProMasterUsername, _powerBiProMasterPassword);
var authenticationContext = new AuthenticationContext(AuthorityUrl);
// Taken from https://stackoverflow.com/questions/5095183/how-would-i-run-an-async-taskt-method-synchronously
var authenticationResult = authenticationContext.AcquireTokenAsync(ResourceUrl, dataArchiverSettings.PowerBiApplicationId, credential).GetAwaiter().GetResult();
if (authenticationResult == null)
{
result.ErrorMessage = "Authentication Failed.";
return result;
}
var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");
using (var client = new PowerBIClient(new Uri(ApiUrl), tokenCredentials))
{
var report = client.Reports.GetReportInGroup(dataArchiverSettings.PowerBiWorkspaceId, reportId);
if (report == null)
{
result.ErrorMessage = $"No report with the ID {reportId} was found in the workspace.";
return result;
}
var datasets = client.Datasets.GetDatasetById(dataArchiverSettings.PowerBiWorkspaceId, report.DatasetId);
result.IsEffectiveIdentityRequired = datasets.IsEffectiveIdentityRequired;
result.IsEffectiveIdentityRolesRequired = datasets.IsEffectiveIdentityRolesRequired;
GenerateTokenRequest tokenRequest;
if (datasets.IsEffectiveIdentityRequired == true)
{
var username = UserHelper.GetCurrentUser();
var roles = _userService.GetRolesForUser(username);
tokenRequest = new GenerateTokenRequest(accessLevel: "view",
identities: new List<EffectiveIdentity>
{
new EffectiveIdentity(username: username,
roles: new List<string> (roles.Select(x=> x.RoleName)),
datasets: new List<string> {datasets.Id})
});
}
else
{
tokenRequest = new GenerateTokenRequest(accessLevel: "view");
}
var tokenResponse =
client.Reports.GenerateTokenInGroup(dataArchiverSettings.PowerBiWorkspaceId, report.Id,
tokenRequest);
if (tokenResponse == null)
{
result.ErrorMessage = "Failed to generate embed token.";
return result;
}
// Generate Embed Configuration.
result.EmbedToken = tokenResponse;
result.EmbedUrl = report.EmbedUrl;
result.Id = report.Id.ToString();
result.WorkloadResourceName = dataArchiverSettings.PowerBiWorkloadResourceName.Trim();
}
}
catch (HttpOperationException exc)
{
result.ErrorMessage =
$"Status: {exc.Response.StatusCode} ({(int)exc.Response.StatusCode})\r\n" +
$"Response: {exc.Response.Content}\r\n" +
$"RequestId: {exc.Response.Headers["RequestId"].FirstOrDefault()}";
}
catch (Exception exc)
{
result.ErrorMessage = exc.ToString();
}
return result;
}
The closest to "upgrade guide" is the announcement in Power BI blog. It looks like your code is using v2 (e.g. reportId is string, while in v3 it should be Guid).
Here is a brief summary of the changes:
What you should know about v3
Here are the key changes with this version update:
Namespaces renaming:
Microsoft.PowerBI.Api.V2 was changed to Microsoft.PowerBI.Api
Microsoft.PowerBI.Api.Extensions.V2 was changed to Microsoft.PowerBI.Api.Extensions
Microsoft.PowerBI.Api.V1 namespace was removed.
SetAllConnections and SetAllConnectionsInGroup operations are deprecated and marked as obsolete. You should use UpdateDatasources or UpdateParameters APIs instead.
PowerBI artifacts IDs typing was changed* from string to Guid, we recommend to work with Guid when possible.
*Dataset ID is an exception and it’s typing will remain string.
ODataResponse[List[Object]] types was changed to Objects, thus returning an objects collection on responses. For example, a response of ODataResponse[List[Report]] type will now return Reports collection as the return type.
New credentials classes allow easier build of credentialDetails. The new classes include: BasicCredentials, WindowsCredentials, OAuth2Credentials, and more.
Read Configure credentials article to learn more.
New encryption helper classes for easier encryption when creating CredentialDetails.
For example, using AsymmetricKeyEncryptor class with a gateway public key:
GatewayPublicKey publicKey = new GatewayPublicKey
{
Exponent = "...",
Modulus = "..."
};
CredentialsBase credentials = new BasicCredentials("<USER>", "<PASSWORD>");
var credentialsEncryptor = new AsymmetricKeyEncryptor(publicKey);
var credentialDetails = new CredentialDetails(credentials, PrivacyLevel.None, EncryptedConnection.Encrypted, credentialsEncryptor);
Read Configure credentials article to learn more.
Consistency on field names.
For example, reportKey, datasetKey, dashboardKey and tileKey was changed to reportId, datasetId, dashboardId and tileId.
Consistency on operations names.
For example, use GetDataset instead of GetDatasetById. The effected opertation names are imports, datasets, gateways and datasources.
Use enum class instead of string for enumerated types.
For example, In the generateTokenRequest, we recommend to use TokenAccessLevel.View, and not explicitly use “view” as value.
Required fields was marked – some fields was changed to required fields are not nullable anymore.
Examples
Change in Get Reports call if WorkspaceId is a string:
var reports = await client.Reports.GetReportsInGroupAsync(WorkspaceId);
var reports = await client.Reports.GetReportsInGroupAsync(new Guid( WorkspaceId ) );
Change in response handling if a string is expected:
report = reports.Value.FirstOrDefault(r => r.Id.Equals(ReportId, StringComparison.InvariantCultureIgnoreCase));
report = reports.Value.FirstOrDefault(r => r.Id .ToString() .Equals(ReportId, StringComparison.InvariantCultureIgnoreCase));
Change in Generate token:
var tokenResponse = await client.Reports.GenerateTokenInGroupAsync(WorkspaceId, report.Id, generateTokenRequestParameters);
var tokenResponse = await client.Reports.GenerateTokenInGroupAsync( new Guid( WorkspaceId ), report.Id, generateTokenRequestParameters);
Change in Generate token response handling if a string is expected:
m_embedConfig.Id = report.Id;
m_embedConfig.Id = report.Id .ToString() ;
Required fields are not nullable, i.e. Expiration is not nullable and the Value property should be removed:
var minutesToExpiration = EmbedToken.Expiration .Value – DateTime.UtcNow;
var minutesToExpiration = EmbedToken.Expiration – DateTime.UtcNow;
Consistency on operations names, i.e. use GetDataset instead of GetDatasetById:
var datasets = await client.Datasets.GetDataset ById InGroupAsync(WorkspaceId, report.DatasetId);
var datasets = await client.Datasets.GetDatasetInGroupAsync(new Guid(WorkspaceId), report.DatasetId);

Adding Basic authentication params to HttpSolrClient using invariantParams

I am trying to write a SOLR client using SolrJ HttpSolrClient.
I have to use Basic Authentication.
The documentation for SolrJ HttpSolrClient says that there is:
protected ModifiableSolrParams invariantParams
and the description is:
Parameters that are added to every request regardless. This may be a place to add something like an authentication token.
Can someone who has used this before please tell me how to use the invariantParams for basic auth?
public class MySolrClient extends HttpSolrClient{
private String hostUrl;
private String userName;
private String password;
public CloudForgeProcurementSolrClient(final String hostUrl, final String userName, final String password) {
super(hostUrl);
this.hostUrl = hostUrl;
this.userName = userName;
this.password = password;
if (invariantParams == null) {
invariantParams = new ModifiableSolrParams();
}
// invariantParams.add(arg0, arg1);
}
}
What should be arg0 and arg1 be in my case.
Thanks in advance!
You can do something like
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
"myusername","mypassword");
provider.setCredentials(AuthScope.ANY, credentials);
HttpClient client = HttpClientBuilder.create().setDefaultCredentialsProvider(provider).build();
HttpSolrClient solrClient = new HttpSolrClient(solrCoreUrl, client);
you can use below code:
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new
UsernamePasswordCredentials(username,password);
provider.setCredentials(AuthScope.ANY, credentials);
HttpClient client = HttpClientBuilder.create().setDefaultCredentialsProvider(provider).build();
HttpSolrClient solrClient = new HttpSolrClient.Builder()
.withBaseSolrUrl(host)
.withHttpClient(client).build();

Unable to verify secret hash for client in Amazon Cognito Userpools

I am stuck at "Amazon Cognito Identity user pools" process.
I tried all possible codes for authenticating user in cognito userpools. But I always get error saying "Error: Unable to verify secret hash for client 4b*******fd".
Here is code:
AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:b64bb629-ec73-4569-91eb-0d950f854f4f'
});
AWSCognito.config.region = 'us-east-1';
AWSCognito.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:b6b629-er73-9969-91eb-0dfffff445d'
});
AWSCognito.config.update({accessKeyId: 'AKIAJNYLRONAKTKBXGMWA', secretAccessKey: 'PITHVAS5/UBADLU/dHITesd7ilsBCm'})
var poolData = {
UserPoolId : 'us-east-1_l2arPB10',
ClientId : '4bmsrr65ah3oas5d4sd54st11k'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var userData = {
Username : 'ronakpatel#gmail.com',
Pool : userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.confirmRegistration('123456', true,function(err, result) {
if (err) {
alert(err);
return;
}
console.log('call result: ' + result);
});
It seems that currently AWS Cognito doesn't handle client secret perfectly. It will work in the near future but as for now it is still a beta version.
For me it is working fine for an app without a client secret but fails for an app with a client secret.
So in your user pool try to create a new app without generating a client secret. Then use that app to signup a new user or to confirm registration.
According to the Docs: http://docs.aws.amazon.com/cognito/latest/developerguide/setting-up-the-javascript-sdk.html
The Javascript SDK doesn't support Apps with a Client Secret.
The instructions now state that you need to uncheck the "Generate Client Secret" when creating the app for the User Pool.
This might be a fews years late but just uncheck the "Generate client secret" option" and it will work for your web clients.
Since everyone else has posted their language, here's node (and it works in the browser with browserify-crypto, automatically used if you use webpack or browserify):
const crypto = require('crypto');
...
crypto.createHmac('SHA256', clientSecret)
.update(username + clientId)
.digest('base64')
I had the same problem in the .net SDK.
Here's how I solved in, in case anyone else needs it:
public static class CognitoHashCalculator
{
public static string GetSecretHash(string username, string appClientId, string appSecretKey)
{
var dataString = username + appClientId;
var data = Encoding.UTF8.GetBytes(dataString);
var key = Encoding.UTF8.GetBytes(appSecretKey);
return Convert.ToBase64String(HmacSHA256(data, key));
}
public static byte[] HmacSHA256(byte[] data, byte[] key)
{
using (var shaAlgorithm = new System.Security.Cryptography.HMACSHA256(key))
{
var result = shaAlgorithm.ComputeHash(data);
return result;
}
}
}
Signing up then looks like this:
public class CognitoSignUpController
{
private readonly IAmazonCognitoIdentityProvider _amazonCognitoIdentityProvider;
public CognitoSignUpController(IAmazonCognitoIdentityProvider amazonCognitoIdentityProvider)
{
_amazonCognitoIdentityProvider = amazonCognitoIdentityProvider;
}
public async Task<bool> SignUpAsync(string userName, string password, string email)
{
try
{
var request = CreateSignUpRequest(userName, password, email);
var authResp = await _amazonCognitoIdentityProvider.SignUpAsync(request);
return true;
}
catch
{
return false;
}
}
private static SignUpRequest CreateSignUpRequest(string userName, string password, string email)
{
var clientId = ConfigurationManager.AppSettings["ClientId"];
var clientSecretId = ConfigurationManager.AppSettings["ClientSecretId"];
var request = new SignUpRequest
{
ClientId = clientId,
SecretHash = CognitoHashCalculator.GetSecretHash(userName, clientId, clientSecretId),
Username = userName,
Password = password,
};
request.UserAttributes.Add("email", email);
return request;
}
}
Amazon mention how Computing SecretHash Values for Amazon Cognito in their documentation with Java application code. Here this code works with boto 3 Python SDK.
You can find your App clients in left side menu under General settings. Get those App client id and App client secret to create SECRET_HASH. For your better understand I commented out all the outputs of each and every line.
import hashlib
import hmac
import base64
app_client_secret = 'u8f323eb3itbr3731014d25spqtv5r6pu01olpp5tm8ebicb8qa'
app_client_id = '396u9ekukfo77nhcfbmqnrec8p'
username = 'wasdkiller'
# convert str to bytes
key = bytes(app_client_secret, 'latin-1') # b'u8f323eb3itbr3731014d25spqtv5r6pu01olpp5tm8ebicb8qa'
msg = bytes(username + app_client_id, 'latin-1') # b'wasdkiller396u9ekukfo77nhcfbmqnrec8p'
new_digest = hmac.new(key, msg, hashlib.sha256).digest() # b'P$#\xd6\xc1\xc0U\xce\xc1$\x17\xa1=\x18L\xc5\x1b\xa4\xc8\xea,\x92\xf5\xb9\xcdM\xe4\x084\xf5\x03~'
SECRET_HASH = base64.b64encode(new_digest).decode() # UCQj1sHAVc7BJBehPRhMxRukyOoskvW5zU3kCDT1A34=
In the boto 3 documentation, we can see lot of time ask about SECRET_HASH. So above code lines help you to create this SECRET_HASH.
If you don't want to use SECRET_HASH just uncheck Generate client secret when creating an app.
For anybody interested in using AWS Lambda to sign up a user using the AWS JS SDK, these are the steps I did:
Create another lambda function in python to generate the key:
import hashlib
import hmac
import base64
secretKey = "key"
clientId = "clientid"
digest = hmac.new(secretKey,
msg=username + clientId,
digestmod=hashlib.sha256
).digest()
signature = base64.b64encode(digest).decode()
Call the function through the nodeJS function in AWS. The signature acted as the secret hash for Cognito
Note: The answer is based heavily off George Campbell's answer in the following link: Calculating a SHA hash with a string + secret key in python
Solution for golang. Seems like this should be added to the SDK.
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
)
func SecretHash(username, clientID, clientSecret string) string {
mac := hmac.New(sha256.New, []byte(clientSecret))
mac.Write([]byte(username + ClientID))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
Solution for NodeJS with SecretHash
It seems silly that AWS removed the secret key from the SDK as it will not be exposed in NodeJS.
I got it working in NodeJS by intercepting fetch and adding in the hashed key using #Simon Buchan's answer.
cognito.js
import { CognitoUserPool, CognitoUserAttribute, CognitoUser } from 'amazon-cognito-identity-js'
import crypto from 'crypto'
import * as fetchIntercept from './fetch-intercept'
const COGNITO_SECRET_HASH_API = [
'AWSCognitoIdentityProviderService.ConfirmForgotPassword',
'AWSCognitoIdentityProviderService.ConfirmSignUp',
'AWSCognitoIdentityProviderService.ForgotPassword',
'AWSCognitoIdentityProviderService.ResendConfirmationCode',
'AWSCognitoIdentityProviderService.SignUp',
]
const CLIENT_ID = 'xxx'
const CLIENT_SECRET = 'xxx'
const USER_POOL_ID = 'xxx'
const hashSecret = (clientSecret, username, clientId) => crypto.createHmac('SHA256', clientSecret)
.update(username + clientId)
.digest('base64')
fetchIntercept.register({
request(url, config) {
const { headers } = config
if (headers && COGNITO_SECRET_HASH_API.includes(headers['X-Amz-Target'])) {
const body = JSON.parse(config.body)
const { ClientId: clientId, Username: username } = body
// eslint-disable-next-line no-param-reassign
config.body = JSON.stringify({
...body,
SecretHash: hashSecret(CLIENT_SECRET, username, clientId),
})
}
return [url, config]
},
})
const userPool = new CognitoUserPool({
UserPoolId: USER_POOL_ID,
ClientId: CLIENT_ID,
})
const register = ({ email, password, mobileNumber }) => {
const dataEmail = { Name: 'email', Value: email }
const dataPhoneNumber = { Name: 'phone_number', Value: mobileNumber }
const attributeList = [
new CognitoUserAttribute(dataEmail),
new CognitoUserAttribute(dataPhoneNumber),
]
return userPool.signUp(email, password, attributeList, null, (err, result) => {
if (err) {
console.log((err.message || JSON.stringify(err)))
return
}
const cognitoUser = result.user
console.log(`user name is ${cognitoUser.getUsername()}`)
})
}
export {
register,
}
fetch-inceptor.js (Forked and edited for NodeJS from Fork of https://github.com/werk85/fetch-intercept/blob/develop/src/index.js)
let interceptors = []
if (!global.fetch) {
try {
// eslint-disable-next-line global-require
global.fetch = require('node-fetch')
} catch (err) {
throw Error('No fetch available. Unable to register fetch-intercept')
}
}
global.fetch = (function (fetch) {
return (...args) => interceptor(fetch, ...args)
}(global.fetch))
const interceptor = (fetch, ...args) => {
const reversedInterceptors = interceptors.reduce((array, _interceptor) => [_interceptor].concat(array), [])
let promise = Promise.resolve(args)
// Register request interceptors
reversedInterceptors.forEach(({ request, requestError }) => {
if (request || requestError) {
promise = promise.then(_args => request(..._args), requestError)
}
})
// Register fetch call
promise = promise.then(_args => fetch(..._args))
// Register response interceptors
reversedInterceptors.forEach(({ response, responseError }) => {
if (response || responseError) {
promise = promise.then(response, responseError)
}
})
return promise
}
const register = (_interceptor) => {
interceptors.push(_interceptor)
return () => {
const index = interceptors.indexOf(_interceptor)
if (index >= 0) {
interceptors.splice(index, 1)
}
}
}
const clear = () => {
interceptors = []
}
export {
register,
clear,
}
A quick fix for the above mentioned problem statement would be to delete the existing "App Client" and crate a new one with unchecked Generate client secret
Note : Don't forget to change the app client string in the code.
In Java you could use this code:
private String getSecretHash(String email, String appClientId, String appSecretKey) throws Exception {
byte[] data = (email + appClientId).getBytes("UTF-8");
byte[] key = appSecretKey.getBytes("UTF-8");
return Base64.encodeAsString(HmacSHA256(data, key));
}
static byte[] HmacSHA256(byte[] data, byte[] key) throws Exception {
String algorithm = "HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data);
}
this is a sample php code that I use to generate the secret hash
<?php
$userId = "aaa";
$clientId = "bbb";
$clientSecret = "ccc";
$s = hash_hmac('sha256', $userId.$clientId, $clientSecret, true);
echo base64_encode($s);
?>
in this case the result is:
DdSuILDJ2V84zfOChcn6TfgmlfnHsUYq0J6c01QV43I=
for JAVA and .NET you need to pass the secret has in the auth parameters with the name SECRET_HASH.
AdminInitiateAuthRequest request = new AdminInitiateAuthRequest
{
ClientId = this.authorizationSettings.AppClientId,
AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH,
AuthParameters = new Dictionary<string, string>
{
{"USERNAME", username},
{"PASSWORD", password},
{
"SECRET_HASH", EncryptionHelper.GetSecretHash(username, AppClientId, AppClientSecret)
}
},
UserPoolId = this.authorizationSettings.UserPoolId
};
And it should work.
The crypto package for javascript is deprecated so using crypto-js:
import CryptoJS from 'crypto-js';
import Base64 from 'crypto-js/enc-base64';
const secretHash = Base64.stringify(CryptoJS.HmacSHA256(username + clientId, clientSecret));
Remeber to run npm install #types/crypto-js crypto-js before
C++ with the Qt Framework
QByteArray MyObject::secretHash(
const QByteArray& email,
const QByteArray& appClientId,
const QByteArray& appSecretKey)
{
QMessageAuthenticationCode code(QCryptographicHash::Sha256);
code.setKey(appSecretKey);
code.addData(email);
code.addData(appClientId);
return code.result().toBase64();
};
Here is my 1 command, and it works (Confirmed :))
EMAIL="EMAIL#HERE.com" \
CLIENT_ID="[CLIENT_ID]" \
CLIENT_SECRET="[CLIENT_ID]" \
&& SECRET_HASH=$(echo -n "${EMAIL}${CLIENT_ID}" | openssl dgst -sha256 -hmac "${CLIENT_SECRET}" | xxd -r -p | openssl base64) \
&& aws cognito-idp ... --secret-hash "${SECRET_HASH}"
This solution works in March 2021:
In case you're working with a client which has both "client_secret" and "client_id" generated, instead of calculating the SECRET_HASH and providing it to the function as specified in AWS docs, pass the "client_secret".
Note: I was trying to generate new tokens from the refresh token.
let result = await cognitoIdentityServiceProvidor
.initiateAuth({
AuthFlow: "REFRESH_TOKEN",
ClientId: clientId,
AuthParameters: {
REFRESH_TOKEN: refresh_token,
SECRET_HASH: clientSecret,
},
})
.promise();
It's absurd, but it works!
There might be a more compact version, but this works for Ruby, specifically in Ruby on Rails without having to require anything:
key = ENV['COGNITO_SECRET_HASH']
data = username + ENV['COGNITO_CLIENT_ID']
digest = OpenSSL::Digest.new('sha256')
hmac = Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))
NodeJS solution:
Compute secret hash for authenticating action:
import * as crypto from 'crypto';
const secretHash = crypto
.createHmac('SHA256', clientSecret)
.update(email + clientId)
.digest('base64');
Compute secret hash for refresh token action:
import * as crypto from 'crypto';
const secretHash = crypto
.createHmac('SHA256', clientSecret)
.update(sub + clientId)
.digest('base64');
The parameter object looks like this:
const authenticateParams = {
ClientId: clientId,
UserPoolId: poolId,
AuthFlow: CognitoAuthFlow.ADMIN_NO_SRP_AUTH,
AuthParameters: {
PASSWORD: password,
USERNAME: email,
SECRET_HASH: secretHash,
},
};
const refreshTokenParams = {
ClientId: clientId,
UserPoolId: poolId,
AuthFlow: CognitoAuthFlow.REFRESH_TOKEN_AUTH,
AuthParameters: {
REFRESH_TOKEN: refreshToken,
SECRET_HASH: secretHash,
},
};
Usage:
import * as CognitoIdentityProvider from 'aws-sdk/clients/cognitoidentityserviceprovider';
const provider = new CognitoIdentityProvider({ region });
provider.adminInitiateAuth(params).promise(); // authenticateParams or refreshTokenParams, return a promise object.
Cognito Authentication
Error: App client is not configured for secret but secret hash was received
Providing secretKey as nil worked for me. Credentials provided include :-
CognitoIdentityUserPoolRegion (region)
CognitoIdentityUserPoolId
(userPoolId)
CognitoIdentityUserPoolAppClientId (ClientId)
AWSCognitoUserPoolsSignInProviderKey (AccessKeyId)
// setup service configuration
let serviceConfiguration = AWSServiceConfiguration(region: CognitoIdentityUserPoolRegion, credentialsProvider: nil)
// create pool configuration
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: CognitoIdentityUserPoolAppClientId,
clientSecret: nil,
poolId: CognitoIdentityUserPoolId)
// initialize user pool client
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)
All above things work with below linked code sample.
AWS Sample code : https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift
Let me know if that doesn't work for you.
The below seems to work with .NET now, for asp.net pages using the Alexa Skills SDK for .NET by Time Heur
Inject dependency
private readonly CognitoUserManager<CognitoUser> _userManager;
public RegisterModel(
UserManager<CognitoUser> userManager,
)
_userManager = userManager as CognitoUserManager<CognitoUser> as CognitoUserManager<CognitoUser>;
Then assign a hash
var user = _pool.GetUser(Input.UserName);
_userManager.PasswordHasher.HashPassword(user,Input.Password);
var result = await _userManager.CreateAsync(user, Input.Password);
I saw a .NET one suggested here, but here is the variation that worked for me since I couldn't find access to "EncryptionHelper.GetSecretHash":
private string GetHMAC(string text, string key)
{
// TODO: null checks or whatever you want on your inputs...
using (var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
var hash = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(text));
return Convert.ToBase64String(hash);
}
}
And you call this for something like a sign up request as follows:
SignUpRequest signUpRequest = new SignUpRequest
{
ClientId = "<your_client_app_id>",
Password = "<the-password-your-user-wanted>",
Username = "<the-username-your-user-wanted",
};
// TODO: add whatever else you need to on your sign up request (like email, phone number etc...)
// and the magic line right here:
signUpRequest.SecretHash = GetHMAC(
signUpRequest.Username + "<your_client_app_id>",
"<your_client_app_secret>");
SignUpResponse response = await _provider.SignUpAsync(signUpRequest);
For me this worked like a charm. I originally was putting the client app secret directly assigned to this "SecretHash" property, but from scanning the rest of the answers here, I realized I truly needed to hash some data using that key as an input to the hash.