I am writing a Lambda function in Go to authenticate a user, the AccessToken/IdToken I want to use for subsequent API calls.
When I execute the Go code from a standalone program, it works, the InitiateAuth was successful.
When I tried to use the same code from the lambda function, I get an error NotAuthorizedException: Unable to verify secret hash for client .......
Here is the code snippet I am using
func AuthenticateUser(userName string, passWord string) (*cognitoidentityprovider.InitiateAuthOutput, error) {
username := aws.String(userName)
password := aws.String(passWord)
clientID := aws.String(constants.COGNITO_APP_CLIENT_ID)
params := &cognitoidentityprovider.InitiateAuthInput{
AuthFlow: aws.String("USER_PASSWORD_AUTH"),
AuthParameters: map[string]*string{
"USERNAME": username,
"PASSWORD": password,
},
ClientId: clientID,
}
authResponse, authError := cognitoClient.InitiateAuth(params)
if authError != nil {
fmt.Println("Error = ", authError)
return nil, authError
}
fmt.Println(authResponse)
fmt.Println(*authResponse.Session)
return authResponse, nil
}
I have given sufficient permissions to the lambda user
- cognito-idp:AdminCreateUser
- cognito-idp:AdminDeleteUser
- cognito-idp:InitiateAuth
- cognito-idp:ChangePassword
- cognito-idp:AdminRespondToAuthChallenge
- cognito-idp:AdminInitiateAuth
- cognito-idp:ConfirmForgotPassword
Am I missing something here?
When we create a new App client, by default it has an associated App client secret.
I created one more app client, without "Client Secret". I used this new App client.
I modified the code to use the API AdminInitiateAuth, instead of the InitiateAuth
I was able to successfully login.
Here is the reference link, which was useful - Amplify "Unable to verify secret hash for client"
Related
I'm developing a Javascript (browser) client for HTTP API's in AWS API Gateway.The API's use an IAM authorizer. In my Javascript App I log in through a Cognito Identity Pool (developer identity). Next I convert the OpenID token into an access key id, secret access key and session token using AWS.CognitoIdentityCredentials.
I then want to use these credentials to make the API call, using the code below. I see the call being executed, but I get a HTTP/403 error back. The reply does not contain any further indication of the cause. I'd appreciate all help to understand what is going wrong. When disabling the IAM authorizer, the HTTP API works nicely.
I also tried the JWT authorizer, passing the OpenID token received from the Cognito Identity Pool (using http://cognito-identity.amazon.com as provider). When doing so I get the error: Bearer scope="" error="invalid_token" error_description="unable to decode "n" from RSA public key" in the www-authenticate response header.
Thanks a lot.
// Credentials will be available when this function is called.
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
let test_url = 'https://xxxxxxx.execute-api.eu-central-1.amazonaws.com/yyyyyy';
var httpRequest = new AWS.HttpRequest("https://" + test_url, "eu-central-1");
httpRequest.method = "GET";
AWS.config.credentials = {
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: sessionToken
}
var v4signer = new AWS.Signers.V4(httpRequest, "execute-api");
v4signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
fetch(httpRequest.endpoint.href , {
method: httpRequest.method,
headers: httpRequest.headers,
//body: httpRequest.body
}).then(function (response) {
if (!response.ok) {
$('body').html("ERROR: " + JSON.stringify(response.blob()));
return;
}
$('body').html("SUCCESS: " + JSON.stringify(response.blob()));
});
After some debugging and searching the web, I found the solution. Generating a correct signature seems to require a 'host' header:
httpRequest.headers.host = 'xxxxxxx.execute-api.eu-central-1.amazonaws.com'
After adding this host header the API call succeeds.
My express server has a credentials.json containing credentials for a google service account. These credentials are used to get a jwt from google, and that jwt is used by my server to update google sheets owned by the service account.
var jwt_client = null;
// load credentials form a local file
fs.readFile('./private/credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Sheets API.
authorize(JSON.parse(content));
});
// get JWT
function authorize(credentials) {
const {client_email, private_key} = credentials;
jwt_client = new google.auth.JWT(client_email, null, private_key, SCOPES);
}
var sheets = google.sheets({version: 'v4', auth: jwt_client });
// at this point i can call google api and make authorized requests
The issue is that I'm trying to move from node/express to npm serverless/aws. I'm using the same code but getting 403 - forbidden.
errors:
[ { message: 'The request is missing a valid API key.',
domain: 'global',
reason: 'forbidden' } ] }
Research has pointed me to many things including: AWS Cognito, storing credentials in environment variables, custom authorizers in API gateway. All of these seem viable to me but I am new to AWS so any advice on which direction to take would be greatly appreciated.
it is late, but may help someone else. Here is my working code.
const {google} = require('googleapis');
const KEY = require('./keys');
const _ = require('lodash');
const sheets = google.sheets('v4');
const jwtClient = new google.auth.JWT(
KEY.client_email,
null,
KEY.private_key,
[
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/spreadsheets'
],
null
);
async function getGoogleSheetData() {
await jwtClient.authorize();
const request = {
// The ID of the spreadsheet to retrieve data from.
spreadsheetId: 'put your id here',
// The A1 notation of the values to retrieve.
range: 'put your range here', // TODO: Update placeholder value.
auth: jwtClient,
};
return await sheets.spreadsheets.values.get(request)
}
And then call it in the lambda handler. There is one thing I don't like is storing key.json as file in the project root. Will try to find some better place to save.
I'm trying to invoke a mutation from lambda (specifically using golang). I used AWS_IAM as the authentication method of my AppSync API. I also give appsync:GraphQL permission to my lambda.
However, after looking at the docs here: https://docs.aws.amazon.com/sdk-for-go/api/service/appsync/
I can't find any documentation on how to invoke the appsync from the library. Can anyone point me to the right direction here?
P.S. I don't want to query or subscribe or anything else from lambda. It's just a mutation
Thanks!
------UPDATE-------
Thanks to #thomasmichaelwallace for informing me to use https://godoc.org/github.com/machinebox/graphql
Now the problem is how can I sign the request from that package using aws v4?
I found a way of using plain http.Request and AWS v4 signing. (Thanks to #thomasmichaelwallace for pointing this method out)
client := new(http.Client)
// construct the query
query := AppSyncPublish{
Query: `
mutation ($userid: ID!) {
publishMessage(
userid: $userid
){
userid
}
}
`,
Variables: PublishInput{
UserID: "wow",
},
}
b, err := json.Marshal(&query)
if err != nil {
fmt.Println(err)
}
// construct the request object
req, err := http.NewRequest("POST", os.Getenv("APPSYNC_URL"), bytes.NewReader(b))
if err != nil {
fmt.Println(err)
}
req.Header.Set("Content-Type", "application/json")
// get aws credential
config := aws.Config{
Region: aws.String(os.Getenv("AWS_REGION")),
}
sess := session.Must(session.NewSession(&config))
//sign the request
signer := v4.NewSigner(sess.Config.Credentials)
signer.Sign(req, bytes.NewReader(b), "appsync", "ap-southeast-1", time.Now())
//FIRE!!
response, _ := client.Do(req)
//print the response
buf := new(bytes.Buffer)
buf.ReadFrom(response.Body)
newStr := buf.String()
fmt.Printf(newStr)
The problem is that that API/library is designed to help you create/update app-sync instances.
If you want to actually invoke them then you need to POST to the GraphQL endpoint.
The easiest way for testing is to sign-in to the AWS AppSync Console, press the 'Queries' button in the sidebar and then enter and run your mutation.
I'm not great with go, but from what I can see there are client libraries for GraphQL in golang (e.g. https://godoc.org/github.com/machinebox/graphql).
If you are using IAM then you'll need to sign your request with a v4 signature (see this article for details: https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html)
I'm trying to invoke my API Gateway with authenticated users with the REST API. For this I'm using: Cognito UserPool + Cognito Identity Pool + API Gateway + AWS_IAM Authorization + Cognito Credentials. From what I've gathered I need to sign my request with (temporary credentials). Based on this thread I want to sign my request with the following keys:
{
SecretKey: '',
AccesKeyId: '',
SessionKey: ''
}
If I use an associated user from my IAM console and use the corresponding SecretKey + AccesKeyID everything works fine. However, I want to use the Unauthenticated and Authenticated roles from my Identity Pools to apply IAM policies based on authenticated or unauthenticated users.
FYI: I can call the authenticated functions from this part of the documentation.
I'm building a React-Native app, and because of that I want to keep the native SDK to a minimum and I'm only using AWSCognitoIdentityProvider part. For the user handling.
I trying to receive the correct keys using this Objective-C code:
[[self.credentialsProvider credentials] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
}
else {
AWSCredentials *response = task.result;
NSString *accessKey = response.accessKey;
NSString *secretKey = response.secretKey;
NSString *sessionKey = response.sessionKey;
NSDictionary *responseData = #{
#"AccessKey" : accessKey,
#"SecretKey" : secretKey,
#"SessionKey": sessionKey
};
}
return nil;
}];
The rest I've setup using the relevant docs.
I (wrongly?) tried to sign my requests with the
AccessKey, SecretKey, SessionKey retrieved from the CredentialsProvider above.
{
SecretKey: credentials.SecretKey,
AccesKeyId: credentials.AccessKey,
SessionKey: credentials.SessionKey
}
The signing fails with the following error:
{ message: 'The security token included in the request is invalid.' }
So the question I have is: which keys should I use to sign my requests for authenticated users so that I can apply the attached IAM policies from my Cognito Setup?
Thanks for any help :)
Like Michael - sqlbot, point out, it should be SessionToken instead of SessionKey. I found a better instruction on how to get credentials from Cognito.
I'm trying to implement a login/link account system like the one in this question.
(scroll down to where it says "Methods for explicit associations")
You used to be able to get the user's login data with something like this:
data = Package.facebook.Facebook.retrieveCredential(token).serviceData
Now it looks like this retrieveCredential(token, secret).
Here's the commit where that happened.
I for the life of me can't figure out how to get the credential secret on the server after I call:
Package.facebook.Facebook.requestCredential(
requestPermissions: Accounts.ui._options.requestPermissions["facebook"]
, (token) ->
Meteor.call "userAddOauthCredentials", token, Meteor.userId(), service, (err, resp) ->
if err?
Meteor.userError.throwError(err.reason)
)
Turns out you can do it like this at the moment (on the client):
service = "facebook"
Package.facebook.Facebook.requestCredential(
requestPermissions: Accounts.ui._options.requestPermissions["facebook"]
, (token) ->
secret = Package.oauth.OAuth._retrieveCredentialSecret(token)
Meteor.call "userAddOauthCredentials", token, secret, service, (err, resp) ->
if err?
Meteor.userError.throwError(err.reason)
)
Then on the server you'll need the secret to access the service data for the user.
userAddOAuthCredentials: (token, secret, service) ->
services = Meteor.user().services
serviceSearch = {}
data = {}
switch service
when "facebook"
if not services.facebook?
data = Package.facebook.Facebook.retrieveCredential(token, secret)?.serviceData
services.facebook = data
serviceSearch = {"services.facebook.id": services.facebook.id}
else
throw new Meteor.Error(500, "You already have a linked Facebook account with email #{services.facebook.email}...")
oldUser = Meteor.users.findOne(serviceSearch)
if oldUser?
throw new Meteor.Error(500, "Your #{service} account has already been assigned to another user.")
Meteor.users.update(#userId, {$set: {services: services}})
if data.email?
if not _.contains(Meteor.user().emails, data.email)
Meteor.users.update(#userId, {$push: {"emails": {address: data.email, verified: true}}})
Those functions will get you the user's service data so you can link multiple accounts, or do whatever you want with them.