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?
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);
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.