aws-amplify Authentication...how to access tokens on successful Auth.signIn? - amazon-web-services

I'm trying to figure out how to access the accessToken, refreshToken, and idToken that I receive back from aws-amplify using the Auth library.
example in docs: https://aws.github.io/aws-amplify/media/authentication_guide.html
example of my usage:
const user = await Auth.signIn(email, password);
user has a bunch of properties that are inaccessible including everything I need. In the docs, it's unclear how to get to these properties because the examples all log the result. Any ideas?

Auth.currentSession().then(res=>{
let accessToken = res.getAccessToken()
let jwt = accessToken.getJwtToken()
//You can print them to see the full objects
console.log(`myAccessToken: ${JSON.stringify(accessToken)}`)
console.log(`myJwt: ${jwt}`)
})

Auth.currentSession() will return a CognitoUserSession containing accessToken, idToken, and refreshToken.
The CognitoUserSession is actually the following: CognitoUserSession {idToken: CognitoIdToken, refreshToken: CognitoRefreshToken, accessToken: CognitoAccessToken, clockDrift: 0}
Accessing pairs within that object can be achieved through straightforward dot notation at this point.
Example: Retrieve the accessToken and log to console
Auth.currentSession().then(data => console.log(data.accessToken));
The result will be a CognitoAccessToken in the form CognitoAccessToken { jwtToken: '', payload: ''}
If you just want the jwtToken within the CognitoAccessToken, it's just dot notation all the way down (with log to console example):
Auth.currentSession().then(data => console.log(data.accessToken.jwtToken));
Note: This method also refreshes the current session if needed (reference).

I believe you can do
Auth.currentCredentials(credentials => {
const tokens = Auth.essentialCredentials(credentials);
})
where essentialCredentials will return all of the tokens
Hope this helps.

Angular 9, getting JWT token from current session :
import Auth from '#aws-amplify/auth';
Auth.currentSession().then(data => console.log("JWT", data.getAccessToken().getJwtToken()));

For those in search of the AWSCredentials:
const checkCognitoUserSession = async () => {
const getAwsCredentials = await Auth.currentCredentials();
const awsCredentials = await Auth.essentialCredentials(getAwsCredentials);
// accessKeyId, secretAccessKey, sessionToken post login
return { awsCredentials };
};

Retrieve current session using aws-amplify
Auth.currentSession() returns a CognitoUserSession object which contains JWT accessToken, idToken, and refreshToken.
Auth.currentSession()
.then((data) => console.log(data))
.catch((err) => console.log(err));
aws-amplify Docs currentSession

Related

can we add custom data while requesting jwt id token in aws

exports.signIn = (body) => {
return new Promise((resolve, reject) => {
var authenticationData = {
Username: body['username'],
Password: body['password'],
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: body['username'],
Pool: userPool,
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
resolve({
"status": 1, "message": "user signed in successfully ", "data": {
"idToken": result.getIdToken().getJwtToken(),
"accessToken": result.getAccessToken().getJwtToken(),
"refreshToken": result.getRefreshToken().getToken()
}
});
},
onFailure: (error) => {
let message = "User sign in failed " + error
let status = 0
if(error.code == 'NotAuthorizedException'){
message = "Incorrect username or password"
} else if(error.code == 'UserNotConfirmedException'){
message = "User confirmation pending with OTP"
status = 2
}
reject({ "status": status, "message": message });
},
});
})
}
I need to add custom data inside the id token. The data is dynamic, so I cannot add it as a custom field in cogito user detail. The exact requirement is: Just before creating the id token, I need to fetch the data from the database and include it with JWT id token.
If you want a cognito jwt token, it's not possible at the moment.
In order to add data to JWT id token, you need decode the token, add data, and encode the updated data. However, in order to encode the updated data, you would need the private key that AWS cognito uses, and there is no way to get it or use your own private key at the moment to my knowledge.
An alternative method would be to use your own private key when you encode the updated data. Yet, the token would not be a Cognito JWT anymore and could cause problems in other parts of your app.
Therefore, my suggestion is to pass the data separately instead of including it in the JWT token. If you can include JWT token in your future requests, you can also include a parameter or a body as well. If it is a sensitive data, you can encode it with the typical algorithms
References:
where can I find the secret key for the JWT from cognito

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

Chai-http doesn't seem to accept header always sends 401

I am trying to test my API routes that are protected by using jsonwebtoken.
I have tested my routes in postman and have gotten the correct results however running the same tests using mocha/chai/chai-http is not working.
Everytime I test a protected route I receive a 401 'unauthorized'
describe('user', () => {
let user, token
beforeEach(async () => {
await dropDb()
user = await new User({ email: 'testing#test.com', password: 'password' }).save()
const payload = { userid: user.id, role: user.roles.title, status: user.roles.status }
token = jwt.sign(payload, secret, { expiresIn: 3600 })
})
it('should return a list of users when logged in', async () => {
console.log(token)
const result = await chai.request(app)
.get('/api/user')
.set('Authorization', `Bearer ${token}`)
expect(result).to.have.status(200)
expect(result).to.be.json
})
})
My gut feeling was that there was somehow a race condition where the token being passed into the set wasn't finished being signed before the test ran. But inside the result it appears that my token has been set.
Additionally if I console.log the token after I can see that it matches the token created. Anybody run into a similar issue?

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

How can I force a cognito token refresh from the client

I am using aws amplify and I know that the tokens get automatically refreshed when needed and that that is done behind the scenes.
What I need to do is change a custom attribute on the user in the cognito user pool via a Lambda backend process. This I can do, and it is working. However, the web client user never sees this new custom attribute and I am thinking the only way they can see it is if the token gets refreshed since the value is stored within the JWT token.
The correct solution as of 2021 is to call:
await Auth.currentAuthenticatedUser({bypassCache: true})
Here is how you can update tokens on demand (forcefully)
import { Auth } from 'aws-amplify';
try {
const cognitoUser = await Auth.currentAuthenticatedUser();
const currentSession = await Auth.currentSession();
cognitoUser.refreshSession(currentSession.refreshToken, (err, session) => {
console.log('session', err, session);
const { idToken, refreshToken, accessToken } = session;
// do whatever you want to do now :)
});
} catch (e) {
console.log('Unable to refresh Token', e);
}
Like it's said here:
https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html
The access token and ID token are good for 1 hour. With Amplify you can get the info about the session using currentSession or currentUserInfo in Auth class to be able to retrieve information about tokens.
Undocumented, but you can use the refreshSession method on the User. Your next call to currentAuthenticatedUser and currentSession will have updated profile attributes (and groups)
User = Auth.currentAuthenticatedUser()
Session = Auth.currentSession()
User.refreshSession(Session.refreshToken)
#andreialecu wrote the correct answer. For full code to get the JWT:
static async amplifyRefresh() {
try {
const currentUser = await Auth.currentAuthenticatedUser({ bypassCache: true })
const currentSession = await Auth.currentSession()
const jwt = currentSession.getIdToken().getJwtToken()
// do what you want
} catch (error) {
console.log("error refreshing token: ", error)
throw error
}
}