We have implemented a SAML SSO ,we have used passport-saml for the same. The login works perfectly using "http-post" as authnRequestBinding.But I am unable to find any such method for logout . it appears logout defaults to http redirection and this does not work correctly.
First of all, you need to define the logoutUrl and logoutCallback in the config for the passport-saml. The logoutUrl is the url where your server will send the logoutRequest. So it is an URL got from the identity provider you are using. The logoutCallback is as the name says, the callbackUrl which will be called by the browser (through redirect) after logout is done. Here's how you add those configurations:
module.exports = {
development: {
app: {
name: 'Passport SAML strategy example',
port: process.env.PORT || 8443
},
passport: {
strategy: 'saml',
saml: {
logoutUrl: 'idp-logout-url-here',
logoutCallback: 'your-logout-callback-url-here'
}
}
}
};
Then you need to have the SamlStrategy somewhere in your code, where you will use the config defined above. Of course the config will have other variables as well, I just put the logout related variables there for now.
Finally, you need to have your own logout route defined in your node application, which will initiate the logout process when called:
app.get('/logout', function(req, res) {
if (req.user == null) {
return res.redirect('/');
}
return SamlStrategy.logout(req, function(err, uri) {
return res.redirect(uri);
});
});
As you can see from above, it will call the logout function defined in the SamlStrategy. So there is a logout function defined in the passport-saml strategy. As in the above, you need to give it a callback function, which will then redirect the response to the uri. That uri will be the logoutCallback url you defined earlier.
If you're wondering what is the SamlStrategy there, it is actually the strategy of the passport-saml. I can show you how to get it working. In a separate file, called 'saml-strategy.js' for example, put this:
const SamlStrategy = require('passport-saml').Strategy;
var config = require('./config.js')['development'];
module.exports = new SamlStrategy(
{
otherImportantvariable1: config.passport.saml.OtherImportantvariable1,
logoutUrl: config.passport.saml.logoutUrl,
logoutCallback: config.passport.saml.logoutCallback
}
function (profile, done) {
user = Object.assign({}, profile);
return done(null, user);
}
);
Insert all your important config variables same way as the logout related variables are defined above. Include the config created in the first step.
Then you can just require the SamlStrategy to the same file where you have your routes:
const SamlStrategy = require('../config/saml-strategy');
Please ask if anything is unclear!
Logout fix for ADFS is as follows,
1) Session index attribute should be added as part of passport-saml logout request. you can get that from passport profile object.
function (profile, done) {
console.log('Profile: %j', profile);
return done(null,
{
id: profile.nameID,
sessionIndex: profile.sessionIndex
});
If there is no sessionIndex returned from ADFS. Then, NameID rule in relying part trusts should be as follows,
Add NameID as "Claim rule name", choose "Active Directory" as Attribute store, choose "SAM-Account-Name" as LDAP Attribute and "Name ID" as "Outgoing claim type", finish the wizard and confirm the claim rules window. (Reference: spring saml ADFS)
2) Debug using the ADFS logs (Event viewer) and check whether your error is similar to the one below,
The SAML Single Logout request does not correspond to the logged-in
session participant. Requestor: app.yyy.com
Request name identifier:
Format: urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified,
NameQualifier: SPNameQualifier: , SPProvidedId: Logged-in session
participants: Count: 1, [Issuer: app.yyy.com, NameID: (Format: ,
NameQualifier: SPNameQualifier: , SPProvidedId: )]
This request failed.
If yes, It means that nameIDFormat should be left empty for logging out the session. Previously I was using nameIDFormat that was specified for authentication request and it did not work.
HTTP-POST binding does not make any difference without this config.
Hope this helps !
Related
I have a React/Redux/React Router front end, Node/Express back end. I’m using Passport (various strategies including Facebook, Google and Github) for authentication.
What I want to happen:
Unauthenticated user attempts to access protected client route
(something like /posts/:postid, and is redirected to /login.
(React Router is handling this part)
User clicks the ‘Log in with Facebook’ button (or other Social auth service)
After authentication, user is automatically redirected back to the route they were attempting to access in step 1.
What is happening instead:
The only way I’ve found to successfully handle Passport social authentication with a React front end is to wrap the ‘Log in with Facebook’ button in an <a> tag:
Facebook Login
If I try to do it as an API call instead of a link I always get an error message (this issue is explained in a lot more detail here: Authentication with Passport + Facebook + Express + create-react-app + React-Router + proxy)
So the user clicks the link, which hits the Express API, successfully authenticates with Passport, and then Passport redirects to the callback route (http://localhost:8080/auth/facebook/callback).
In the callback function I need to (1) return the user object and token to the client, and (2) redirect to a client route — either the protected route they were trying to access before they got redirected to /login, or some default route like / or /dashboard.
But since there isn’t a way to do both of these things in Express (I can’t res.send AND res.redirect, I have to choose one), I’ve been handling it in what feels like kind of a clunky way:
res.redirect(`${CLIENT_URL}/user/${userId}`)
This loads the /user route on the client, and then I’m pulling the userId out of the route params, saving it to Redux, then making ANOTHER call to the server to return the token to save token to localStorage.
This is all working, although it feels clunky, but I can’t figure out how to redirect to the protected route the user was trying to access before being prompted to log in.
I first tried saving the attempted route to Redux when the user tries to access it, thinking I could use that to redirect once they land on the profile page after authentication. But since the Passport auth flow takes the user off-site for 3d-party authentication and then reloads the SPA on res.redirect, the store is destroyed and the redirect path is lost.
What I ended up settling on is saving the attempted route to localStorage, checking to see if there is a redirectUrl key in localStorage when the /user component mounts on the front end, redirecting with this.props.history.push(redirectUrl) and then clearing the redirectUrl key from localStorage. This seems like a really dirty workaround and there has got to be a better way to do this. Has anybody else figuree out how to make this work?
In case anybody else is struggling with this, this is what I ended up going with:
1. When user tries to access protected route, redirect to /login with React-Router.
First define a <PrivateRoute> component:
// App.jsx
const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => {
return (
<Route
{...rest}
render={props =>
loggedIn === true ? (
<Component {...rest} {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
/>
);
};
Then pass the loggedIn property to the route:
// App.jsx
<PrivateRoute
loggedIn={this.props.appState.loggedIn}
path="/poll/:id"
component={ViewPoll}
/>
2. In /login component, save previous route to localStorage so I can later redirect back there after authentication:
// Login.jsx
componentDidMount() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const pathname = from.pathname;
window.localStorage.setItem("redirectUrl", pathname);
}
3. In SocialAuth callback, redirect to profile page on client, adding userId and token as route params
// auth.ctrl.js
exports.socialAuthCallback = (req, res) => {
if (req.user.err) {
res.status(401).json({
success: false,
message: `social auth failed: ${req.user.err}`,
error: req.user.err
})
} else {
if (req.user) {
const user = req.user._doc;
const userInfo = helpers.setUserInfo(user);
const token = helpers.generateToken(userInfo);
return res.redirect(`${CLIENT_URL}/user/${userObj._doc._id}/${token}`);
} else {
return res.redirect('/login');
}
}
};
4. In the Profile component on the client, pull the userId and token
out of the route params, immediately remove them using
window.location.replaceState, and save them to localStorage. Then check for a redirectUrl in localStorage. If it exists, redirect and then clear the value
// Profile.jsx
componentWillMount() {
let userId, token, authCallback;
if (this.props.match.params.id) {
userId = this.props.match.params.id;
token = this.props.match.params.token;
authCallback = true;
// if logged in for first time through social auth,
// need to save userId & token to local storage
window.localStorage.setItem("userId", JSON.stringify(userId));
window.localStorage.setItem("authToken", JSON.stringify(token));
this.props.actions.setLoggedIn();
this.props.actions.setSpinner("hide");
// remove id & token from route params after saving to local storage
window.history.replaceState(null, null, `${window.location.origin}/user`);
} else {
console.log("user id not in route params");
// if userId is not in route params
// look in redux store or local storage
userId =
this.props.profile.user._id ||
JSON.parse(window.localStorage.getItem("userId"));
if (window.localStorage.getItem("authToken")) {
token = window.localStorage.getItem("authToken");
} else {
token = this.props.appState.authToken;
}
}
// retrieve user profile & save to app state
this.props.api.getProfile(token, userId).then(result => {
if (result.type === "GET_PROFILE_SUCCESS") {
this.props.actions.setLoggedIn();
if (authCallback) {
// if landing on profile page after social auth callback,
// check for redirect url in local storage
const redirect = window.localStorage.getItem("redirectUrl");
if (redirect) {
// redirect to originally requested page and then clear value
// from local storage
this.props.history.push(redirect);
window.localStorage.setItem("redirectUrl", null);
}
}
}
});
}
This blog post was helpful in figuring things out. The #4 (recommended) solution in the linked post is much simpler and would probably work fine in production, but I couldn't get it to work in development where the server and client have different base URLs, because a value set to localStorage by a page rendered at the server URL will not exist in local Storage for the client URL
Depending on your application architecture, I can give you a couple of ideas, but they are all based on the fundamental :
Once you have backend handling authentication, you need to store the state of the user in your backend as well ( via session cookie / JWT )
You can create a cookie-session store for your express app which cookie, you need to configure properly to use both the domains ( the backend domain and the front-end domain ) or use JWT for this.
Let's go with more details
Use React to check the authentication state
You can implement an end-point in express called /api/credentials/check which will return 403 if the user is not authenticated and 200 if is.
In your react app you will have to call this end-point and check if the user is authenticated or not. In case of not authenticated you can redirect to /login in your React front-end.
I use something similar :
class AuthRoute extends React.Component {
render() {
const isAuthenticated = this.props.user;
const props = assign( {}, this.props );
if ( isAuthenticated ) {
return <Route {...props} />;
} else {
return <Redirect to="/login"/>;
}
}
}
And then in your router
<AuthRoute exact path="/users" component={Users} />
<Route exact path="/login" component={Login} />
In my root component I add
componentDidMount() {
store.dispatch( CredentialsActions.check() );
}
Where CredentialsActions.check is just a call that populates props.user in case we return 200 from /credentials/check.
Use express to render your React app and dehydrate the user state inside the react app
This one is a bit tricky. And it has the presumption that your react app is served from your express app and not as static .html file.
In this case you can add a special <script>const state = { authenticated: true }</script> which will be served by express if the user was authenticated.
By doing this you can do:
const isAuthenticated = window.authenticated;
This is not the best practice, but it's the idea of hydrate and rehydration of your state.
References :
Hydration / rehydration in Redux
Hydrate / rehydrate idea
Example of React / Passport authentication
Example of cookie / Passport authentication
I’m quite new to backend development…
With using my API I would like to be able to display a list of users and also indicate if they are currently logged in. I got the basic authentification working using passport and json web token
I’m not looking to get the current logged in user.
I want to be able to retrieve a list of users and see if they are logged in or not.
Like this:
var users = Users.find({});
// console.log(users) output:
{
name: 'foo'
password: ...
isLoggedIn: false
},
{
name: 'bar'
password: ...
isLoggedIn: true
},
{
name: 'baz'
password: ...
isLoggedIn: false
}
isLoggedIn would be set to true if the user is currently logged in and to falseif not.
How can I do that? Thank you!
It sounds like what you would like to do is update your MongoDB database based on login/logout events. To do this you could use something like mongoose to work with your Node backend to easily access your database in MongoDB.
You can include mongoose after installing with npm install mongoose like so:
var mongoose = require('mongoose');
var User = mongoose.model('User');
Note that User corresponds to whatever schema you create for storing user information.
Assuming you have some sort of router object for handling requests, you could construct route handlers for /logout and /login and use your imported mongoose User model to retrieve and then modify a specific User object as such:
// whenever user goes to '/login' (you can have, say, your 'login' button make a request to this URL
router.get('/login', function(req,res) {
// your authentication here; passport stores the currently authenticated user in req.user
var username = req.user.name; // here we assume the username is stored as 'name' as you have in your code but change this based on your schema
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = true;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to some page here maybe
}
});
});
});
// whenever user goes to '/logout' (you can have a logout button make a request to this URL
router.get('/logout', function(req,res) {
// currently authenticated user is still in req.user
var username = req.user.name;
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = false;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to login/register page maybe
}
});
});
});
So to summarize what this code would do:
based on the url a user would go to, our route handler would fetch one correct, unique User object from our database based on the name (username)
it would do so by accessing the username property of req.user which corresponds to the currently authenticated user with Passport, which, again will be different for all users
update the field that we use to keep track of login status (isLoggedIn)
and then save the changes, after which we are done updating the state to reflect whether the user is logged in or not, so we can now redirect to some other page or display other content
Finally then, you could retrieve a list of all users similarly to your code like so:
User.find({}, function(err, users, data) {
// all users from your database are in `users`
console.log(users);
});
Edit for expired sessions:
So, to track expired sessions, since you're using Passport, would in theory require functionality to signal with some sort of event / callback / message, etc. the moment the session is deemed invalid. Now that is tough to monitor and from my experience with Passport, stuff like that isn't implemented in all authentication strategies and might vary based on the strategy to be used by developers (think for instance if a browser window is closed, based on Passports authentication strategy, or just browser, it might destroy the cookie for the session right away and our server has no way of knowing about it). I do recommend checking out all the authentication strategies Passport offers in case there are some better ones here.
Now, if you would like to add functionality to track the users passive login/logout status with sessions yourself, you could use something related to cookies. Again, not necessarily one to use, but here's a couple handy Express modules: cookie-parser and cookie-session.
Then, you could set and read cookies like this, using cookie-parser:
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
You would put this code somewhere right after the user is authenticated:
// cookies are stored here
console.log(req.cookies);
// configure your cookie
var options = {
expires: 1000 * 60 * 60, // expires after one hour
httpOnly: true
}
// Set cookie
res.cookie('session', ('user-' + req.user.name), options);
And then, on the client side check if that cookie is valid continuously on some time interval, and if it expired Date.now() > cookie.expires then make a GET request to /logout, and there log out the user (currently still authenticated) by updating MongoDB and all.
However, since this would require making a mechanism to basically simulate an expired session, I would recommend using something analogous to a timeout, which would be much easier to implement. Just a note, this is sort of analogous to mechanisms on some pages you might have encountered where you get a pop-up saying 'You will be logged out due to inactivity'. In your main.js or whatever client-side script define a function to keep going on a time-out, unless the user does some action.
var inactivity = function () {
var t;
// user doing something on your page, so keep resetting time counter when events happen
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
// this is a callback function that will get called once a time-out countdown is done
function timeOut() {
// make a request to '/logout' here and logout the current user (you still will have access to req.user from Passport)
// also can redirect from back-end route handler to the login page for instance
}
// this gets called whenever an event happens, resetting the counter of sorts
function resetTimer() {
t = 0;
t = setTimeout(timeOut, 1000 * 60 ) // set this to however long you should wait to log out your user time (in milliseconds)
}
};
So basically what this approach would let you do, is automatically invalidate sessions yourself, which means you would have much greater control over updating the state of your database and logging users out.
Hope this helps!
I am currently working on a authentication service for a node.js microservices application using typescript, WebStorm, passport and jwt. While trying to add the route to "/api/login", I am noticing that the intellisense does not seem to pick up the user object of req.user or the authorization object of req.header.authorization. For example, the following method is not working because it can not find the user object:
private generateToken(req: Request, res: Response, next: NextFunction){
req.token = jwt.sign({
id: req.user.id,
firstname: req.user.firstname,
lastname: req.user.lastname,
roles: req.user.roles
}, process.env.AUTH_KEY, {
expiresIn: "7d"
});
return next();
}
I am using the Request object from express:
import { NextFunction, Request, Response, Router } from "express";
Would I need to use a different Request object?
Also, if I need to force authentication to certain api routes but lock other routes down, how should this be done using passport-jwt? I know there is an express-unless package that I can use for express-jwt.
Not sure why this question downvoted, maybe because it should be two separate questions.
You can extend type declarations for Express.
Extending the express type definitions
Add a file library-ext.d.ts into your source directory with this.
declare module 'express' {
export interface Request {
user?: any;
}
}
For req.header.authorization try: req.headers['authorization']. Notice the 's'.
Relating to general authentication
It depends whether your registered users can also use the guest routes. If you never need identity on the guest routes then just register the passport authentication middleware only on the authenticated routes, or split the routes into separate routers. That's fairly simple to do, just search stack overflow or look in the docs for it.
The more complicated case is when you need both authenticated and non-authenticated users to access a route - think either a guest or authenticated customer adding something to a cart. Unfortunately passport-jwt rejects with a 401 when a token is not in the authorzation header, so the easiest way I found, rather than forking the project or rolling my own strategy, was to use middleware to add a known value to represent an otherwise anonymous request. Then just make sure that middleware is before the passport authentication in the affected routes. Here's a snippet to get you going:
CoreCtrl
class CoreCtrl {
simulateAnonymous(req, res, next) {
if (!req.headers.authorization) {
req.headers.authorization = 'Bearer guest-token';
}
return next();
}
}
Then somewhere in your Express Setup
setupRouters() {
// the public and admin routers are bound to the application
const coreCtrl = new CoreCtrl(this.serverOpts);
const anonymousCtrl = coreCtrl.simulateAnonymous.bind(coreCtrl);
this.routers.admin.use(anonymousCtrl);
this.routers.admin.use(passport.authenticate('UserBearer', { session: false }));
this.routers.public.use(anonymousCtrl);
this.routers.public.use(passport.authenticate('CustomerBearer', { session: false }));
}
Note that I had separate routers for public and admin set up here, that's not necessary but just to illustrate how to do it.
Then in the bearer strategy, you would have some code similar to this.
/**
* Run the strategy
*
* #param token {String} The JWT Token
* #param done {Callback} Callback function
*/
exec(token:string, done):Promise<any> {
// this is the workaround to support not passing a token for guest users.
if (token === 'guest-token') {
return done(null, {
userId: 'guest',
roles: ['guest']
});
}
// otherwise decode the token and find the user.
}
Finally, in some later Middleware you can check if the 'guest' role has access to the protected resource. I'd recommend acl module to manage role-based ACL list.
Property 'isAuthenticated' does not exist on type 'Request'.
Google lands here for that search, and the solution is to use Request/Response types in Express. My code was defaulting to the Fetch API's Request/Response definitions.
import { Express, NextFunction, Request, Response } from 'express'
guys
on my exisiting api i already have user authhication using Bearer security. Using http header api_key and later tokens.
My problem seems to be i have diffefrent end point that are only need to be consumed based on roles.
For example to post a new user :
POST user should only be authenticated to user with admin role.
I have looked at the swagger spec here but nothing i could find on thier docuemation and google as well.
Please could give me some brain stroming idea ? below is my access verifaction code in nodejs and express.
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
app.use(middleware.swaggerSecurity({
Bearer: function(req,def,apiKey,next){
apiKey= apiKey.slice(7)
debug("token check",def,apiKey)
var ok=checkToken(apiKey)
if(ok) {
req.user=ok
debug('Token is ok')
return next()
}
debug("Invalid token",apiKey)
var err=Error("Invalid token")
err.statusCode=403
next(err)
}
}));
As of this writing, the solution is still homebrewing. Swagger does not, save through oAuth scopes or using a "hacky" api-key security definition (https://stackoverflow.com/a/40222161/3736937), have a built in RBAC mechanism.
Fortunately, we can create some pretty basic middleware to handle the problem because swagger does allow us to add x-swagger-* members to the swagger definition.
So here's what I did:
Add x-swagger-roles to each endpoint that requires RBAC (Role-based Access Control)
paths:
"/":
x-swagger-router-controller: getStatus
get:
operationId: getStatus
x-swagger-roles:
- admin
tags:
- "users"
summary: "Returns message: 'working'"
description: "default endpoint for testing"
responses:
$ref: "#/definitions/AnyResponse"
Place middleware before swagger-node-runner is registered with the application. In our case we're using expressjs, so the connect middleware is used.
var findOne = function (haystack, arr) {
return arr.some(function (v) {
return haystack.indexOf(v) >= 0;
});
};
app.use(function(req, res, next) {
var operation = runner.getOperation(req);
if(operation && operation.definition) {
var definition = operation.definition;
var requiredRoles = definition['x-swagger-roles'];
// if the endpoint has no required roles then go to the next route
if(!requiredRoles) return next();
// get the users roles
var userRoles = req.session.roles; // this may differ for you
// if any roles match then go to the next route
if(findOne(userRoles, requiredRoles)) return next();
// if no roles match then assert that this endpoint is forbidden
else return res.sendStatus(403);
}
next();
})
// it's important to register the middleware after the role check
runner.expressMiddleware().register(app);
Notes:
This code has not been tested in production, and should be reviewed by a security professional.
x-swagger-roles will not appear in your swagger-ui without altering it, which is beyond the scope of this answer.
I am trying to send state parameters to the Oauth and then catch them in the callback, but I cannot make it work. So does passportjs support such a functionality?
My idea is to send an id as state parameter to the Oauth and then on the callback depending on the id from the state parameters sent back to my app I want to do a proper redirect.
In the Twitter strategy I have enabled
passReqToCallback: true,
state: true
My request should look like this:
app.get('/auth/twitter/:gameId/:url',
function(req, res){
try {
var json = JSON.stringify({gameId: req.params.gameId, url: req.params.url});
var encodedValues = base64url(json);
console.log('encodedValues:'+encodedValues)
passport.authenticate('twitter', {state:encodedValues})
}catch (e){
console.log(e);
}
}
);
then on the callback
app.get('/auth/twitter/callback', passport.authenticate('twitter', function(req, res, next) {
try{
//get the state params from the res uri/body
//decode the state params
//redirect depending on the state.gameId
}catch(e){
console.log('twitter exception:'+e);
}}));
I already know that I can save the id in a session, but I would like to know if there is a session less way to do it by passing this information from url since it is not sensitive.
Thanks
With oAuth 2.0 you don't have to rely on any type of session for this. The way you pass in state on the /auth/twitter/:gameId/:url route, you would be able to access the state on your callback with req.query.state.
In case someone else runs into problems, when trying to pass application state through the oAuth progress, this is how I solved it in my Node-app:
Since I want to redirect the user to different locations, based on the location the user came from before entering the authentication screen, I built different routes for accessing the authentication screen, and passed different state along depending on which route was hit. Like this:
router.get('/google/io', passport.authenticate('google', {
scope: ['email', 'profile'],
state: 'io'
}));
router.get('/google/de', passport.authenticate('google', {
scope: ['email', 'profile'],
state: 'de'
}));
So if my users access the auth screen coming from my page using the .io domain, my state parameter will carry the value of 'io', and if a user accesses the auth screen coming from the .de version of my page, the state parameter will carry the value of 'de'. On successful authentication, I can then just extract the value from req.query.state, and redirect them back to mypage.de or mypage.io