How to restore an expired token [AWS Cognito]? - javascript

I'm using AWS for my website. After 1 hour the token expires and the user pretty much can't do anything.
For now i'm trying to refresh the credentials like this:
function getTokens(session) {
return {
accessToken: session.getAccessToken().getJwtToken(),
idToken: session.getIdToken().getJwtToken(),
refreshToken: session.getRefreshToken().getToken()
};
};
function getCognitoIdentityCredentials(tokens) {
const loginInfo = {};
loginInfo[`cognito-idp.eu-central-1.amazonaws.com/eu-central-1_XXX`] = tokens.idToken;
const params = {
IdentityPoolId: AWSConfiguration.IdPoolId
Logins: loginInfo
};
return new AWS.CognitoIdentityCredentials(params);
};
if(AWS.config.credentials.needsRefresh()) {
clearInterval(messwerte_updaten);
cognitoUser.refreshSession(cognitoUser.signInUserSession.refreshToken, (err, session) => {
if (err) {
console.log(err);
}
else {
var tokens = getTokens(session);
AWS.config.credentials = getCognitoIdentityCredentials(tokens);
AWS.config.credentials.get(function (err) {
if (err) {
console.log(err);
}
else {
callLambda();
}
});
}
});
}
the thing is, after 1hour, the login token gets refreshed without a problem, but after 2hrs i can't refresh the login token anymore.
i also tried using AWS.config.credentials.get(), AWS.config.credentials.getCredentials() and AWS.config.credentials.refresh()
which doesn't work either.
The error messages i'm getting are:
Missing credentials in config
Invalid login token. Token expired: 1446742058 >= 1446727732

After almost 2 weeks i finally solved it.
You need the Refresh Token to receive a new Id Token. Once the Refreshed Token is acquired, update the AWS.config.credentials object with the new Id Token.
here is an example on how to set this up, runs smoothly!
refresh_token = session.getRefreshToken(); // you'll get session from calling cognitoUser.getSession()
if (AWS.config.credentials.needsRefresh()) {
cognitoUser.refreshSession(refresh_token, (err, session) => {
if(err) {
console.log(err);
}
else {
AWS.config.credentials.params.Logins['cognito-idp.<YOUR-REGION>.amazonaws.com/<YOUR_USER_POOL_ID>'] = session.getIdToken().getJwtToken();
AWS.config.credentials.refresh((err)=> {
if(err) {
console.log(err);
}
else{
console.log("TOKEN SUCCESSFULLY UPDATED");
}
});
}
});
}

Usually it's solved by intercepting http requests with additional logic.
function authenticationExpiryInterceptor() {
// check if token expired, if yes refresh
}
function authenticationHeadersInterceptor() {
// include headers, or no
}}
then with use of HttpService layer
return HttpService.get(url, params, opts) {
return authenticationExpiryInterceptor(...)
.then((...) => authenticationHeadersInterceptor(...))
.then((...) => makeRequest(...))
}
It could be solved by proxy as well http://2ality.com/2015/10/intercepting-method-calls.html
In relation to AWS:
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Credentials.html
You're interested in:
getPromise()
refreshPromise()

Here is how I implemented this:
First you need to authorize the user to the service and grant permissions:
Sample request:
Here is how I implemented this:
First you need to authorize the user to the service and grant permissions:
Sample request:
POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token&
Content-Type='application/x-www-form-urlencoded'&
Authorization=Basic aSdxd892iujendek328uedj
grant_type=authorization_code&
client_id={your client_id}
code=AUTHORIZATION_CODE&
redirect_uri={your rediect uri}
This will return a Json something like:
HTTP/1.1 200 OK
Content-Type: application/json
{"access_token":"eyJz9sdfsdfsdfsd", "refresh_token":"dn43ud8uj32nk2je","id_token":"dmcxd329ujdmkemkd349r", "token_type":"Bearer", "expires_in":3600}
Now you need to get an access token depending on your scope:
POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token
Content-Type='application/x-www-form-urlencoded'&
Authorization=Basic aSdxd892iujendek328uedj
grant_type=client_credentials&
scope={resourceServerIdentifier1}/{scope1} {resourceServerIdentifier2}/{scope2}
Json would be:
HTTP/1.1 200 OK
Content-Type: application/json
{"access_token":"eyJz9sdfsdfsdfsd", "token_type":"Bearer", "expires_in":3600}
Now this access_token is only valid for 3600 secs, after which you need to exchange this to get a new access token. To do this,
To get new access token from refresh Token:
POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token >
Content-Type='application/x-www-form-urlencoded'
Authorization=Basic aSdxd892iujendek328uedj
grant_type=refresh_token&
client_id={client_id}
refresh_token=REFRESH_TOKEN
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{"access_token":"eyJz9sdfsdfsdfsd", "refresh_token":"dn43ud8uj32nk2je", "id_token":"dmcxd329ujdmkemkd349r","token_type":"Bearer", "expires_in":3600}
You get the picture right.
If you need more details go here.

