Cognito admin_initiate_auth responds with exception User does not exist when creating a new user - ruby

I'm trying to create a new user in a Cognito user pool from my ruby backend server. Using this code:
client = Aws::CognitoIdentityProvider::Client.new
response = client.admin_initiate_auth({
auth_flow: 'ADMIN_NO_SRP_AUTH',
auth_parameters: {
'USERNAME': #user.email,
'PASSWORD': '123456789'
},
client_id: ENV['AWS_COGNITO_CLIENT_ID'],
user_pool_id: ENV['AWS_COGNITO_POOL_ID']
})
The response I get is Aws::CognitoIdentityProvider::Errors::UserNotFoundException: User does not exist.
I'm trying to follow the Server Authentication Flow (https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html), and from that I understood that I could create a new user using admin_initiate_auth.
Am I doing something wrong here?
Thanks

You're using the wrong method. admin_initiate_auth is for logging in/authenticating a user with the ADMIN_NO_SRP_AUTH turned on.
You need to use the sign_up method:
resp = client.sign_up({
client_id: "ClientIdType", # required
secret_hash: "SecretHashType",
username: "UsernameType", # required
password: "PasswordType", # required
user_attributes: [
{
name: "AttributeNameType", # required
value: "AttributeValueType",
},
],
validation_data: [
{
name: "AttributeNameType", # required
value: "AttributeValueType",
},
],
analytics_metadata: {
analytics_endpoint_id: "StringType",
},
user_context_data: {
encoded_data: "StringType",
},
})
You can find it in the AWS Cognito IDP docs here.

Related

SAM Template - define HttpApi with Lambda Authorizer and Simple Response

