Cognito Custom Auth trigger not getting Session from Cognito - amazon-cognito

I tried to call the InitiateAuth API from AWS CLI. I have set up Define auth, create auth and verify auth lambda triggers correctly. The problem is that, when I ran the below command, it's showing error:
aws cognito-idp initiate-auth --client-id <my_client_id> --auth-flow CUSTOM_AUTH --auth-parameters USERNAME=uname,ChallengeName="SRP_A",SRP_A="<srp_value>"
Error: An error occurred (UserLambdaValidationException) when calling the InitiateAuth operation: DefineAuthChallenge failed with error Cannot read property 'challengeName' of undefined.
I checked the Define Auth lambda code, and also the Cloud Watch logs of Lambda execution. The error occurred because the input from Cognito contains an empty session key in the event json (which usually sent from Cognito to Lambda). As the property challengeName resides inside the session key (as shown in official documentation).
Here is the JSON event sent to Lambda from Cognito when I ran that command (I got this JSON from CloudWatch Lambda logs, I printed the event which is being sent from Cognito):
{
version: '1',
region: 'us-east-1',
userPoolId: 'us-east-1_******',
userName: 'uname',
callerContext: {
awsSdkVersion: 'aws-sdk-unknown-unknown',
clientId: '<my_client_id>'
},
triggerSource: 'DefineAuthChallenge_Authentication',
request: {
userAttributes: {
sub: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx',
'cognito:email_alias': '<email>',
'cognito:user_status': 'CONFIRMED',
email_verified: 'true',
name: 'Custom Test',
email: '<email>'
},
session: [], -----> !! Empty
userNotFound: false
},
response: { challengeName: null, issueTokens: null, failAuthentication: null }
}
What is the reason? Is it because I am sending the request from CLI so Cognito not able to create a session or something? I'm not sure. Any help will be appreciated.

Session holds previous auth challenge results (either from built-in challenges or you custom challenges). It will be empty for the first invocation of the define auth challenge lambda. As the name suggests you have to define the auth challenge in the handler response.

Related

invalid_grant exchanging authorization code for access and refresh tokens

My application is using OAuth to access the Youtube Data API. My OAuth callback is written in node and uses the OAuth2Client class from the "googleapis" npm package to exchange the authorization code for the access and refresh tokens.
Everything was working fine up to last week until suddenly I started getting the "invalid_grant" response during the authorization code exchange. I have tried everything to resolve this and am running out of ideas. My callback executes as a cloud function so I don't think that it would be out of sync with NTP.
My OAuth consent screen is in "Testing" mode and my email address is included in the test users. The odd thing is that even though the authorization code exchange fails, my Google account's "Third-party apps with account access" section lists my application as if the handshake succeeded.
Is there a limit to how many refresh tokens can be minted for my application? I am testing my implementation of incremental authorization so I have been going through the OAuth flow often.
Edit
I've included my code for generating the auth URL and exchanging the authorization code below. The invalid_grant occurs during the call to "oauth2.getToken"
async startFlow(scopes: string[], state: string): Promise<AuthFlow> {
const codes = await oauth2.generateCodeVerifierAsync();
const href = oauth2.generateAuthUrl({
scope: scopes,
state,
access_type: 'offline',
include_granted_scopes: true,
prompt: 'consent',
code_challenge_method: CodeChallengeMethod.S256,
code_challenge: codes.codeChallenge
});
return { href, code_verifier: codes.codeVerifier };
}
async finishFlow(code: string, verifier: string): Promise<Tokens> {
const tokens = await oauth2.getToken({ code, codeVerifier: verifier })
return {
refresh_token: tokens.tokens.refresh_token!,
access_token: tokens.tokens.access_token!,
expires_in: tokens.tokens.expiry_date!,
token_type: 'Bearer',
scopes: tokens.tokens.scope!.split(' ')
};
}
"oauth2" is an instance of OAuth2Client from "google-auth-library". I initialize it here:
export const oauth2 = new google.auth.OAuth2({
clientId: YT_CLIENT_ID,
clientSecret: YT_CLIENT_SECRET,
redirectUri: `${APP_URI}/oauth`
});
Looking at the logs, the only out of the ordinary thing I notice is that the application/x-www-form-urlencoded body looks slightly different than the example https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
The POST request to "https://oauth2.googleapis.com/token" ends up looking like this:
code=4%2F0AX4XfWiKHVnsavUH7en0TywjPJVRyJ9aGN-JR8CAAcAG7dT-THxyWQNcxd769nzaHLUb8Q&client_id=XXXXXXXXXX-XXXXXXXXXXXXXXX.apps.googleusercontent.com&client_secret=XXXXXX-XXXXXXXXXXXXXXX-XX_XXX&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth&grant_type=authorization_code&code_verifier=KjOBmr4D9ISLPSE4claEBWr3UN-bKdPHZa8BBcQvcmajfr9RhWrgt7G429PLEpsP7oGzFGnBICu3HgWaHPsLhMkGBuQ2GmHHiB4OpY2F0rJ06wkpCjV2cCTDdpfRY~Ej
Notice that the "/" characters are not percent-encoded in the official example, but they are in my requests. Could this actually be the issue? I don't see how the official google auth library would have an issue this large.
The most common cause for the invalid_grant error is your refresh token expiring.
If you check oauth2#expiration you will see the following
A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days.
Once you set your project to production your refresh tokens will stop expiring.
Is there a limit to how many refresh tokens can be minted for my application?
No but you have a limit of 100 test users.