This is how you can refresh access token using AWS Amplify library:
import Amplify, { Auth } from "aws-amplify";
Amplify.configure({
Auth: {
userPoolId: <USER_POOL_ID>,
userPoolWebClientId: <USER_POOL_WEB_CLIENT_ID>
}
});
try {
const currentUser = await Auth.currentAuthenticatedUser();
const currentSession = currentUser.signInUserSession;
currentUser.refreshSession(currentSession.refreshToken, (err, session) => {
// do something with the new session
});
} catch (e) {
// whatever
}
};
More discussion here: https://github.com/aws-amplify/amplify-js/issues/2560.

Related

Getting error on console despite handling it (MERN + Redux)

I am sending axios get request whose end-point sends the user associated with the token stored in localStorage and then the redux state is updated with the user. When I don't have a token the end-point return a res with status 401 with message "Unauthorized" and then I handle it in the catch statement and set the "error" redux state. But even after doing this the error is displayed on the console like this:
Failed to load resource: the server responded with a status of 401 (Unauthorized) /users/auth:1
This is the function which makes api call and authorizes the user:
export function loadUser(){
return function (dispatch,getState){
dispatch(userLoading());
const token = getState().auth.token;
const config = {
headers:{
'Content-Type':'application/json'
}
}
if(token) config.headers['auth-token']=token;
axios.get('http://localhost:80/users/auth',config)
.then(user => {
dispatch(clearError())
dispatch(userLoaded(user.data))
})
.catch(error => {
dispatch(setError(error.response.status,error.response.data.msg));
dispatch(authError());
})
}
}
This is the middleware which handles the token before hitting the endpoint (In my case response is returned from here itself since there is no token sent):
function auth(req,res,next){
const token = req.header('auth-token');
if(!token) res.status(401).json({msg:"Unauthorized"})
else{
try{
const decoded = jwt.verify(token,jwt_secret);
req.user = decoded;
next();
}
catch(e){
res.status(400).json({msg:"Invalid token"})
}
}
}
I'm not able to figure out why am I getting error on console (State is getting updated as desired)
It is actually impossible to do with JavaScript. because of security concerns and a potential for a script to hide its activity from the user.
The best you can do is clearing them from your console.
console.clear();
I think it is because you are not getting the token when consulting your API.
If this is the case I recommend you use defaults.headers.common in this way
const axiosApi = axios.create({ baseURL: "http://localhost:80" });
const headerAuth = () => {
const token = getMyToken();
if (token) {
axiosApi.defaults.headers.common["Authorization"] = `Bearer ${token}`;
} else {
delete axiosApi.defaults.headers.common.Authorization;
}
};
export function loadUser(){
headerAuth(); // <-----
return function (dispatch,getState){
dispatch(userLoading());
axiosApi.get('/users/auth',config)
.then(user => {
dispatch(clearError())
dispatch(userLoaded(user.data))
})
.catch(error => {
dispatch(setError(error.response.status,error.response.data.msg));
dispatch(authError());
})
}
I recommend that you do not store the token in the REDUX but in sessionStorage

MS graph api get teams using javascript returns no results

i am using graph api javascript example from here https://learn.microsoft.com/en-us/graph/api/user-list-joinedteams?view=graph-rest-beta&tabs=javascript
and my code is like:
async function(req, res) {
if (!req.isAuthenticated()) {
// Redirect unauthenticated requests to home page
res.redirect('/')
} else {
let params = {
active: { calendar: true }
};
// Get the access token
var accessToken;
try {
accessToken = await tokens.getAccessToken(req);
console.log("access token is:", accessToken)
} catch (err) {
req.flash('error_msg', {
message: 'Could not get access token. Try signing out and signing in again.',
debug: JSON.stringify(err)
});
}
if (accessToken && accessToken.length > 0) {
try {
console.log("vik testing stuff12 for teams")
const user = await graph.getTeams(accessToken)
console.log("graph me:::", user)
} catch (err) {
req.flash('error_msg', {
message: 'Could not fetch events',
debug: JSON.stringify(err)
});
}
} else {
req.flash('error_msg', 'Could not get an access token');
}
res.render('calendar', params);
}
}
getTeams is
getTeams: async function(accessToken) {
const client = getAuthenticatedClient(accessToken);
const events = await client
.api('/me/joinedTeams')
.version('beta')
.get();
return events;
}
this prints no results and no error. if I replace 'me/joinedTeams' to just 'me' then it returns logged in user details.
You can got a response successfully, so it seems no error with your code as you said if you call https://graph.microsoft.com/v1.0/me you can get user information.
And I tried to call this API using my account(my account hasn't joined any Teams), and got response like below, so if you got the same response as mine, perhaps you need to check if you have joined any Teams:
On the other hand, following the document, this API needs several permissions. So please obtain your access token when debug and use JWT tool to decrypt it to check if the access token have enough scope.
And I used the same request and got Teams information after adding my account to a team.

JWT Authorization with Axios and Vue.js (Header)

I'm still pretty new to web development, so I apologize in advance if the solution is obvious or my question is asked poorly.
So: I would like to use JWT to authenticate my users. I use axios, vue.js and of course JWT. I would like to access a secure route:
router.post('/secureroute', checkAuth, (req, res) => {
res.status(200).json({
message: 'all ok'
})
});
In order to do so, I use this check-auth.js:
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(" ")[1];
console.log(token);
const decoded = jwt.verify(token, process.env.SECRET_KEY);
next();
} catch (error) {
return res.status(401).json({
message: 'Auth failed'
})
}
next();
}
part of my Login.vue:
methods: {
login() {
if (!this.username) this.alertUsername = true;
if (!this.password) this.alertPassword = true;
axios
.post("/user/login", {
username: this.username,
password: this.password
})
.then(res => {
localStorage.setItem("usertoken", res.data.token);
if (res.data.token) {
console.log("Success");
router.push({ name: "start" });
} else {
this.alertWrong = true;
}
this.username = "";
this.password = "";
})
.catch(err => {
console.log(err);
});
this.emitMethod();
}
Using postman with an authorization header, everything seems to work fine. But after hours of searching the Internet and trying things out, I simply do not know how to make it work with the real website. I would like to pass the JWT as an authorization-header. I know that it is possible with axios, but I really don't know how I can do so in my example here.
You've got your login flow, and you are storing the usertoken in localStorage as the usertoken key. You also verified that your requests are processed correctly if the authorization header is set.
The easiest way to work with api requests is by abstracting axios a bit more, to automatically add the authorization token, and maybe pre-process the response you get back. For example, you may want to handle some errors globally instead of on a case-by-case basis, or want to transform the request into something that is always the same.
You first want to make some abstraction that calls axios.request. You can pass it a configuration object as described here. What's most important for you right now is the headers key-value pair, but you may want to expand this in the future.
export default request (config) {
const userToken = window.localStorage.getItem('usertoken');
const requestConfig = { ...config };
if (!requestConfig.headers) {
requestConfig.headers = {};
}
if (userToken) {
requestConfig.headers.authorization = `Bearer ${userToken}`;
}
return axios.request(requestConfig);
}
Now we can expand on that:
export default post (url, data = {}, config = {}) {
return request({
...config,
method: 'POST'
url,
data
});
}
When inspecting the request in your developer console you should now see that, if the user token is correctly set in localStorage, you have an extra header in your request that is sent to the server.

Wrap javascript fetch to add custom functionality

I would like to know if it is possible to do this, because I'm not sure if I'm wrong or if it isn't possible. Basically, what I want to do is to create a wrap function for native fetch javascript function. This wrap function would implement token validation process, requesting a new accessToken if the one given is expired and requesting again the desired resource. This is what I've reached until now:
customFetch.js
// 'url' and 'options' parameters are used strictely as you would use them in fetch. 'authOptions' are used to configure the call to refresh the access token
window.customFetch = (url, options, authOptions) => {
const OPTIONS = {
url: '',
unauthorizedRedirect: '',
storage: window.sessionStorage,
tokenName: 'accessToken'
}
// Merge options passed by user with the default auth options
let opts = Object.assign({}, OPTIONS, authOptions);
// Try to update 'authorizarion's header in order to send always the proper one to the server
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
// Actual server request that user wants to do.
const request = window.fetch(url, options)
.then((d) => {
if (d.status === 401) {
// Unauthorized
console.log('not authorized');
return refreshAccesToken();
}
else {
return d.json();
}
});
// Auxiliar server call to get refresh the access token if it is expired. Here also check if the
// cookie has expired and if it has expired, then we should redirect to other page to login again in
// the application.
const refreshAccesToken = () => {
window.fetch(opts.url, {
method: 'get',
credentials: 'include'
}).then((d) => {
// For this example, we can omit this, we can suppose we always receive the access token
if (d.status === 401) {
// Unauthorized and the cookie used to validate and refresh the access token has expired. So we want to login in to the app again
window.location.href = opts.unauthorizedRedirect;
}
return d.json();
}).then((json) => {
const jwt = json.token;
if (jwt) {
// Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
opts.storage.setItem(opts.tokenName, jwt);
console.log('new acces token: ' + jwt);
// Re-send the original request when we have received the refreshed access token.
return window.customFetch(url, options, authOptions);
}
else {
console.log('no token has been sent');
return null;
}
});
}
return request;
}
consumer.js
const getResourcePrivate = () => {
const url = MAIN_URL + '/resource';
customFetch(url, {
method: 'get'
},{
url: AUTH_SERVER_TOKEN,
unauthorizedRedirect: AUTH_URI,
tokenName: TOKEN_NAME
}).then((json) => {
const resource = json ? json.resource : null;
if (resource) {
console.log(resource);
}
else {
console.log('No resource has been provided.');
}
});
}
I'll try to explain a little better the above code: I want to make transparent for users the token validation, in order to let them just worry about to request the resource they want. This approach is working fine when the accessToken is still valid, because the return request instruction is giving to the consumer the promise of the fetch request.
Of course, when the accessToken has expired and we request a new one to auth server, this is not working. The token is refreshed and the private resource is requested, but the consumer.js doesn't see it.
For this last scenario, is it possible to modify the flow of the program, in order to refresh the accessToken and perform the server call to get the private resource again? The consumer shouldn't realize about this process; in both cases (accessToken is valid and accessToken has expired and has been refreshed) the consumer.js should get the private requested resource in its then function.
Well, finally I've reached a solution. I've tried to resolve it using a Promise and it has work. Here is the approach for customFetch.js file:
window.customFetch = (url, options, authOptions) => {
const OPTIONS = {
url: '',
unauthorizedRedirect: '',
storage: window.sessionStorage,
tokenName: 'accessToken'
}
// Merge options passed by user with the default auth options
let opts = Object.assign({}, OPTIONS, authOptions);
const requestResource = (resolve) => {
// Try to update 'authorizarion's header in order to send always the proper one to the server
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
window.fetch(url, options)
.then((d) => {
if (d.status === 401) {
// Unauthorized
console.log('not authorized');
return refreshAccesToken(resolve);
}
else {
resolve(d.json());
}
});
}
// Auxiliar server call to get refresh the access token if it is expired. Here also check if the
// cookie has expired and if it has expired, then we should redirect to other page to login again in
// the application.
const refreshAccesToken = (resolve) => {
window.fetch(opts.url, {
method: 'get',
credentials: 'include'
}).then((d) => {
if (d.status === 401) {
// Unauthorized
window.location.href = opts.unauthorizedRedirect;
}
return d.json();
}).then((json) => {
const jwt = json.token;
if (jwt) {
// Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
opts.storage.setItem(opts.tokenName, jwt);
console.log('new acces token: ' + jwt);
// Re-send the original request when we have received the refreshed access token.
requestResource(resolve);
}
else {
console.log('no token has been sent');
return null;
}
});
}
let promise = new Promise((resolve, reject) => {
requestResource(resolve);
});
return promise;
}
Basically, I've created a Promise and I've called inside it to the function which calls to server to get the resource. I've modified a little the request(now called requestResource) and refreshAccessToken in order to make them parametrizable functions. And I've passed to them the resolve function in order to "resolve" any function once I've received the new token.
Probably the solution can be improved and optimized, but as first approach, it is working as I expected, so I think it's a valid solution.
EDIT: As #Dennis has suggested me, I made a mistake in my initial approach. I just had to return the promise inside the refreshAccessToken function, and it would worked fine. This is how the customFetch.js file should look (which is more similar to the code I first posted. In fact, I've just added a return instruction inside the function, although removing the start and end brackets would work too):
// 'url' and 'options' parameters are used strictely as you would use them in fetch. 'authOptions' are used to configure the call to refresh the access token
window.customFetch = (url, options, authOptions) => {
const OPTIONS = {
url: '',
unauthorizedRedirect: '',
storage: window.sessionStorage,
tokenName: 'accessToken'
}
// Merge options passed by user with the default auth options
let opts = Object.assign({}, OPTIONS, authOptions);
// Try to update 'authorizarion's header in order to send always the proper one to the server
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
// Actual server request that user wants to do.
const request = window.fetch(url, options)
.then((d) => {
if (d.status === 401) {
// Unauthorized
console.log('not authorized');
return refreshAccesToken();
}
else {
return d.json();
}
});
// Auxiliar server call to get refresh the access token if it is expired. Here also check if the
// cookie has expired and if it has expired, then we should redirect to other page to login again in
// the application.
const refreshAccesToken = () => {
return window.fetch(opts.url, {
method: 'get',
credentials: 'include'
}).then((d) => {
// For this example, we can omit this, we can suppose we always receive the access token
if (d.status === 401) {
// Unauthorized and the cookie used to validate and refresh the access token has expired. So we want to login in to the app again
window.location.href = opts.unauthorizedRedirect;
}
return d.json();
}).then((json) => {
const jwt = json.token;
if (jwt) {
// Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
opts.storage.setItem(opts.tokenName, jwt);
console.log('new acces token: ' + jwt);
// Re-send the original request when we have received the refreshed access token.
return window.customFetch(url, options, authOptions);
}
else {
console.log('no token has been sent');
return null;
}
});
}
return request;
}

NodeJs - Retrieve user information from JWT token?

Node and Angular. I have a MEAN stack authentication application where I am setting a JWT token on successful login as follows, and storing it in a session in the controller. Assigning the JWT token to config.headers through service interceptor:
var token = jwt.sign({id: user._id}, secret.secretToken, { expiresIn: tokenManager.TOKEN_EXPIRATION_SEC });
return res.json({token:token});
authservice.js Interceptor(omitted requestError,response and responseError):
authServices.factory('TokenInterceptor', ['$q', '$window', '$location','AuthenticationService',function ($q, $window, $location, AuthenticationService) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
}
return config;
}
};
}]);
Now I wanted to get the logged in user details from the token, How can I do that? I tried as follows, not working. When I log the error from Users.js file it's saying "ReferenceError: headers is not defined"
authController.js:
$scope.me = function() {
UserService.me(function(res) {
$scope.myDetails = res;
}, function() {
console.log('Failed to fetch details');
$rootScope.error = 'Failed to fetch details';
})
};
authService.js:
authServices.factory('UserService',['$http', function($http) {
return {
me:function() {
return $http.get(options.api.base_url + '/me');
}
}
}]);
Users.js (Node):
exports.me = function(req,res){
if (req.headers && req.headers.authorization) {
var authorization =req.headers.authorization;
var part = authorization.split(' ');
//logic here to retrieve the user from database
}
return res.send(200);
}
Do i have to pass the token as a parameter too for retrieving the user details? Or save the user details in a separate session variable as well?
First of all, it is a good practice to use Passport middleware for user authorization handling. It takes all the dirty job of parsing your request and also provides many authorization options.
Now for your Node.js code.
You need to verify and parse the passed token with jwt methods and then find the user by id extracted from the token:
exports.me = function(req,res){
if (req.headers && req.headers.authorization) {
var authorization = req.headers.authorization.split(' ')[1],
decoded;
try {
decoded = jwt.verify(authorization, secret.secretToken);
} catch (e) {
return res.status(401).send('unauthorized');
}
var userId = decoded.id;
// Fetch the user by id
User.findOne({_id: userId}).then(function(user){
// Do something with the user
return res.send(200);
});
}
return res.send(500);
}
Find a token from request data:
const usertoken = req.headers.authorization;
const token = usertoken.split(' ');
const decoded = jwt.verify(token[1], 'secret-key');
console.log(decoded);
Your are calling the function UserService.me with two callbacks, although the function does not accept any arguments. What I think you want to do is:
$scope.me = function() {
UserService.me().then(function(res) {
$scope.myDetails = res;
}, function() {
console.log('Failed to fetch details');
$rootScope.error = 'Failed to fetch details';
});
};
Also, note that the $http methods return a response object. Make sure that what you want is not a $scope.myDetails = res.data
And in your Users.js file, you are using the variable headers.authorization directly, whereas it should be req.header.authorization:
var authorization = req.headers.authorization;
According to the documentation https://github.com/themikenicholson/passport-jwt, you could use request.user. Note, I'm supposing that you are using passport with passport-jwt.
It's possible because passport during the context of an authentication is setting the request object and populating the user property. So, just access that property. You don't need to do a middleware.
Anderson anzileiro is correct. If you return the full token in the middleware code, the request is indeed populated with the user property and you can access your profile.
passport.use(
new JWTstrategy(
{
secretOrKey: process.env.ACCESS_TOKEN_SECRET,
// jwtFromRequest: ExtractJWT.fromUrlQueryParameter('secret_token')
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
},
async (token, done) => {
try {
return done(null, token);
} catch (error) {
done(error);
}
}
)
);
req.user will return :
{
"user": {
"username": "admin"
},
"iat": 1625920948,
"exp": 1626007348
}

Categories