Description of the problem
I have created a Lambda function with API Gateway in SAM, then deployed it and it was working as expected. In API Gateway I used HttpApi not REST API.
Then, I wanted to add a Lambda authorizer with Simple Response. So, I followed the SAM and API Gateway docs and I came up with the code below.
When I call the route items-list it now returns 401 Unauthorized, which is expected.
However, when I add the header myappauth with the value "test-token-abc", I get a 500 Internal Server Error.
I checked this page but it seems all of the steps listed there are OK https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-http-lambda-integrations/
I enabled logging for the API Gateway, following these instructions: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-logging.html
But all I get is something like this (redacted my IP and request ID):
[MY-IP] - - [07/Jul/2021:08:24:06 +0000] "GET GET /items-list/{userNumber} HTTP/1.1" 500 35 [REQUEST-ID]
(Perhaps I can configure the logger in such a way that it prints a more meaningful error message? EDIT: I've tried adding $context.authorizer.error to the logs, but it doesn't print any specific error message, just prints a dash: -)
I also checked the logs for the Lambda functions, there is nothing there (all logs where from the time before I added the authorizer).
So, what am I doing wrong?
What I tried:
This is my Lambda Authorizer function which I have deployed using sam deploy, when I test it in isolation using an event with the myappauth header, it works:
exports.authorizer = async (event) => {
let response = {
"isAuthorized": false,
};
if (event.headers.myappauth === "test-token-abc") {
response = {
"isAuthorized": true,
};
}
return response;
};
and this is the SAM template.yml which I deployed using sam deploy:
AWSTemplateFormatVersion: 2010-09-09
Description: >-
myapp-v1
Transform:
- AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: nodejs14.x
MemorySize: 128
Timeout: 100
Environment:
Variables:
MYAPP_TOKEN: "test-token-abc"
Resources:
MyAppAPi:
Type: AWS::Serverless::HttpApi
Properties:
FailOnWarnings: true
Auth:
Authorizers:
MyAppLambdaAuthorizer:
AuthorizerPayloadFormatVersion: "2.0"
EnableSimpleResponses: true
FunctionArn: !GetAtt authorizerFunction.Arn
FunctionInvokeRole: !GetAtt authorizerFunctionRole.Arn
Identity:
Headers:
- myappauth
DefaultAuthorizer: MyAppLambdaAuthorizer
itemsListFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/v1-handlers.itemsList
Description: A Lambda function that returns a list of items.
Policies:
- AWSLambdaBasicExecutionRole
Events:
Api:
Type: HttpApi
Properties:
Path: /items-list/{userNumber}
Method: get
ApiId: MyAppAPi
authorizerFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/v1-handlers.authorizer
Description: A Lambda function that authorizes requests.
Policies:
- AWSLambdaBasicExecutionRole
Edit:
User #petey suggested that I tried returning an IAM policy in my authorizer function, so I changed EnableSimpleResponses to false in the template.yml, then I changed my function as below, but got the same result:
exports.authorizer = async (event) => {
let response = {
"principalId": "my-user",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Action": "execute-api:Invoke",
"Effect": "Deny",
"Resource": event.routeArn
}]
}
};
if (event.headers.myappauth == "test-token-abc") {
response = {
"principalId": "my-user",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": event.routeArn
}]
}
};
}
return response;
};
I am going to answer my own question because I have resolved the issue, and I hope this will help people who are going to use the new "HTTP API" format in API Gateway, since there is not a lot of tutorials out there yet; most examples you will find online are for the older API Gateway standard, which Amazon calls "REST API". (If you want to know the difference between the two, see here).
The main problem lies in the example that is presented in the official documentation. They have:
MyLambdaRequestAuthorizer:
FunctionArn: !GetAtt MyAuthFunction.Arn
FunctionInvokeRole: !GetAtt MyAuthFunctionRole.Arn
The problem with this, is that this template will create a new Role called MyAuthFunctionRole but that role will not have all the necessary policies attached to it!
The crucial part that I missed in the official docs is this paragraph:
You must grant API Gateway permission to invoke the Lambda function by using either the function's resource policy or an IAM role. For this example, we update the resource policy for the function so that it grants API Gateway permission to invoke our Lambda function.
The following command grants API Gateway permission to invoke your Lambda function. If API Gateway doesn't have permission to invoke your function, clients receive a 500 Internal Server Error.
The best way to solve this, is to actually include the Role definition in the SAM template.yml, under Resources:
MyAuthFunctionRole
Type: AWS::IAM::Role
Properties:
# [... other properties...]
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action:
- 'sts:AssumeRole'
Policies:
# here you will put the InvokeFunction policy, for example:
- PolicyName: MyPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: 'lambda:InvokeFunction'
Resource: !GetAtt MyAuthFunction.Arn
You can see here a description about the various Properties for a role: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
Another way to solve this, is to separately create a new policy in AWS Console, which has InvokeFunction permission, and then after deployment, attach that policy to the MyAuthFunctionRole that SAM created. Now the Authorizer will be working as expected.
Another strategy would be to create a new role beforehand, that has a policy with InvokeFunction permission, then copy and paste the arn of that role in the SAM template.yml:
MyLambdaRequestAuthorizer:
FunctionArn: !GetAtt MyAuthFunction.Arn
FunctionInvokeRole: arn:aws:iam::[...]
Your lambda authorizer is not returning what is expected to be an actual lambda authorizer (an IAM policy). This could explain that internal error 500.
To fix, replace is with something like this that returns an IAM policy (or rejects):
// A simple token-based authorizer example to demonstrate how to use an authorization token
// to allow or deny a request. In this example, the caller named 'user' is allowed to invoke
// a request if the client-supplied token value is 'allow'. The caller is not allowed to invoke
// the request if the token value is 'deny'. If the token value is 'unauthorized' or an empty
// string, the authorizer function returns an HTTP 401 status code. For any other token value,
// the authorizer returns an HTTP 500 status code.
// Note that token values are case-sensitive.
exports.handler = function(event, context, callback) {
var token = event.authorizationToken;
// modify switch statement here to your needs
switch (token) {
case 'allow':
callback(null, generatePolicy('user', 'Allow', event.methodArn));
break;
case 'deny':
callback(null, generatePolicy('user', 'Deny', event.methodArn));
break;
case 'unauthorized':
callback("Unauthorized"); // Return a 401 Unauthorized response
break;
default:
callback("Error: Invalid token"); // Return a 500 Invalid token response
}
};
// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = {
"stringKey": "stringval",
"numberKey": 123,
"booleanKey": true
};
return authResponse;
}
Lots more information here : https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html#api-gateway-lambda-authorizer-lambda-function-create
Just to complete the answer. You have to add an AssumeRolePolicyDocument under Properties.
The role will then state
MyAuthFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action:
- 'sts:AssumeRole'
Policies:
# see answer above

