AWS Amplify uses guest credentials, not authenticated creds, in API requests - amazon-web-services

I am using the AWS Amplify library with MobileHub.
I have a Cognito User Pool connected, and an API Gateway (which communicates with Lambda functions). I'd like my users to sign before accessing resources, so I've enabled "mandatory sign-in" in MobileHub User Sign-In page, and the Cloud Logic page.
Authentication works fine, but when I send a GET request to my API, I receive this error:
"[WARN] 46:22.756 API - ensure credentials error": "cannot get guest credentials when mandatory signin enabled"
I understand that Amplify generates guest credentials, and has put these in my GET request. Since I've enabled "mandatory signin", this doesn't work.
But why is it use guest credentials? I've signed in -- shouldn't it use those credentials? How do I use the authenticated user's information?
Cheers.
EDIT: Here is the code from the Lambda function:
lambda function:
import { success, failure } from '../lib/response';
import * as dynamoDb from '../lib/dynamodb';
export const main = async (event, context, callback) => {
const params = {
TableName: 'chatrooms',
Key: {
user_id: 'user-abc', //event.pathParameters.user_id,
chatroom_id: 'chatroom-abc',
}
};
try {
const result = await dynamoDb.call('get', params);
if (result.Item) {
return callback(null, success(result.Item, 'Item found'));
} else {
return callback(null, failure({ status: false }, 'Item not found.'));
}
} catch (err) {
console.log(err);
return callback(null, failure({ status: false }), err);
}
}
And these small helper functions:
response.js:
export const success = (body, message) => buildResponse(200, body, message)
export const failure = (body, message) => buildResponse(500, body, message)
const buildResponse = (statusCode, body, message=null) => ({
statusCode: statusCode,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
},
body: JSON.stringify({
...body,
message: message
})
});
dynamodb.js:
import AWS from 'aws-sdk';
AWS.config.update({ region: 'ap-southeast-2' });
export const call = (action, params) => {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
return dynamoDb[action](params).promise();
}

I'm following the guide "serverless-stack" and was prompt with the same warning message, I was logging in correctly and logging out correctly and did not understand why the warning message.
In my case, in the Amplify.configure I skip to add the identity pool id, and that was the problem, User pools and federated identities are not the same.
(English is not my native language)

Have you tried checking why your SignIn request is being rejected/error prone?
Auth.signIn(username, password)
.then(user => console.log(user))
.catch(err => console.log(err));
// If MFA is enabled, confirm user signing
// `user` : Return object from Auth.signIn()
// `code` : Confirmation code
// `mfaType` : MFA Type e.g. SMS, TOTP.
Auth.confirmSignIn(user, code, mfaType)
.then(data => console.log(data))
.catch(err => console.log(err));
You can try this, then it would be easier for you to debug.

From the suggestions on the aws-amplify issues tracker, add an anonymous user to your cognito user pool and hard code the password in your app. Seems like there are other options but this is the simplest in my opinion.

You have to use your credentials at each request to use AWS Services :
(sample code angular)
SignIn :
import Amplify from 'aws-amplify';
import Auth from '#aws-amplify/auth';
Amplify.configure({
Auth: {
region: ****,
userPoolId: *****,
userPoolWebClientId: ******,
}
});
//sign in
Auth.signIn(email, password)
Request
import Auth from '#aws-amplify/auth';
from(Auth.currentCredentials())
.pipe(
map(credentials => {
const documentClient = new AWS.DynamoDB.DocumentClient({
apiVersion: '2012-08-10',
region: *****,
credentials: Auth.essentialCredentials(credentials)
});
return documentClient.query(params).promise()
}),
flatMap(data => {
return data
})
)

Related

AWS Amplify post request fails with "status code 403 at node_modules/axios"

