Sails.js: applying session and token authentication policies simultaniously - session

I am developing an application with sails.js. For the web application I use session authentication with passport. Now I also need to make my server accessibe from a mobile application, which requires token authentication. My question is the following: how can I define the policies so that sails accept SessionAuth or TokenAuth for certain routes?

The way sails handles policies, they are all applied one after another using AND logic. There is no way to combine them logically in other ways, like OR or more complicated combinations.
In your case, I would expect it would be fairly easy to write a third policy that handles the "SessionAuth or TokenAuth" all in one policy. Say you have existing SessionAuth.js and TokenAuth.js policies that look like this:
module.exports = function(req, res, next) {
if (req.isSessionAuthorized()) {
return next();
}
// handle rejected request
};
, and,
module.exports = function(req, res, next) {
if (req.isTokenAuthorized()) {
return next();
}
// handle rejected request
};
Then you just create a third policy called SessionOrTokenAuth.js:
module.exports = function(req, res, next) {
if (req.isSessionAuthorized() || req.isTokenAuthorized()) {
return next();
}
// handle rejected request
};
Then apply the newly created policy to the desired controller endpoints in /config/policies.js:
SomeController: {
'*': true,
'sessionOnlyEndpoint': ['SessionAuth'],
'tokenOnlyEndpoint': ['TokenAuth'],
'anyAuthEndpoint': ['SessionOrTokenAuth'],
}
The actual checks are likely a touch more complicated, but probably not by much. Hope this helps.

Related

New MicrosoftTeams.authentication.getAuthToken is not a MS Graph Bearer: token?