Microsoft single sign on - react-aad-msal library - can't get access token

I'm developing a web application with a react frontend and a .NET CORE 3.1 backend and was asked to add Azure AD single sign on capabilities. I'm using the react-aad-msal library (https://www.npmjs.com/package/react-aad-msal). I'm calling MsalAuthProvider.getAccessToken() and get this error:
Can't construct an AccessTokenResponse from a AuthResponse that has a token type of "id_token".
Can anyone help me?
Anyone? Btw. getAccessToken() is actually inside the standard msal library, if that helps.
I found a solution myself by going into packages.json and lowering the version number on "msal" in "dependencies" like this:
"msal": "~1.3.0",
Change the scopes in authProvider.
export const authProvider = new MsalAuthProvider(
{
auth: {
authority: 'https://login.microsoftonline.com/5555555-5555-5555-5555-555555555555',
clientId: 'AAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA',
postLogoutRedirectUri: 'http://localhost:3000/signin',
redirectUri: 'http://localhost:3000/signin',
validateAuthority: true,
navigateToLoginRequestUrl: false
},
system: {
logger: new Logger(
(logLevel, message, containsPii) => {
console.log("[MSAL]", message);
},
{
level: LogLevel.Verbose,
piiLoggingEnabled: false
}
)
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true
}
},
{
scopes: ["openid", "profile", "user.read"] // <<<-----------|
},
{
loginType: LoginType.Popup,
tokenRefreshUri: window.location.origin + "/auth.html"
}
);

How to register a new user using AWS Cognito Ruby SDK?

I would like to know how to register a new user using AWS Cognito Ruby SDK.
So far I have tried:
Input
AWS_KEY = "MY_AWS_KEY"
AWS_SECRET = "MY_AWS_SECRET"
client = Aws::CognitoIdentityProvider::Client.new(
access_key_id: AWS_KEY,
secret_access_key: AWS_SECRET,
region: 'us-east-1',
)
resp = client.sign_up({
client_id: "4d2c7274mc1bk4e9fr******", # required
username: "test#test.com", # required
password: "Password23sing", # required
user_attributes: [
{
name: "app", # required
value: "my app name",
},
],
validation_data: [
{
name: "username", # required
value: "true",
},
]
})
Output
Aws::CognitoIdentityProvider::Errors::NotAuthorizedException (Unable to verify secret hash for client 4d2c7274mc1bk4e9fr*****)
References
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CognitoIdentityProvider/Client.html#sign_up-instance_method
If your app client is configured with a client secret, most of the client requests require you to include a 'secret hash' in the options parameters of the request. The Cognito docs describe the secret hash thusly:
The SecretHash value is a Base 64-encoded keyed-hash message
authentication code (HMAC) calculated using the secret key of a user
pool client and username plus the client ID in the message. The following pseudocode shows how this value is calculated.
Base64 ( HMAC_SHA256 ( "Client Secret Key", "Username" + "Client Id" ) )
The docs also make it clear via a glob of sample Java that you are expected to roll your own. After a bit of experimenting I was able to successfully complete a sign_up call with the following (my test pool was set up to require email and name attributes):
def secret_hash(client_secret, username, client_id)
Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', CLIENT_SECRET, username + CLIENT_ID))
end
client = Aws::CognitoIdentityProvider::Client.new(
access_key_id: AWS_KEY,
secret_access_key: AWS_SECRET,
region: REGION)
username = 'bob.scum#example.com'
resp = client.sign_up({
client_id: CLIENT_ID,
username: username,
password: 'Password23sing!',
secret_hash: secret_hash(CLIENT_SECRET, username, CLIENT_ID),
user_attributes: [{ name: 'email', value: username },
{ name: 'name', value: 'Bob' }],
validation_data: [{ name: 'username', value: 'true' },
{ name: 'email', value: 'true' }]
})
CLIENT_SECRET is the app client secret that can be found under General Settings > App Clients.
Result:
#<struct Aws::CognitoIdentityProvider::Types::SignUpResponse
user_confirmed=false,
code_delivery_details=nil,
user_sub="c87c2ac8-1480-4d15-a28d-6998d9260e73">

Cannot fire Bigcommerce webhooks

so far I've managed to create two webhooks by using their official gem (https://github.com/bigcommerce/bigcommerce-api-ruby) with the following events:
store/order/statusUpdated
store/app/uninstalled
The destination URL is a localhost tunnel managed by ngrok (the https) version.
status_update_hook = Bigcommerce::Webhook.create(connection: connection, headers: { is_active: true }, scope: 'store/order/statusUpdated', destination: 'https://myapp.ngrok.io/bigcommerce/notifications')
uninstall_hook = Bigcommerce::Webhook.create(connection: connection, headers: { is_active: true }, scope: 'store/app/uninstalled', destination: 'https://myapp.ngrok.io/bigcommerce/notifications')
The webhooks seems to be active and correctly created as I can retrieve and list them.
Bigcommerce::Webhook.all(connection:connection)
I manually created an order in my store dashboard but no matter to which state or how many states I change it, no notification is fired. Am I missing something?
The exception that I'm seeing in the logs is:
ExceptionMessage: true is not a valid header value
The "is-active" flag should be sent as part of the request body--your headers, if you choose to include them, would be an arbitrary key value pair that you can check at runtime to verify the hook's origin.
Here's an example request body:
{
"scope": "store/order/*",
"headers": {
"X-Custom-Auth-Header": "{secret_auth_password}"
},
"destination": "https://app.example.com/orders",
"is_active": true
}
Hope this helps!

404 Resource Not Found: domain with Google Directory API

I followed the quick start and am attempting to create a user using the google-api-ruby-client.
I've set up access in the google api console. And I can get this to work using the API explorer.
But when I try using the ruby client, I'm getting a resource not found: domain error.
Here's the code:
def self.create_user
# Initialize the client.
client = Google::APIClient.new(
:application_name => 'MYAPP',
:application_version => '0.0.1'
)
# Authorization
# Load our credentials for the service account
key = Google::APIClient::KeyUtils.load_from_pkcs12(KEY_FILE, KEY_SECRET)
client.authorization = Signet::OAuth2::Client.new(
token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
audience: 'https://accounts.google.com/o/oauth2/token',
scope: 'https://www.googleapis.com/auth/admin.directory.user',
issuer: ACCOUNT_ID,
signing_key: key)
# Request a token for our service account
client.authorization.fetch_access_token!
# Load API Methods
admin = client.discovered_api('admin', 'directory_v1')
# Make an API call.
result = client.execute(
admin.users.update,
name: { familyName: 'testy', givenName: 'testerson' },
password: '!password12345!',
primaryEmail: 'ttesterson#my-actual-domain.com'
)
result.data
end
Here's the response:
"error"=>{"errors"=>[{"domain"=>"global", "reason"=>"notFound", "message"=>"Resource Not Found: domain"}], "code"=>404, "message"=>"Resource Not Found: domain"}
Why?
After a bit of documentation reading, there were two things that I needed to fix.
I hadn't set up the proper authorization for my test service account.
You have to go to the Apps Console > Security > Advanced > Manage API client access and add the client url for your service account as well as any specific permissions that you want to add
As seen in this question, it seems that you need to create a user object rather than just passing in parameters.
Here's my updated code:
# Authorization happens here ....
api = client.discovered_api('admin', 'directory_v1')
new_user = api.users.insert.request_schema.new(
name: { familyName: 'Testy', givenName: 'Testerson' },
primaryEmail: 'ttttesterson#<domain-redacted>.com',
password: 'password123'
)
result = client.execute(
api_method: api.users.insert,
body_object: new_user
)

Resources