I configured and initialized AWS Amplify for my ReactNative/Expo app and added a REST Api. Im new to AWS in general, but im assuming that once I add the API, my project is populated with amplify/backend folders and files and is ready for consumption.
So i tried to create a simple post request to create an item in my DynamoDB table with
import { Amplify, API } from "aws-amplify";
import awsconfig from "./src/aws-exports";
Amplify.configure(awsconfig);
const enterData = async () => {
API.post("API", "/", {
body: {
dateID: "testing",
},
headers: {
Authorization: `Bearer ${(await Auth.currentSession())
.getIdToken()
.getJwtToken()}`
}
})
.then((result) => {
// console.log(JSON.parse(result));
})
.catch((err) => {
console.log(err);
});
};
const signIn = async () => {
Auth.signIn('test#test.com', 'testpassword')
.then((data) => {
console.log(data)
enterData() //enterData is attempted after signin is confirmed.
})
.catch((err) => {
console.log(err)
})
}
signIn()
I did not touch anything else in my project folder besides including the above in my App.tsx because im unsure if i need to and where. I got a 403 error code and it "points" to the axios package but im not sure if issue is related to aws integration.
I configured the REST Api with restricted access where Authenticated users are allowed to CRUD, and guests are allowed to Read. How could I even check if I am considered an "Authorized User" .
Yes, AWS Amplify API category uses Axios under the hood so axios is related to your problem.
Probably you get 403 because you didn't authorized, for Rest API's you need to set authorization headers,
I don't know how is your config but you can take help from this page. Please review the "Define Authorization Rules" section under the API(REST) section.
https://docs.amplify.aws/lib/restapi/authz/q/platform/js/#customizing-http-request-headers
To check authorization methods, you can use "Auth" class like that also you can see auth class usage in the above link.
import { Amplify, API, Auth } from "aws-amplify";
https://aws-amplify.github.io/amplify-js/api/classes/authclass.html

Unable to by pass amazon cognito authentication using cypress

I am trying to get login, but login functionality takes place in 'AWS Cognito Authetication', which is making my life little mess.
What happens, when user enters base url on browser, app navigates to 'AWS Cognito' message, where I enter my credentials, after adding credentials, app is showing me an alert message i.e.
An error was encountered with the requested page.
Screenshot is attached. I have checked network logs, but it is showing me that:
{"error":{"name":"UnauthorizedError","message":"No authorization token was found"}}
I need to know , where to start the procedure, I have gone through AWS Cognito Credentials section, but nothing happened yet.
Can someone help me there, how to start and how to work with it?
Cypress documentation has advice on how to authenticate with Cognito.
It could be more complete though, this blog post by Nick Van Hoof offers a more complete solution.
First install aws-amplify and cypress-localstorage-commands libs.
Add a Cypress command like:
import { Amplify, Auth } from 'aws-amplify';
import 'cypress-localstorage-commands';
Amplify.configure({
Auth: {
region: 'your aws region',
userPoolId 'your cognito userPoolId',
userPoolWebClientId: 'your cognito userPoolWebClientId',
},
});
Cypress.Commands.add("signIn", () => {
cy.then(() => Auth.signIn(username, password)).then((cognitoUser) => {
const idToken = cognitoUser.signInUserSession.idToken.jwtToken;
const accessToken = cognitoUser.signInUserSession.accessToken.jwtToken;
const makeKey = (name) => `CognitoIdentityServiceProvider.${cognitoUser.pool.clientId}.${cognitoUser.username}.${name}`;
cy.setLocalStorage(makeKey("accessToken"), accessToken);
cy.setLocalStorage(makeKey("idToken"), idToken);
cy.setLocalStorage(
`CognitoIdentityServiceProvider.${cognitoUser.pool.clientId}.LastAuthUser`,
cognitoUser.username
);
});
cy.saveLocalStorage();
});
Then your test:
describe("Example test", () => {
before(() => {
cy.signIn();
});
after(() => {
cy.clearLocalStorageSnapshot();
cy.clearLocalStorage();
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
it("should be logged in", () => {
cy.visit("/");
// ...
});
});

AWS Cognito JWT will not pass validation with .net core api, missing cognito configuration?

I am trying to attach an angular application to a .NET core API utilizing the JWT token. At this point i have the local angular app authenticating with Cognito and getting the user account.
I've followed this to get the token attached to the request.
https://medium.com/#umashankar.itn/aws-cognito-hosted-ui-with-angular-and-asp-net-core-5ddf351680a5
Amplify.Configure({
Auth: {
region: 'us-west-2',
userPoolId: 'us-west-MY POOL',
userPoolWebClientId: 'MY APP CLIENT ID'
}
}
});
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
if (request.url.indexOf(environment.api.baseUrl) == 0) {
return this.getToken().pipe(mergeMap(token => {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next.handle(request);
}));
}
return next.handle(request);
}
}
getToken() {
return from(
new Promise((resolve, reject) => {
Auth.currentSession()
.then((session) => {
if (!session.isValid()) {
resolve(null);
} else {
resolve(session.getIdToken().getJwtToken());
}
})
.catch(err => {
return resolve(null)
});
})
);
}
And i can confirm that it is adding the token to the request.
Interesting thing to note is that i'm using the session.getIdToken().getJwtToken() but there also is session.getAccessToken().getJwtToken() and they are different. I can't find anything telling me what the difference is, but i've tried both and they both have the same issue.
For the server side i've followed this answer to setup the .net core site and i can confirm that it is appropriately downloading the keys from /.well-known/jwks.json. It however just keeps rejecting the request with authentication failure.
How to validate AWS Cognito JWT in .NET Core Web API using .AddJwtBearer()
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
// get JsonWebKeySet from AWS
var json = new WebClient().DownloadString(parameters.ValidIssuer + "/.well-known/jwks.json");
// serialize the result
return JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
},
ValidateIssuer = true,
ValidIssuer = $"https://cognito-idp.us-west-2.amazonaws.com/us-west-MYID",
ValidateLifetime = true,
LifetimeValidator = (before, expires, token, param) => expires > DateTime.UtcNow,
ValidateAudience = true,
ValidAudience = "MY APP CLIENT ID"
};
});
app.UseAuthentication();
[HttpGet]
[Authorize]
public IEnumerable<Device> Get()
{
return 'my devices...';
}
The angular app is running at http://localhost:4200 and the .net core is running at https://localhost:44300.
So the question i have is, am i missing some sort of setup in my cognito app client? What am i missing to get the .NET core app to take the JWT?
Turns out i actually did have everything correct as far as Cognito goes.
What i did have was this.
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
Which is not the correct order for things to work... this is..
app.UseRouting();
app.UseAuthentication(); <-- Authentication before authorization
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});

