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
}
Related
I make a lowdb request to update a JSON file, but only the date is updated. When I make a console.log of the information to update, this is what I want to update:
res.on("end", function () {
let body = Buffer.concat(chunks);
let final = JSON.parse(body);
if (final.error !== undefined) {
console.log("Initial authentication:", final.error_description, "Please refresh the authentication grant");
extAuthCallback(84);
} else {
tokens.set('access_token', final.access_token)
.set('expires_in', final.expires_in)
.set('refresh_token', final.refresh_token)
.set('refresh_date', moment())
.write()
console.log(final, tokens.value())
extAuthCallback(1);
}
});
console.log of my final variable:
{
access_token: 'oa_prod_iq00cRPk5Jhh4VffSHlDj7DEDsSIlpCRRczI3l3ASC0',
token_type: 'bearer',
expires_in: 2399,
refresh_token: 'oa_prod_nIjBZs74xGvJXi1B-wdMyITfxGyklpCRRczI3l3ASC0'
}
console.log of my JSON file after the request:
{
access_token: 'oa_prod_pB9Q0FFM9Tk4c5n3HMRBFKAVz6naiJ-jmb3QCeBrT00',
expires_in: 2399,
refresh_token: 'oa_prod_nX3EDs530SM8eHv_fM5BN7-5RLBwkrKoUi6uExBbTY4',
refresh_date: '2020-11-28T23:31:13.855Z',
primary_autorization_date: '2020-11-29T00:40:58.421Z'
}
My JSON file after the modifications:
{
"access_token": "oa_prod_pB9Q0FFM9Tk4c5n3HMRBFKAVz6naiJ-jmb3QCeBrT00",
"expires_in": 2399,
"refresh_token": "oa_prod_nX3EDs530SM8eHv_fM5BN7-5RLBwkrKoUi6uExBbTY4",
"refresh_date": "2020-11-28T23:31:13.855Z",
"primary_autorization_date": "2020-11-29T00:40:58.421Z"
}
So it only has the primary_autorization_date field changing...
You should use set instead of update.
tokens.set('access_token', final.access_token)
.set('expires_in', final.expires_in)
.set('refresh_token', final.refresh_token)
.set('refresh_date', moment())
.write()
The update method is accepted a function like this.
db.update('test1', (n) => 5555)
.update('test2', (n) => n + 1)
.write()
If you use set, you just need to assign the value to it.
db.set('test1', 5555).set('test2', 3333).write()
And when you use moment, there are two ways to you could use.
// Way 1 with moment()
db.set('date', moment()).write()
// Way 2 with moment
db.update('date', moment).write()
So the solution is:
I call my function that contains the HTTP request in another file like this:
app.get('/', async function(req, res) {
const access_token = req.query.code;
if (access_token) {
let authentication = await asyncExtAuth(access_token);
if (authentication == 84)
return res.send({error: "Please give a valid access_token"});
res.sendFile(path.join(__dirname + '/success_page/index.html'));
db.set('access_token', access_token).write()
tokens.set('primary_autorization_date', moment()).write()
console.log("Access Token successfully refreshed")
}
else
res.send({error: "Please specify an access token"})
})
In which I modify a second time my file with the line tokens.set('primary_autorization_date', moment()).write(). By doing this, lowdb doesn't take into account the modification made just before and re-modifies my file with the information it contained before. The solution is to add the line tokens.read() just before modifying the file to update the cache:
app.get('/', async function(req, res) {
const access_token = req.query.code;
if (access_token) {
let authentication = await asyncExtAuth(access_token);
if (authentication == 84)
return res.send({error: "Please give a valid access_token"});
res.sendFile(path.join(__dirname + '/success_page/index.html'));
db.set('access_token', access_token).write()
tokens.read()
tokens.set('primary_autorization_date', moment()).write()
console.log("Access Token successfully refreshed")
}
else
res.send({error: "Please specify an access token"})
})
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.
I am releasing access to pages using connect-roles and loopback but I have a pertinent question about how I can collect the customer's role and through the connect-roles to read the session and respond to a route.
Example, when the client logs in I load a string containing the client's role and access it in a function that controls access to pages.
I have this doubt because I'm finalizing a large scale service that usually there are multiple client sessions that are accessed instantly using a same storage and check function.
It would be efficient to store the customer's role using app.set() and app.get()?
app.get('/session-details', function (req, res) {
var AccessToken = app.models.AccessToken;
AccessToken.findForRequest(req, {}, function (aux, accesstoken) {
// console.log(aux, accesstoken);
if (accesstoken == undefined) {
res.status(401);
res.send({
'Error': 'Unauthorized',
'Message': 'You need to be authenticated to access this endpoint'
});
} else {
var UserModel = app.models.user;
UserModel.findById(accesstoken.userId, function (err, user) {
// console.log(user);
res.status(200);
res.json(user);
// storage employee role
app.set('employeeRole', user.accessLevel);
});
}
});
});
Until that moment everything happens as desired I collect the string loaded with the role of the client and soon after I create a connect-roles function to validate all this.
var dsConfig = require('../datasources.json');
var path = require('path');
module.exports = function (app) {
var User = app.models.user;
var ConnectRoles = require('connect-roles');
const employeeFunction = 'Developer';
var user = new ConnectRoles({
failureHandler: function (req, res, action) {
// optional function to customise code that runs when
// user fails authorisation
var accept = req.headers.accept || '';
res.status(403);
if (~accept.indexOf('ejs')) {
res.send('Access Denied - You don\'t have permission to: ' + action);
} else {
res.render('access-denied', {action: action});
// here
console.log(app.get('employeeRole'));
}
}
});
user.use('authorize access private page', function (req) {
if (employeeFunction === 'Manager') {
return true;
}
});
app.get('/private/page', user.can('authorize access private page'), function (req, res) {
res.render('channel-new');
});
app.use(user.middleware());
};
Look especially at this moment, when I use the
console.log(app.get('employeeRole')); will not I have problems with simultaneous connections?
app.get('/private/page', user.can('authorize access private page'), function (req, res) {
res.render('channel-new');
});
Example client x and y connect at the same time and use the same function to store data about your session?
Being more specific when I print the string in the console.log(app.get('employeeRole')); if correct my doubt, that I have no problem with simultaneous connections I will load a new variable var employeeFunction = app.get('employeeRole'); so yes my function can use the object containing the role of my client in if (employeeFunction === 'Any Role') if the role that is loaded in the string contain the required role the route it frees the page otherwise it uses the callback of failureHandler.
My test environment is limited to this type of test so I hope you help me on this xD
Instead of using app.set you can create a session map(like hashmaps). I have integrated the same in one of my projects and it is working flawlessly. Below is the code for it and how you can access it:
hashmap.js
var hashmapSession = {};
exports.auth = auth = {
set : function(key, value){
hashmapSession[key] = value;
},
get : function(key){
return hashmapSession[key];
},
delete : function(key){
delete hashmapSession[key];
},
all : function(){
return hashmapSession;
}
};
app.js
var hashmap = require('./hashmap');
var testObj = { id : 1, name : "john doe" };
hashmap.auth.set('employeeRole', testObj);
hashmap.auth.get('employeeRole');
hashmap.auth.all();
hashmap.auth.delete('employeeRole');
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.
I am following a book tutorial, I am currently building the authentication for the app. Whenever I login correctly, I can't seem to set the token back into the request. The error I am getting is:
Failed to execute 'setRequestHeader' on 'XMLHttpRequest': 'function () {
return $window.localStorage.getItem('token');
}' is not a valid HTTP header field value.
Any help would be greatly appreciated
authService.js
angular.module('authService', [])
// ===================================================
// auth factory to login and get information
// inject $http for communicating with the API
// inject $q to return promise objects
// inject AuthToken to manage tokens
// ===================================================
.factory('Auth', function($http, $q, AuthToken) {
// create auth factory obj
var authFactory = {};
// login user
authFactory.login = function(username, password) {
// return promise obj and its data
return $http.post('/api/authenticate', {
username: username,
password: password
})
.success(function(data) {
console.log(data);
AuthToken.setToken(data.token);
return data;
});
};
// logout user by clearing token
authFactory.logout = function() {
AuthToken.setToken();
};
// check if user is logged in
// checks for local token
authFactory.isLoggedIn = function() {
if (AuthToken.getToken())
return true;
else
return false;
};
// get logged in user
authFactory.getUser = function() {
if (AuthToken.getToken())
return $http.get('/api/me', { cache : true});
else
return $q.reject({ message : 'User has no token.'});
};
return authFactory;
})
// ===================================================
// factory for handling tokens
// inject $window to store token client-side
//
//
// ===================================================
.factory('AuthToken', function($window) {
var authTokenFactory = {};
// get token out of local storage
authTokenFactory.getToken = function() {
return $window.localStorage.getItem('token');
};
// function to set token or clear token
// if a token is passed, set the token
// if there is no token, clear it from local storage
authTokenFactory.setToken = function(token) {
if (token)
$window.localStorage.setItem('token', token);
else
$window.localStorage.removeItem('token');
};
return authTokenFactory;
})
// ===================================================
// application configuration to integrate token into requests
// ===================================================
.factory('AuthInterceptor', function($q, $location, AuthToken) {
var interceptorFactory = {};
// this will happen on all http requests
interceptorFactory.request = function(config) {
// grab token
var token = AuthToken.getToken;
// if token exists add it to the header as x-access-token
if (token)
config.headers['x-access-token'] = token;
return config;
};
// happens on response errors
interceptorFactory.responseError = function(response) {
// if 403 from server
if (response.status == 403) {
AuthToken.setToken();
$location.path('/login')
}
//return the errors from server as promise
return $q.reject(response);
};
return interceptorFactory;
});
app.js
var app = angular.module('userApp', [
'ngAnimate', 'app.routes', 'authService', 'mainCtrl', 'userCtrl', 'userService']);
// app config to integrate token into req
app.config(function($httpProvider) {
// attach our auth interceptor to the http reqs
$httpProvider.interceptors.push('AuthInterceptor');
});
app.controller('mainController', function($http) {
// Bind this to view / vm-view model
var vm = this;
// define variables and objects on this
// this lets them be available to our views
// define a basic variable
vm.message = 'Hey! Message';
$http.get('/api/users')
.then(function(data) {
// bind users to vm.users
vm.users = data.users;
});
});
In custom interceptor factory
interceptorFactory.request = function(config) {
// grab token
var token = AuthToken.getToken;
// if token exists add it to the header as x-access-token
if (token)
config.headers['x-access-token'] = token;
return config;
};
change AuthToken.getToken; to AuthToken.getToken();
and your error was quite clear that you were passing function into header instead of value
Failed to execute 'setRequestHeader' on 'XMLHttpRequest': 'function () {
return $window.localStorage.getItem('token');
}' is not a valid HTTP header field value.