node S3 Object Storage Linode

Im trying to use the aws-sdk to acces my linode S3 compatible bucket, but everything I try doesn't work. Not sure what the correct endpoint should be? For testing purposes is my bucket set to public read/write.
const s3 = new S3({
endpoint: "https://linodeobjects.com",
region: eu-central-1,
accesKeyId: <accesKey>,
secretAccessKey: <secretKey>,
});
const params = {
Bucket: bucketName,
Key: "someKey",
Expires: 60,
};
const uploadURL = await s3.getSignedUrlPromise("putObject", params);
The error im getting
code: 'CredentialsError',
time: 2021-07-15T08:29:50.000Z,
retryable: true,
originalError: {
message: 'Could not load credentials from any providers',
code: 'CredentialsError',
time: 2021-07-15T08:29:50.000Z,
retryable: true,
originalError: {
message: 'EC2 Metadata roleName request returned error',
code: 'TimeoutError',
time: 2021-07-15T08:29:49.999Z,
retryable: true,
originalError: [Object]
}
}
}
It seems like a problem with the credentials of the environment that this code is executed in and not with the bucket permissions themselves.
The pre-signing of the URL is an operation that is done entirely locally. It uses local credentials (i.e., access key ID and secret access key) to create a sigv4 signature for the URL. This also means that whether or not the credentials used for signing the URL are valid is only checked at the moment the URL is used, and not at the moment of signing the URL itself.
The error simply indicates that from all the ways the SDK is trying to find credentials (more info here) it cannot find credentials it can use to sign the URL.
This might be unrelated, but according to the documentation, the endpoint should be the following: The endpoint URI to send requests to. The default endpoint is built from the configured region. The endpoint should be a string like 'https://{service}.{region}.amazonaws.com' or an Endpoint object. Which, in the code example above, is not the case.
You should set the endpoint to be eu-central-1.linodeobjects.com. When using Linode object storage the region is not determined by the endpoint that you use.

Refresh Token Issue

I am using AWS amplify SDK for React native.
Using the below method in the SDK signing into Cognito user pool.
Auth.federatedSignIn(
COGNITO_SAML_PROVIDER,
{ token: samlToken, expires_at: 0 },
{ email, name: '' },
);
Then retrieving the keys using currentCredentials to access AWS API gateway.
credentials = await Auth.currentCredentials();
const requestTokens = {
access_key: credentials.accessKeyId,
secret_key: credentials.secretAccessKey,
session_token: credentials.sessionToken,
};
Problem:
After idle period of 30 mins the SDK doesn't refresh the session_token and uses the expired token for subsequent request and we run into issue "the security token included in the request is invalid"
Is there a way or some parameter to set in the SDK so that the token gets refreshed periodically?

How to avoid that AWS Amplify OAuth tries to parse every oauth process