AWS Amplify: Token is not from a supported provider of this identity pool

I call this function on my app to allow for login through Facebook.
async fbSignIn(){
const { type, token, expires } = await Facebook.logInWithReadPermissionsAsync('454050215391261', {
permissions: ['public_profile'],
});
if (type === 'success') {
console.log(token);
Auth.federatedSignIn('facebook', { token, expires_at: expires}, { name: 'USER_NAME' })
.then(credentials => {
console.log('get aws credentials', credentials);
console.log("userProfile: ",JSON.stringify(userProfile));
this.setState({ userProfile: userProfile});
this.props.navigation.replace('Dashboard');
}).catch(e => {
console.log("ERROR:",e);
});
}
}
It properly opens the Facebook UI, but after entering my credentials I receive this error:
NotAuthorizedException: Token is not from a supported provider of this identity pool.
I have Facebook enabled as an identity provider for my user pool.
Any ideas how I can fix this?

AWS Cognito services APIs? Amplify => Javascript SDK Angular app

I used Amplify for authentication and it seems to work fine. Now I want to setup an admin app for CRUD with the user pool. It seems that I have to leave Amplify and use the JavaScript SDK to use the appropriate api's.
How does this work? I've failed at figuring out how to get the tokens I receive in Amplify into AWS.config or wherever they are supposed to go.
What a struggle this had been. It seems that the code in the docs is dated and what little advice is online is worse. I suspect that is because an Amplify object contains the config options and I have to bring those to the AWS.config object. I've tried below and failed. Any idea what I need to do? I'm sure answers here will be useful for many AWS newbies.
I have this code in my Angular app but thinking about Lambda. I have an EC2 server with Node.js as another option.
This is for dev on my MBP but I'm integrating it with AWS.
With the code below I get an error message that contains in part:
Error in getCognitoUsers: Error: Missing credentials in config
at credError (config.js:345)
at getStaticCredentials (config.js:366)
at Config.getCredentials (config.js:375)
The JWT I inserted into the object below is the AccessKeyID that is in my browser storage and I used for authentication.
In console.log cognitoidentityserviceprovider I have this object, in part:
config: Config
apiVersion: "2016-04-18"
credentialProvider: null
credentials: "eyJraWQiOiJwaUdRSnc4TWtVSlR...
endpoint: "cognito-idp.us-west-2.amazonaws.com"
region: "us-west-2"
endpoint: Endpoint
host: "cognito-idp.us-west-2.amazonaws.com"
hostname: "cognito-idp.us-west-2.amazonaws.com"
href: "https://cognito-idp.us-west-2.amazonaws.com/"
These functions flow down as a sequence. I left some vars in the bodies in case someone wants to know how to get this data from the user object. I used them in various attempts build objects but most aren't needed here, maybe. The all produce the correct results from the Amplify user object.
import { AmplifyService } from 'aws-amplify-angular';
import Amplify, { Auth } from 'aws-amplify';
import { CognitoIdentityServiceProvider } from 'aws-sdk';
import * as AWS from 'aws-sdk';
#Injectable()
export class CognitoApisService {
private cognitoConfig = Amplify.Auth.configure(); // Data from main.ts
private cognitoIdPoolID = this.cognitoConfig.identityPoolId;
private cognitoUserPoolClient = this.cognitoConfig.userPoolWebClientId;
private cognitoIdPoolRegion = this.cognitoConfig.region;
private cognitoUserPoolID = this.cognitoConfig.userPoolId;
...
constructor(
private amplifyService: AmplifyService,
) { }
public getAccessToken() {
return this.amplifyService
.auth() // Calls class that includes currentAuthenticaedUser.
.currentAuthenticatedUser() // Sets up a promise and gets user session info.
.then(user => {
console.log('user: ', user);
this.accessKeyId = user.signInUserSession.accessToken.jwtToken;
this.buildAWSConfig();
return true;
})
.catch(err => {
console.log('getAccessToken err: ', err);
});
}
public buildAWSConfig() {
// Constructor for the global config.
this.AWSconfig = new AWS.Config({
apiVersion: '2016-04-18',
credentials: this.accessKeyId,
region: this.cognitoIdPoolRegion
});
this.cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider(this.AWSconfig);
/* This doesn't get creds, probably because of Amplify.
this.cognitoidentityserviceprovider.config.getCredentials(function(err) {
if (err) console.log('No creds: ', err); // Error: Missing credentials
else console.log("Access Key:", AWS.config.credentials.accessKeyId);
});
*/
console.log('cognitoidentityserviceprovider: ', this.cognitoidentityserviceprovider);
this.getCognitoUsers();
}
public getCognitoUsers() {
// Used for listUsers() below.
const params = {
UserPoolId: this.cognitoUserPoolID,
AttributesToGet: [
'username',
'given_name',
'family_name',
],
Filter: '',
Limit: 10,
PaginationToken: '',
};
this.cognitoidentityserviceprovider.listUsers(params, function (err, data) {
if
(err) console.log('Error in getCognitoUsers: ', err); // an error occurred
else
console.log('all users in service: ', data);
});
}
The one problem was that the credentials needs the whole user object from Amplify, not just the access token as I show above. By the way, I have the Cognito settings in main.ts. They can also go in environment.ts. A better security option is to migrate this to the server side. Not sure how to do that yet.
// Constructor for the global config.
this.AWSconfig = new AWS.Config({
apiVersion: '2016-04-18',
credentials: this.accessKeyId, // Won't work.
region: this.cognitoIdPoolRegion
});
My complete code is simpler and now an observable. Notice another major issue I had to figure out. Import the AWS object from Amplify, not the SDK. See below.
Yeah, this goes against current docs and tutorials. If you want more background on how this has recently changed, even while I was working on it, see the bottom of this Github issue. Amplify is mostly for authentication and the JavaScript SDK is for the service APIs.
import { AmplifyService } from 'aws-amplify-angular';
// Import the config object from main.ts but must match Cognito config in AWS console.
import Amplify, { Auth } from 'aws-amplify';
import { AWS } from '#aws-amplify/core';
import { CognitoIdentityServiceProvider } from 'aws-sdk';
// import * as AWS from 'aws-sdk'; // Don't do this.
#Injectable()
export class CognitoApisService {
private cognitoConfig = Amplify.Auth.configure(); // Data from main.ts
private cognitoIdPoolRegion = this.cognitoConfig.region;
private cognitoUserPoolID = this.cognitoConfig.userPoolId;
private cognitoGroup;
private AWSconfig;
// Used in listUsers() below.
private params = {
AttributesToGet: [
'given_name',
'family_name',
'locale',
'email',
'phone_number'
],
// Filter: '',
UserPoolId: this.cognitoUserPoolID
};
constructor(
private amplifyService: AmplifyService,
) { }
public getCognitoUsers() {
const getUsers$ = new Observable(observer => {
Auth
.currentCredentials()
.then(user => {
// Constructor for the global config.
this.AWSconfig = new AWS.Config({
apiVersion: '2016-04-18',
credentials: user, // The whole user object goes in the config.credentials field! Key issue.
region: this.cognitoIdPoolRegion
});
const cognitoidentityserviceprovider = new CognitoIdentityServiceProvider(this.AWSconfig);
cognitoidentityserviceprovider.listUsers(this.params, function (err, userData) {
if (err) {
console.log('Error in getCognitoUsers: ', err);
} else {
observer.next(userData);
}
});
});
});
return getUsers$;
}
Let's call this service from a component. I'm putting the JS object parsing in the component but for now, I left the console.log here for you to get started and see if the code works for your app.
// Called from button on html component.
public getAllCognitoUsers() {
this.cognitoApisService.getCognitoUsers()
.subscribe(userData => {
console.log('data in cognito component: ', userData);
})
}