I have been trying to solve this for several days.
I followed the tutorial in Auth0's documentation.
After decoding the token with express-jwt:
export let headerJWTCheck = expressJwt({
secret: '*************************',
audience: '******************************'
});
the content of req.user doesn't have the profile and roles that I need for role restrictions in the API.
Instead the content is in the form:
{ iss: 'https://******.eu.auth0.com/',
sub: 'google-oauth2|***********************',
aud: '********************',
exp: **************,
iat: **************}
In the front end I already get the user profile information I need, but I can't progress beyond that.
I'm using a function to restric the roles:
export function requireRole(role: string) {
return function (req, res, next) {
console.log(req.user);
var appMetadata = req.user.profile._json.app_metadata || {};
var roles = appMetadata.roles || [];
if (roles.indexOf(role) != -1) {
next();
} else {
res.redirect('/unauthorized');
}
}
but req.user.profile is always undefined.
In the main express application definition I have:
app.use(cookieParser());
app.use(session({
.................
}));
configurePassport();
app.use(passport.initialize());
app.use(passport.session());
The express-jwt library is initializing req.user based on the contents of the token that was included in the request.
If you require information at the req.user level you'll either need to include that information directly on the token or do an enrichment of req.user before running the role checks. For example, you could enrich req.user by running additional code that gets the roles based on the sub claim (user identifier).
The solution was to add the following code the auth0-lock configuration:
auth: {
params: {
scope: 'openid app_metadata roles'
}
}
Which added the app_metadata and the roles array to req.user after decoding the token.
Related
How can I implement multiple authentications in nodejs for a education firm, having three role- student, parent, and admin using REST API approach i.e token based authentication.
0. Concept
A JWT token (https://jwt.io/), contains a payload, within this payload you can specify a custom role object. Within the role object you can set boolean values to determine if the role is student, parent or admin.
Example Payload
{
...
"role": {
student: true,
parent: false,
admin: false,
}
}
When you go to generate your token for a specific user, attach the payload details to the token. (Ofcourse you would adjust the payload details depending on whether you want the user to be a student, parent or admin).
Whenever a user makes a request in the future using their token, you can make a callback to check their token and look at the payload.role object to see what role the user has and then make a decision as to whether they are authorized to perform a specific action or not.
1. Generating the token
See https://www.npmjs.com/package/jsonwebtoken for more information on generating a token.
const payload = {
userid: 123,
role: {
student: false,
parent: false,
admin: true,
},
};
const signOptions = {
issuer: this.config.jwt.issuer,
subject: payload.userid,
audience: this.config.jwt.audience,
expiresIn: "730d",
algorithm: "RS256",
};
const token = jwt.sign(payload, this.config.jwt.privateKey.replace(/\\n/g, "\n"), signOptions);
2. Middleware To Check Role
You should already have something like this if youre are using passport with JWT authentication.
const authGuard = PassportMiddleware.authenticate("jwt", { session: false });
router.get("/admin", authGuard, controller.index);
We need a new middleware to handle the checking of roles. We will call this middleware adminGuard. After authenticating using the authGuard middleware the req object will contain a user (which is the jwt payload). Now that we have the user information we can check what role they have.
const adminGuard = (req, res, next) => {
if(req.user && !req.user.role.admin) {
next(new Error('You are not an admin'));
} else {
next();
}
}
router.get("/admin", authGuard, adminGuard, controller.index);
You can create a new middleware guard for each role.
I have a big CMS built with Meteor that until now used basic-auth to login as we didn't have more than one editor. However, more people will start working with it, so I am trying to create a login functionality through LDAP so any authorised employee can login with their username/password
I tried to do my best given poor documentation of WebApp.connectHandlers and the lack of content integrating passport with Meteor. I based my LDAP login code on express examples (assuming WebApp.connectHandlers.use() was the Meteor equivalent of Express' app.use())
The login part works, that is to query and verify the user through LDAP.
However I cannot figure how to identify the logged-in user when they make a Meteor.call( ).
What I have in mind at the moment is to send a JWT token back to the authenticated user, store it in a cookie or something, and whenever a Meteor.call( ) is made, the client passes the token as a parameter. Then I would be able to identify the caller and can for example store the username in the database as the person who made a certain change.
Good to mention that I am trying to avoid using Meteor's accounts system as the requirement is to use LDAP without creating any extra tables (that's why the complication of using those several packages together).
Here's my code, with working login but no idea how to pass back the token to the user.
Maybe my whole JWT logic is wrong, I would appreciate any help/suggestions.
var basicAuth = require("basic-auth");
var passport = require("passport");
var bodyParser = require("body-parser");
var LdapStrategy = require("passport-ldapauth");
var jwt = require("jsonwebtoken");
// Example of the real LDAP config
var OPTS = {
server: {
url: "ldap://address:port",
bindDN: "admin",
bindCredentials: "adminPassword",
searchBase: "OU=Users,DC=example,DC=com",
searchFilter: "(&(objectClass=user)(sAMAccountName={{username}}))"
},
credentialsLookup: basicAuth
};
Meteor.startup(() => {
passport.use(new LdapStrategy(OPTS));
WebApp.connectHandlers.use(bodyParser.json());
WebApp.connectHandlers.use(bodyParser.urlencoded({ extended: false }));
WebApp.connectHandlers.use(passport.initialize());
WebApp.connectHandlers.use(
"/",
(req, res, next) => {
// This part before the else is to trigger a basic auth popup to enter username/password
let credentials = basicAuth(req);
if (!credentials) {
res.statusCode = 401;
res.setHeader("WWW-Authenticate", "Basic");
res.end("Access denied");
} else {
passport.authenticate(
"ldapauth",
{
session: false
},
function(err, user, info) {
if (err) {
return next(err);
}
if (user) {
var token = jwt.sign(
{ username: user.sAMAccountName },
"someSecretString"
);
console.log("token", token);
next();
}
}
)(req, res, next);
}
},
function(req, res) {
console.log("debug point#2");
res.send({ status: "ok" });
}
);
}
Using: passport-google-oauth2.
I want to use JWT with Google login - for that I need to disable session and somehow pass the user model back to client.
All the examples are using google callback that magically redirect to '/'.
How do I:
1. Disable session while using passport-google-oauth2.
2. res.send() user to client after google authentication.
Feel free to suggest alternatives if I'm not on the right direction.
Manage to overcome this with some insights:
1. disable session in express - just remove the middleware of the session
// app.use(session({secret: config.secret}))
2. when using Google authentication what actually happens is that there is a redirection to google login page and if login is successful it redirect you back with the url have you provided.
This actually mean that once google call your callback you cannot do res.send(token, user) - its simply does not work (anyone can elaborate why?). So you are force to do a redirect to the client by doing res.redirect("/").
But the whole purpose is to pass the token so you can also do res.redirect("/?token=" + token).
app.get( '/auth/google/callback',
passport.authenticate('google', {
//successRedirect: '/',
failureRedirect: '/'
, session: false
}),
function(req, res) {
var token = AuthService.encode(req.user);
res.redirect("/home?token=" + token);
});
But how the client will get the user entity?
So you can also pass the user in the same way but it didn't felt right for me (passing the whole user entity in the parameter list...).
So what I did is make the client use the token and retrieve the user.
function handleNewToken(token) {
if (!token)
return;
localStorageService.set('token', token);
// Fetch activeUser
$http.get("/api/authenticate/" + token)
.then(function (result) {
setActiveUser(result.data);
});
}
Which mean another http request - This make me think that maybe I didnt get right the token concept.
Feel free to enlighten me.
Initialize passport in index.js:
app.use(passport.initialize());
In your passport.js file:
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
'http://localhost:3000/auth/google/redirect',
},
async (accessToken, refreshToken, profile,
callback) => {
// Extract email from profile
const email = profile.emails![0].value;
if (!email) {
throw new BadRequestError('Login failed');
}
// Check if user already exist in database
const existingUser = await User.findOne({ email
});
if (existingUser) {
// Generate JWT
const jwt = jwt.sign(
{ id: existingUser.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update existing user
existingUser.token = jwt
await existingUser.save();
return callback(null, existingUser);
} else {
// Build a new User
const user = User.build({
email,
googleId: profile.id,
token?: undefined
});
// Generate JWT for new user
const jwt = jwt.sign(
{ id: user.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update new user
user.token = jwt;
await auth.save();
return callback(null, auth);
}
}));
Receive this JWT in route via req.user
app.get('/google/redirect', passport.authenticate('google',
{failureRedirect: '/api/relogin', session: false}), (req, res) => {
// Fetch JWT from req.user
const jwt = req.user.token;
req.session = {jwt}
// Successful authentication, redirect home
res.status(200).redirect('/home');
}
I'm using JWT ("jsonwebtoken": "^5.4.0") with express 4 and jade.
I'm able to create the right Token, but How can i Pass this token in each call?
Where I have to store this token ? in headers or in localStorage?
For now I'm using CURL with Postman, and Set token in header in
x-access-token
Have I Do create a middleware that retrieve a token from Database and use this in each call?
thanks
You do not need to save and check the token from the database. This token such a mechanism can be decoded with only your-server, and if it was done that the token is valid. The code that you want to do should look like.
var cookieParser = require('cookie-parser')
app.use(cookieParser())
app.get('/login', function(req, res, next) {
var user = {name:'test'}; //!! find the user and check user from db then
var token = jwt.sign(user, 'secret', {
expiresInMinutes: 1440
});
res.cookie('auth',token);
res.send('ok');
});
app.use(function(req, res, next) {
var token = req.cookies.auth;
// decode token
if (token) {
jwt.verify(token, 'secret', function(err, token_data) {
if (err) {
return res.status(403).send('Error');
} else {
req.user_data = token_data;
next();
}
});
} else {
return res.status(403).send('No token');
}
});
Here you can find very nice article : https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
I would recommend checking this out if you want local storage: https://www.npmjs.com/package/node-localstorage
But, with that said, you guys and girls wouldn't believe how long it took me to find res.cookie('auth' token) from the above answer. I scoured Google for hours, Passport docs, Express docs, GraphQL and authentication/authorization docs in an effort to find out how to get the token to the API in a stateless manner.
I already built JWT token security and secured my GraphQL resolvers with it, but then, I opted to use EJS along with graphql-request (approx same as Apollo Client), so I needed to find a way to pass the token to my middleware without using a server side session.
Storing a JWT token in cookies is fine especially if you take extra precautions such as signing the cookie, and I recall there are also options you can include that keep the cookie secure, so that other sites cannot see it if the "browser" allows access to cookies. If a cookie is signed with your server secret, the data inside the cookie simply cannot be altered and still be valid. The risk is always still someone leaking their token/cookie, and if that bothers you, do research into refresh tokens. However, API tokens are generally and should be kept tightly secret and safe. Your biggest annoyance will more likely be the requirement to maintain a blacklist of JWTs that expire a year from now if you set expiry to 1y.
I am just including my findings here because this question is actually a rare resource it seems...
Here is my Express middleware for authentication:
// AUTHENTICATION
app.use(async (req) => {
try {
const token = req.headers.authorization || req.cookies.auth
const { person } = await jwt.verify(token, SECRET)
req.person = person
return req.next()
} catch (e) {
return req.next()
}
})
You can see I am setting the token from the header with cookie as fallback. This supports my needs fine and allows me to use really any client with stateless security.
My logged in user is available as req.person in my views and GraphQL resolvers. If req.person is not set, the user is treated as not-logged-in.
I am using return req.next() which is important to note because calling next() without parameters is treated as "clean go-to next middleware and/or proceed to process request". If you include any string or object parameter, it will throw an error that can bubble down to your error handling middleware. You can try it yourself. Put return next('You are not authenticated.') in the catch block and you will see it halt the request before your route.
I use return next() because I handle authorization in the routes and in my resolvers. It allows more flexibility such as facilitating register and login mutations to be accessed by non-authenticated users.
Here is my GraphQL endpoint (I am using Apollo Server):
app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
const context = {
person: req.person
}
return {
schema,
context,
rootValue: null
}
}))
In my GraphQL resolvers, the third parameter of every query has context.person populated with req.person which comes from the above Authentication middleware.
That is really all a person needs to know.
Here is how I am using the NPM package called graphql-request:
https://www.npmjs.com/package/graphql-request
app.get('/allpeople', async (req, res) => {
try {
const client = new GraphQLClient(GRAPHQL_ENDPOINT, {
headers: { Authorization: req.headers.authorization || req.cookies.auth }
})
const query = `query allPeople($serialNumber: String!) {
allPeople(serialNumber: $serialNumber) {
id
created
status
email
}
}`
const variables = {
serialNumber: req.person
}
const response = await client.request(query, variables)
res.render('allpeople/list', { people: response.allPeople })
} catch (e) {
throw [`allPeople`, `${JSON.stringify(error, null, 2)}`]
}
})
I include this code because there are no "more advanced" example usages of graphql-request, and I like it so far. It is very concise and could easily be swapped out for Apollo Client if you venture into React.js. My examples here are also very relevant for anyone researching createNetworkInterface and new ApolloClient().
I have been trying to connect to dropbox server and use the api, but I'm failing at the first step itself. When i'm requesting for the request token, I'm getting Bad oauth_signature error in nodejs.
The code that i'm using to connect to api is as follows.(I'm using https://github.com/sintaxi/node-dbox/blob/master/README.md library/sdk for nodejs)
/*
* dropbox handlers controller.
*/
var dbox = require('dbox')
,querystring = require("querystring")
var client = dbox.createClient({
app_key : 'my-key', // required
app_secret : 'my-secret', // required
root : 'sandbox' // optional (defaults to sandbox)
})
exports.index = function(req, res){
client.request_token(function(status, reply){
console.log(status)
console.log(reply)
// {
// oauth_token : "h89r0sdfdsfwiko", // required
// oauth_token_secret : "8hielfflk7100mv", // required
// }
})
the result i'm getting in my console is as follows
c:\tmp\dropbox>node app.js
Express server listening on port 3000 in development mode
oauth_consumer_key=[my key]&oauth_signature=faawn09ehmfe25i%2526&oauth_ti
mestamp=1324643574&oauth_nonce=132464357437334176&oauth_signature_method=PLAINTE
XT&oauth_version=1.0
403
{ '{"error": "Bad oauth_signature for oauth_signature_method \'PLAINTEXT\'"}': u
ndefined }
Any help on this is greatly appreciated.
Thanks in advance
This is the author of node-dbox. This issue has been resolved as of version 0.2.2.
Sorry for the trouble.
I took the approach of using the passport module along with its companion passport-dropbox module to handle the routes required for the authentication handshake with Dropbox. Once you receive the token and token secret passed in the Dropbox callback, store them in session state or wherever. Then you can then pass them to node-dbox in subsequent Dropbox API calls. The author of passport has a nice example on GitHub here: https://github.com/jaredhanson/passport-dropbox/tree/master/examples/login
passport.use(new DropboxStrategy({
consumerKey: DROPBOX_APP_KEY,
consumerSecret: DROPBOX_APP_SECRET,
callbackURL: APP_URL + '/auth/dropbox/callback'
},
function(token, tokenSecret, profile, done) {
var user = {
provider: 'dropbox',
displayName: profile.displayName,
email: profile.emails[0].value,
// I'm choosing to store the token and tokenSecret on the user.
// The keys must be as shown below to be compatible with node-dbox
dboxToken: {'oauth_token': token, 'oauth_token_secret': tokenSecret}
};
return done(null, user);
}));
app.get('/files', function(req, res) {
var dboxClient = dboxApp.client(req.user.dboxToken);
dboxClient.metadata('/', {}, function(status, reply) {
res.render('files', {
pathMetaData: reply,
user: req.user
});
});
});
To fix that issue you just need to apply what is mentioned here :
https://github.com/sintaxi/node-dbox/issues/3
On line 28 of Oauth.js signature is being encoded twice.
var getSignature = function(tokenSecret){
return encode(consumerSecret) + "&" + encode(tokenSecret)
}
var signature = encode(getSignature(secret))
Changing it to the following solves the problem of not being able to receive an oauth token.
var signature = getSignature(secret)
Thx