I'm working with a react-native application where i have implement the Authentication flow using AWS Amplify and Federated signin. This is the amplify configuration:
Auth: {
identityPoolId: 'XXX',
region: 'XXX',
mandatorySignIn: false,
userPoolId: 'XXX',
userPoolWebClientId: 'XXX',
oauth: {
domain: env.AWS_OAUTH_DOMAIN,
scope: ['email', 'profile', 'openid','aws.cognito.signin.user.admin', 'given_name', 'family_name', 'user_gender', 'user_birthday', 'user_location'],
redirectSignIn: myapp://signin,
redirectSignOut: myapp://logout,
responseType: 'code',
},
},
Everything works fine. Until now.
Now i have to add another OAuth authentication for other purposes (connecting Strava to my application). Everything works fine, until the Strava authorization dialog redirect to my app at the url: runcard://profilo/servizi?code=XXX&scope=activity%3Aread%2Cread (this callback url is different from the one i've set for amplify configuration). Once redirected, amplify is there, ready to raise an exception by Amplify OAuth:
WARN Possible Unhandled Promise Rejection (id: 0):
TypeError: undefined is not an object (evaluating '_a.accessToken')
I believe that since the callback url has a code parameter, Amplify is trying to do the job himself. Without success.
Does anyone faced the same issue?
I found the cause of the issue!
Amplify is specifically looking for a param in any deeplinked URL called code.
I too was using code for a purpose other than the oauth callback (sign up confirmation code).
Changing the param to anything else (e.g. confirmationCode) prevents the accessToken error.
At the end, I've used this workaround: I removed the Amplify listener and I added a new listener that will parse only Amplify OAuth URLs.
Amplify.configure({ ... });
Analytics.getInstance();
// Workaround: this is to avoid that Amplify OAuth try to parse EVERY url as a OAuth callback url
// https://stackoverflow.com/questions/59883011/how-to-avoid-that-aws-amplify-oauth-tries-to-parse-every-oauth-process
Linking.removeAllListeners('url');
Linking.addEventListener('url', (url) => {
if (url.url.indexOf(AWS_OAUTH_REDIRECT_SIGNIN !== -1 || url.url.indexOf(AWS_OAUTH_REDIRECT_SIGNOUT) !== -1) {
Amplify.Auth._handleAuthResponse(url.url);
}
});
AWS_OAUTH_REDIRECT_SIGNIN and AWS_OAUTH_REDIRECT_SIGNOUT are the same specified in Amplify configuration:
AWS: {
Auth: {
oauth: {
redirectSignIn: AWS_OAUTH_REDIRECT_SIGNIN,
redirectSignOut: AWS_OAUTH_REDIRECT_SIGNOUT,
responseType: 'code'
}
}
}

Configure AWS Cognito User Pool Advanced Security Features Account Takeover Risk Configuration via CLI

Attempting to configure my Cognito user pool via the CLI
If I run
aws cognito-idp set-risk-configuration --user-pool-id ap-southeast-2_123456789 --account-takeover-risk-configuration Actions={LowAction={Notify=false,EventAction=NO_ACTION},MediumAction={Notify=false,EventAction=NO_ACTION},HighAction={Notify=false,EventAction=NO_ACTION}}
I just get the error
Unknown options: Actions=MediumAction=Notify=false, Actions=MediumAction=EventAction=NO_ACTION, Actions=HighAction=Notify=false, Actions=HighAction=EventAction=NO_ACTION, Actions=LowAction=EventAction=NO_ACTION
I have tried simplifying my request to just
aws cognito-idp set-risk-configuration --user-pool-id ap-southeast-2_123456789 --account-takeover-risk-configuration Actions={HighAction={EventAction=NO_ACTION}}
And I get the error
Missing required parameter in AccountTakeoverRiskConfiguration.Actions.HighAction: "Notify"
So I know I am on the right track, but then when I change my command to
aws cognito-idp set-risk-configuration --user-pool-id ap-southeast-2_123456789 --account-takeover-risk-configuration Actions={HighAction={EventAction=NO_ACTION,Notify=false}} to satisfy the missing param, I get Unknown options: Actions={HighAction=Notify=false}
What is the correct syntax for the Notify param?
Confirmed by AWS support as a bug where the boolean is simply being dropped by the parser
Only fix is to use an external JSON file for the props.
set-risk-configuration --user-pool-id ap-southeast-2_123456789 --account-takeover-risk-configuration file://riskconfig.json
riskconfig.json
{
"Actions": {
"LowAction": {
"Notify": false,
"EventAction": "NO_ACTION"
},
"MediumAction": {
"Notify": false,
"EventAction": "NO_ACTION"
},
"HighAction": {
"Notify": false,
"EventAction": "NO_ACTION"
}
}
}