Serverless lambda unit test handlers with custom authorizer - amazon-web-services

My serverless lambda application has custom authorizer
verify-token:
handler: app/Middleware/VerifyToken.auth
user:
handler: app/Handlers/Users.user
events:
- http:
path: user
method: get
cors: true
authorizer: verify-token
I am writing jest unit test for the user handler, but since when deployed the custom authorization is run before the user handler is executed,
How do I apply the same in jest unit test so that I can apply the authorization before running the user handler test?
This is my test
const { user } = require('../../app/Handlers/Users');
/**
* Tests for get()
*/
describe('Get user', () => {
it('Get user data', async done => {
let userEvent = {
headers: {
'authorization': 'Bearer TOKEN'
}
}
// user.authorizer();
user(userEvent, null, (error, data) => {
try {
expect(data.statusCode).toBe(200);
done();
} catch (error) {
done(error);
}
});
});
});

I tried using mock-jws library and it works for me.
here i am trying to test authorizer seperately by mocking jwks library provided by auth0 jsonwebtoken. If authorizer test is succesful then either you can separately test your protected endpoint or generate a jwt token, and generate a poilicy from the code i provided and if allow policy is returned test your protected endpoint
const createJWKSMock = require('mock-jwks');
const authorizer = require('../authorizer/authorizer');
describe('Auth Test', () => {
const jwks = createJWKSMock.default('https://your domain here');
beforeEach(() => {
jwks.start();
});
afterEach(() => {
jwks.stop();
});
test('should verify the token', async () => {
const token = jwks.token({
aud: 'https://your audience,
iss: 'https://issuer of token',
});
console.log(token);
const event = {
authorizationToken: `Bearer ${token}`,
};
const policy = await authorizer.auth(event, 'context');
console.log('jatin', policy);
expect(policy.context).not.toBe(undefined);
});

The authorizer key in serverless.yml basically tell API Gateway what authorizer function to use for a particular API endpoint, the authorizer is only used when you have deployed and invoke the lambda via API, you can't really test this flow locally, i.e. by unit test.
You don't have to test your function along with its authorizer, in case your authorizer will manipulate the event, like extract the JWT, you should mock those processes in your test and pass the processed event to the targeting function.
If you want to test authoirzer, you should write a separate test for it.

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

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();
});

request.cookies is undefined when using Supertest

I'm passing my authentication token via an HTTP-Only cookie in my NestJS API.
As such, when writing some E2E tests for my Auth endpoints, I'm having an issue with cookies not being where I expect them.
Here's my pared-down test code:
describe('auth/logout', () => {
it('should log out a user', async (done) => {
// ... code to create user account
const loginResponse: Response = await request(app.getHttpServer())
.post('/auth/login')
.send({ username: newUser.email, password });
// get cookie manually from response.headers['set-cookie']
const cookie = getCookieFromHeaders(loginResponse);
// Log out the new user
const logoutResponse: Response = await request(app.getHttpServer())
.get('/auth/logout')
.set('Cookie', [cookie]);
});
});
In my JWT Strategy, I'm using a custom cookie parser. The problem I'm having is that request.cookies is always undefined when it gets to the parser. However, the cookie will be present in request.headers.
I'm following the manual cookie example from this Medium article: https://medium.com/#juha.a.hytonen/testing-authenticated-requests-with-supertest-325ccf47c2bb, and there don't appear to be any other methods available on the request object to set cookies.
If I test the same functionality from Postman, everything works as expected. What am I doing wrong?
I know this is an old thread but...
I also had req.cookies undefined, but for a different reason.
I'm testing my router independently, not the top level app. So I bootstrap the app in beforeEach and add the route to test.
I was getting req.cookies undefined because express 4 requires the cookieParser middleware to be present to parse the cookies from the headers.
E.g.
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const request = require('supertest');
const {router} = require('./index');
describe('router', () => {
let app;
beforeAll(() => {
app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use('/', router);
});
beforeEach(() => jest.clearAllMocks());
it('GET to /', async () => {
const jwt = 'qwerty-1234567890';
const resp = await request(app)
.get('/')
.set('Cookie', `jwt=${jwt};`)
.set('Content-Type', 'application/json')
.send({});
});
});
Testing this way allows me to unit test a router in isolation of the app. The req.cookies turn up as expected.
Late but I hope I can help you. The problem is in the initialization of the app object. Probably in your main.ts file you have some middlewares configured as they are: cors and queryParse. You must also put them in your tests when you create the app.
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
const app = moduleFixture.createNestApplication();
// Add cors
app.enableCors({
credentials: true,
origin: ['http://localhost:4200'],
});
// Add cookie parser
app.use(cookieParser());
await app.init();
As per the article you're following, the code at https://medium.com/#juha.a.hytonen/testing-authenticated-requests-with-supertest-325ccf47c2bb :
1) has the 'cookie' value in .set('cookie', cookie) in lowercase and in your code it's in Pascal case ==> Have you tried with lowercase in your code instead ?
2) the cookie value assigned to the 'cookie' header is not an array, whereas in your code you're assigning an array ==> Have you tried with a non array value ?
So to resume, can you try with the following code:
describe('auth/logout', () => {
it('should log out a user', async (done) => {
// ... code to create user account
const loginResponse: Response = await request(app.getHttpServer())
.post('/auth/login')
.send({ username: newUser.email, password });
// get cookie manually from response.headers['set-cookie']
const cookie = getCookieFromHeaders(loginResponse);
// Log out the new user
const logoutResponse: Response = await request(app.getHttpServer())
.get('/auth/logout')
.set('cookie', cookie) // <== here goes the diff
.expect(200, done);
});
});
Let us know if that helps :)

AWS Amplify uses guest credentials, not authenticated creds, in API requests

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
})
)

Mock Firebase Admin Auth for Unit Testing Authenticated Routes

I'm using firebase-admin for authentication on my express backend. I have a middleware that checks if requests are authenticated.
public resolve(): (req, res, next) => void {
return async (req, res, next) => {
const header = req.header('Authorization');
if (!header || !header.split(' ')) {
throw new HttpException('Unauthorized', UNAUTHORIZED);
}
const token = header.split(' ')[1];
await admin.auth().verifyIdToken(token).then((decodedToken: any) => {
req.user = decodedToken;
next();
}).catch((error: any) => {
throw new HttpException(error, UNAUTHORIZED);
});
};
}
So far, I can only unit test my routes to make sure that they respond UNAUTHORIZED instead of NOT_FOUND.
it('GET /api/menu should return 401 ', done => {
const NOT_FOUND = 404;
const UNAUTHORIZED = 401;
supertest(instance)
.get('/api/menu')
.end((error, response: superagent.Response) => {
expect(response.status).not.toEqual(NOT_FOUND);
expect(response.status).toEqual(UNAUTHORIZED);
done();
});
});
But, I want to write more unit tests than this! I want to mock users so I can make AUTHORIZED requests! I want to use the type property I have on users to verify that users of a certain type can/cannot use certain routes. Does anyone have an idea of how I could do this with firebase-admin-node?
It looks like the firebase-admin-node repo generates tokens for unit tests here, but I'm not sure how I would apply that to my specific problem.