In the Single Sign-On for Teams
I have the call microsoftTeams.authentication.getAuthToken(authTokenRequest); working; that is, it successfully returns a token resolving to my Azure Active Directory (AAD) successfully. All good. Surprisingly easy. JWT returns with correct audience and scopes (as I have set in my tenant's AAD)
However what I get back when I decode the JWT this seems to just be an Authentication Token, not an Access Token.
Looking at the sample at Task Meow/teams.auth.service.js Does not seem to show how to swap the Auth for the Access Token.
I assume the code will look something like the method getToken() ... but since I have already spent 10+ working days on auth (old ADAL OH MY GOODNESS WAS THIS HORRIBLE) ...
Question:
I was wondering if there are any other good samples of MicrosoftTeams.js Authenticate / Auth Token / MSAL Access token out there?
Anyway, I did solve my problem by the following
Follow TaskMeow example through the abstractions ofauth.service.js > sso.auth.service.js > teams.auth.service.js
As I wanted additional AAD scopes (Files.ReadWrite.All to access the Sharepoint Online files in Teams and Groups.ReadWrite.All - to add Tabs) my getToken() method in teams.auth.service.js is something like the following:
getToken() {
if (!this.getTokenPromise) {
this.getTokenPromise = new Promise((resolve, reject) => {
this.ensureLoginHint().then(() => {
this.authContext.acquireToken(
'https://graph.microsoft.com',
(reason, token, error) => {
if (!error) {
resolve(token);
} else {
reject({ error, reason });
}
}
);
});
});
}
return this.getTokenPromise;
}
Editorial Comment:
Authentication in Microsoft Teams is too difficult
There seems to be many "approaches" in the documentation
The present "SSO" flow still has flaws, and is in "Developer Preview"
If you are an SPA developer it is just too difficult. I am (obviously) not an expert on Authentication -- so current "recipes" are imperative.
This is especially the case if you want more than the default "scopes" as described in Single Sign-on ... and most of the "good stuff" in Microsoft Graph is outside of these default scopes.
Also, this snippet may help.
If you follow the recommended Taskmeow in your Microsoft Teams app, you will get a quick appearance of the Redirect URI (aka /tab/silent-start)
To solve this, adal.js caches the user and access token.
So you can add a check in login()
login() {
if (!this.loginPromise) {
this.loginPromise = new Promise((resolve, reject) => {
this.ensureLoginHint().then(() => {
// Start the login flow
let cachedUser = this.authContext.getCachedUser();
let currentIdToken = this.authContext.getCachedToken(this.applicationConfig.clientId);
if (cachedUser && currentIdToken) {
resolve(this.getUser());
} else {
microsoftTeams.authentication.authenticate({
url: `${window.location.origin}/silent-start.html`,
width: 600,
height: 535,
successCallback: result => {
resolve(this.getUser());
},
failureCallback: reason => {
reject(reason);
}
});
}
});
});
}
return this.loginPromise;
}

How to use passport-local with graphql

I'm trying to implement GraphQL in my project and I would like to use passport.authenticate('local') in my login Mutation
Code adaptation of what I want:
const typeDefs = gql`
type Mutation {
login(userInfo: UserInfo!): User
}
`
const resolvers = {
Mutation: {
login: (parent, args) => {
passport.authenticate('local')
return req.user
}
}
Questions:
Was passport designed mostly for REST/Express?
Can I manipulate passport.authenticate method (pass username and password to it)?
Is this even a common practice or I should stick to some JWT library?
Passport.js is a "Express-compatible authentication middleware". authenticate returns an Express middleware function -- it's meant to prevent unauthorized access to particular Express routes. It's not really suitable for use inside a resolver. If you pass your req object to your resolver through the context, you can call req.login to manually login a user, but you have to verify the credentials and create the user object yourself before passing it to the function. Similarly, you can call req.logout to manually log out a user. See here for the docs.
If you want to use Passport.js, the best thing to do is to create an Express app with an authorization route and a callback route for each identify provider you're using (see this for an example). Then integrate the Express app with your GraphQL service using apollo-server-express. Your client app will use the authorization route to initialize the authentication flow and the callback endpoint will redirect back to your client app. You can then add req.user to your context and check for it inside resolvers, directives, GraphQL middleware, etc.
However, if you are only using local strategy, you might consider dropping Passport altogether and just handling things yourself.
It took me a while to wrap my head around the combination of GraphQL and Passport. Especially when you want to use the local strategy together with a login mutation makes life complicated. That's why I created a small npm package called graphql-passport.
This is how the setup of the server looks like.
import express from 'express';
import session from 'express-session';
import { ApolloServer } from 'apollo-server-express';
import passport from 'passport';
import { GraphQLLocalStrategy, buildContext } from 'graphql-passport';
passport.use(
new GraphQLLocalStrategy((email, password, done) => {
// Adjust this callback to your needs
const users = User.getUsers();
const matchingUser = users.find(user => email === user.email && password === user.password);
const error = matchingUser ? null : new Error('no matching user');
done(error, matchingUser);
}),
);
const app = express();
app.use(session(options)); // optional
app.use(passport.initialize());
app.use(passport.session()); // if session is used
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => buildContext({ req, res, User }),
});
server.applyMiddleware({ app, cors: false });
app.listen({ port: PORT }, () => {
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`);
});
Now you will have access to passport specific functions and user via the GraphQL context. This is how you can write your resolvers:
const resolvers = {
Query: {
currentUser: (parent, args, context) => context.getUser(),
},
Mutation: {
login: async (parent, { email, password }, context) => {
// instead of email you can pass username as well
const { user } = await context.authenticate('graphql-local', { email, password });
// only required if express-session is used
context.login(user);
return { user }
},
},
};
The combination of GraphQL and Passport.js makes sense. Especially if you want to add more authentication providers like Facebook, Google and so on. You can find more detailed information in this blog post if needed.
You should definitely use passport unless your goal is to learn about authentication in depth.
I found the most straightforward way to integrate passport with GraphQL is to:
use a JWT strategy
keep REST endpoints to authenticate and retrieve tokens
send the token to the GraphQL endpoint and validate it on the backend
Why?
If you're using a client-side app, token-based auth is the best practice anyways.
Implementing REST JWT with passport is straightforward. You could try to build this in GraphQL as described by #jkettmann but it's way more complicated and less supported. I don't see the overwhelming benefit to do so.
Implementing JWT in GraphQL is straightforward. See e.g. for express or NestJS
To your questions:
Was passport designed mostly for REST/Express?
Not in principle, but you will find most resources about REST and express.
Is this even a common practice or I should stick to some JWT library?
Common practice is to stick to JWT.
More details here: OAuth2 in NestJS for Social Login (Google, Facebook, Twitter, etc)
Example project bhere: https://github.com/thisismydesign/nestjs-starter

Node Express as proxy for a server that requires basic authentication

So I'm using express and express-http-proxy as a proxy to an API web server that requires basic authentication. Then in my app, I'll be issuing Ajax calls to these APIs. After some effort I got this working but I'm sure there's a better way out there, hence this post.
Initially I set up the express proxy as follows:
var express = require('express'),
app = express(),
proxy = require('express-http-proxy');
app.use('/apis', proxy("https://myserver", {
forwardPath: function(req, res) {
return "/apis" + require('url').parse(req.url).path;
}
}));
When calling a URL directly in the browser (not via Ajax), eg. https://myserver/apis/myapi.ashx, I would see the authentication dialog asking me for my credentials, and I could authenticate and see the result.
However, when accessing the same URL via an Ajax call in my app, I was not getting a popup. Why this difference of behavior?
So I decided I needed to add my own basic authentication middleware using request and basic-auth as follows:
var express = require('express'),
app = express(),
proxy = require('express-http-proxy'),
request = require('request'),
basicAuth = require('basic-auth');
var myAuth = function (req, res, next) {
function unauthorized(res) {
res.set('WWW-Authenticate', 'Basic realm=Rimes');
return res.sendStatus(401);
};
var user = basicAuth(req);
if (!user || !user.name || !user.pass) {
return unauthorized(res);
};
var connectUrl = 'https://'+user.name+':'+user.pass+'#myserver/apis/connect.ashx';
request.get(connectUrl, function(error, response, body) {
if (!error && response.statusCode == 200) {
return next();
} else {
return unauthorized(res);
}
});
};
app.use('/apis', proxy("https://myserver", {
forwardPath: function(req, res) {
return "/apis" + require('url').parse(req.url).path;
}
}));
This worked fine, showing me the authentication popup during the Ajax call.
The obvious disadvantage here is:
Credential verification for every API request, although there may be a way to cache valid credentials. But in its defence, this is only on the development environment.
So is there a better way of doing this? Would a different middleware package do a better job?

How to use Passport-Facebook login without redirection?

I'm building a phonegap application which will have nodejs at the server side. I wanted to implement login using passport-facebook strategy but their callbacks specify two routes, /successcallback and /failurecallback. Having a single page application, this makes it very confusing to have users redirected to so and so page.
I don't want to serve static files (index.html, login.html) from the server but rather have them on the client and ask the client to make ajax calls. So far, I'm able to make /auth/facebook call as an AJAX request but I can't receive any response on the same request because the login strategy requires the user to be redirected. I'd rather want to send a user_id or name back to the user on successful login or show him the login form (which is also on the www directory in phonegap) on failure. But the redirection and CORS errors are preventing me from doing this. Is there any way I can implement this? I've looked for this since a few weeks now, but no success. I'd really appreciate your help!
PS: I'd rather avoid having to send all html and static content from the node server.
EDIT: Adding login code for better understanding:
app.get('/userpage', utility.isLoggedIn, function(req, res)
{
res.send('User:'+req.user);
});
app.get('/', utility.isLoggedIn, function(req, res)
{
res.redirect('/userpage');
});
app.get('/auth/facebook', passport.authenticate('facebook'));
app.get('/auth/facebook/callback',passport.authenticate('facebook',
{
successRedirect : '/',
failureRedirect : '/login'
}));
app.get('/logout', function(req, res)
{
req.logout();
res.redirect('/login');
});
utility.isLoggedIn:
function isLoggedIn(req, res, next)
{
if (req.isAuthenticated())
return next();
res.redirect('/login');
}
You can't do that with facebook oAuth, but Facebook provides another login solution where you can code your client app to request a token, that you can later validate on the server with passport-facebook-token.
This way you can use the advantages of passport for persistent sessions, without that annoying redirection.
Instead of using the standard redirections offered by passport, you can define your own function which will be executed instead of the redirection. Here's an example of what that code would look like
passport.authenticate('login', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.json({ status:"failed", "error": "Invalid credentials" }); }
// req / res held in closure
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.json({ "status":"success"});
})
})(req, res, next);

Ghost in a subdirectory and over node-http-proxy

I'm trying to setup the Ghost blogging program (v0.4) in a subdirectory of another nodejs/express app. I was able to get it working following the steps outlined here: Node http-proxy and express
So, I setup the proxy to Ghost from my main app via Express like this (on my dev machine):'
var proxy = new httpProxy.createProxyServer();
server.get('/blog*', function (req, res, next) {
proxy.web(req, res, {
target: 'http://localhost:2368'
});
});
This works to access the blog content. However, when I go to /blog/ghost/signin and try to login, I get a 404. As far as I can tell, the signin page doesn't go anywhere outside of the blog/ directory, so why would it fail?
If I view the blog directly (on port 2368), I can login just fine.
You have defined a route for GET only, so you are only proxying GET requests, but login and signup uses a POST request. Usually a proxy rule in Apache or nginx will proxy all allowed methods for a given url, but since you defining the handler by method this doesn't happen.
The signup POST gets a 404 since it is handled by your first node application that doesn't know what to do.
In addition to POST you also need the DELETE method to be able click on the notification messages and to delete posts. I'm not sure if other methods are needed as well (OPTIONS), GET, POST and DELETE were the only ones I observed, you will see which method is failing if you take a look at the requests the page does e.g. with Firebug.
To fix this, add the same handler you have added with get for post,put and delete as well:
server.post('/blog*', function (req, res, next) {
proxy.web(req, res, {
target: 'http://localhost:2368'
});
});
server.delete('/blog*', function (req, res, next) {
proxy.web(req, res, {
target: 'http://localhost:2368'
});
});
server.put('/blog*', function (req, res, next) {
proxy.web(req, res, {
target: 'http://localhost:2368'
});
});
This way, the admin interface works correct.

Resources