How to get currently login user with passport-jwt - express

I implemented passport-jwt to authenticate user on protected route and also i want to check maybe the user login before creating first admin, please help me on how to do it.
this is my passport-jwt code that i have implemented
exports.getToken = function (user) {
return jwt.sign(user, config.secretKey, { expiresIn: 3600 });
};
var opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = config.secretKey;
exports.jwtPassport = passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
console.log("JWT payload: ", jwt_payload);
User.findOne({ _id: jwt_payload._id, }, (err, user) => {
if (err) {
return done(err, false);
} else if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
})
);

If I understand your question correctly, you have authenticated a user and (s)he's logged in. Now, before creating an admin, you want to check if the currently logged in user hasn't expired or something else. Right ?
To do that:
You need to store JWT on the client-side so that whenever you call your API, you can attach the JWT in your request's authentication header. I say Authentication header because your ExtractJWT Strategy is fromAuthHeaderAsBearerToken.
With this you can attach your token to subsequent API calls headers. You also need to implement a middleware on your server-side so that the controller can verify whether the JWT in the Authorization header is valid or invalid.
Here is a good resource to understand the pipeline. Note that in this resource, they fromUrlQueryParameter as the extract strategy, but the concept is the same.

Related

Google OAuth2 with Passport and Express

I am struggling with getting Google OAuth to work with my Express/React application whilst using Passport.js. I am using JWTs, not sessions.
In my React webapp client, I have a "login with Google" button that calls my backend API /auth/google/ with the following route setup in Express:
router.get('auth/google', passport.authenticate('google', {session: false, scope: ['email','profile']}) );
My Passport.js google strategy is:
const googleStrategy = new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/api/v1/auth/google/callback",
passReqToCallback : true
},
async (request, accessToken, refreshToken, profile, done) => {
try {
console.log('profile', profile);// ** CORRECT USER PRINTED **
let existingUser = await User.findOne({ 'google.id': profile.id });
// if user exists return the user
if (existingUser) {
console.log('Found existing user...');
return done(null, existingUser);
}
// if user does not exist create a new user
const newUser = new User({
method: 'google',
googleId: profile.id,
profileImage: profile.photos[0].value,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
shortName: profile.displayName,
});
await newUser.save();
return done(null, newUser);
} catch (error) {
return done(error, false)
}
}
);
My Google developer dashboard is setup to call the following URL in my Express API backend upon successful authentication: /auth/google/callback
My Express route for this is defined as: router.get('auth/google/callback', passport.authenticate('google', {session: false}), authController.googleAuthCallback);
My Express googleAuthCallback function is defined as:
exports.googleAuthCallback = async (req, res) => {
console.log(req.user) // ** WRONG USER PRINTED HERE ** different from above user printed in google strategy
}
The strange this is when I console.log the profile variable in my googleStrategy, I get the right user profile information for the account from Google. This means the authentication vis a vis Google is fine. However, this same account is NOT being provided to my /auth/google/callback endpoint in the req.user object at that location. It is an entirely different account (it is the first value from my database of Users, which is authenticated using local authentication).
How do I get the user object back to my Express callback endpoint that I supplied to Google in the developer console as the authorized redirect URI?
As a general question, what happens after the strategy calls return done(null, existingUser);? I have no callback in the /auth/google route after the passport.authenticate() middleware is called so what happens next?
I am using "passport-google-oauth20": "^2.0.0"
My let existingUser = await User.findOne({ 'google.id': profile.id });
line was incorrect and was essentially returning no user. Mongoose does not complain and hence the strategy was just returning the first user from my database rather than the authenticated google user.

Why isn't NextAuth jwt automatically saved in cookies?

I have two simple authentication callback functions "jwt" and "session" which check if the user object exists and create the session if so.
callbacks: {
jwt: async ({ token, user }) => {
if(user) {
token.id = user.id
}
return token
},
session: ({ session, token }) => {
if(token) {
session.id = token.id
}
return session
},
}
My issue is, and I have been searching a lot to find information concerning this, why isn't this jwt automatically saved to cookies?
I find that my session is created and I am successfully "logged in", however if I look into my local storage there are no jwt cookies saved.
Do I have to manually save the jwt to my cookies in the jwt callback? Or is the jwt cookie not even required in the case of jwt session strategy? I need jwt cookies because from what I've read about middleware most solutions use cookies and decrypt the jwt to see if the user is logged in, instead of checking the getSession() hook.
You might need to to explain your problem in more detail since I can´t really tell what you already implemented and left out for simplicity sake. I hope this helps you anyway:
The steps to add the cookie look roughly like this:
Create / sign a jwt with a npm package like jose or jsonwebtoken
Set the header of your response and add your signed jwt to it; return it to the client
import { SignJWT, jwtVerify, JWTVerifyResult } from "jose";
async function setCookie(response, user: {id: number}) {
const token = await generateJwtToken(user);
response.setHeader("Set-Cookie", [
`user=${token};` +
"expires=" + new Date(new Date().getTime() + 1 * 86409000).toUTCString() + ";"
]);
response.status(200).json({message: "Successfully set cookie"})
}
async function generateJwtToken(user: { id: number }) {
return await new SignJWT({id: user.id})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("24h")
.sign(new TextEncoder().encode(process.env.YOUR_JWT_TOKEN_ENV_VAR));
}
Verify the jwt on further requests with the same package as in 1.
export async function verifyJwt(request) {
const token = request.cookies["yourCustomUser"];
const verified: JWTVerifyResult = await jwtVerify(
token,
new TextEncoder().encode(process.env.YOUR_JWT_TOKEN_ENV_VAR),
);
verified.payload.status = 200;
return verified.payload;
}
In addition to that you might wanna add sameSite=Strict, secure or path=/. For further information you should have a look at developers.mozilla
Also make sure to add error handling for expired Jwts etc.

auth0 checkSession({}) returns login_required when logged in through social provider, but not when logging in via username/password

I have an Angular app that uses Auth0 for authentication, and I'm trying to use checkSession({}, …) to persist a user's session if the token hasn't expired yet.
When I log in with my username/pw that I set up for the site, this works fine when I reload the browser/navigate directly to a resource. However, when I log in using a social provider (such as Google), the checkSession({}, …) call on a page reload returns an error and forces the user to log in again.
Some of the relevant code (mostly from the auth0 tutorial(s)):
export class AuthService {
// Create Auth0 web auth instance
private _auth0 = new auth0.WebAuth({
clientID: AUTH_CONFIG.CLIENT_ID,
domain: AUTH_CONFIG.CLIENT_DOMAIN,
responseType: 'token',
redirectUri: AUTH_CONFIG.REDIRECT,
audience: AUTH_CONFIG.AUDIENCE,
scope: AUTH_CONFIG.SCOPE
});
accessToken: string;
userProfile: any;
expiresAt: number;
// Create a stream of logged in status to communicate throughout app
loggedIn: boolean;
loggedIn$ = new BehaviorSubject<boolean>(this.loggedIn);
loggingIn: boolean;
isAdmin: boolean;
// Subscribe to token expiration stream
refreshSub: Subscription;
constructor(private router: Router) {
// If app auth token is not expired, request new token
if (JSON.parse(localStorage.getItem('expires_at')) > Date.now()) {
this.renewToken();
}
}
...
handleAuth() {
// When Auth0 hash parsed, get profile
this._auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken) {
window.location.hash = '';
this._getProfile(authResult);
} else if (err) {
this._clearRedirect();
this.router.navigate(['/']);
console.error(`Error authenticating: ${err.error}`);
}
this.router.navigate(['/']);
});
}
private _getProfile(authResult) {
this.loggingIn = true;
// Use access token to retrieve user's profile and set session
this._auth0.client.userInfo(authResult.accessToken, (err, profile) => {
if (profile) {
this._setSession(authResult, profile);
this._redirect();
} else if (err) {
console.warn(`Error retrieving profile: ${err.error}`);
}
});
}
private _setSession(authResult, profile?) {
this.expiresAt = (authResult.expiresIn * 1000) + Date.now();
// Store expiration in local storage to access in constructor
localStorage.setItem('expires_at', JSON.stringify(this.expiresAt));
this.accessToken = authResult.accessToken;
this.userProfile = profile;
if (profile) {
this.isAdmin = this._checkAdmin(profile);
}
...
}
...
get tokenValid(): boolean {
// Check if current time is past access token's expiration
return Date.now() < JSON.parse(localStorage.getItem('expires_at'));
}
renewToken() {
// Check for valid Auth0 session
this._auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken) {
this._getProfile(authResult);
} else {
this._clearExpiration();
}
});
}
}
(This is from a service that is called in many places within the app, including some route guards and within some components that rely on profile information. If more of the app code would be useful, I can provide it.)
Also note: AUTH_CONFIG.SCOPE = 'openid profile email'
So, the issue appears to not have been related to my app at all. When using Social Providers, Auth0 has an explicit note in one of their tutorials that really helped me out:
The issue with social providers is that they were incorrectly configured in my Auth0 dashboard, and needed to use provider-specific app keys.
Important Note: If you are using Auth0 social connections in your app,
please make sure that you have set the connections up to use your own
client app keys. If you're using Auth0 dev keys, token renewal will
always return login_required. Each social connection's details has a
link with explicit instructions on how to acquire your own key for the
particular IdP.
Comment was found on this page: https://auth0.com/blog/real-world-angular-series-part-7/

Authentication as an anonymous user

I would like to reproduce how plunker manages the anonymous accounts.
Plunker can recognise an anonymous user. For example, we can save a plunker as anonym and then freeze it. As a result,
only the same user (before clearing browser history) has the full access to this plunker (eg, save a modification, unfreeze).
if the same user opens it in another browser or other users open the same link, they can NOT save any modification; they have to fork it.
In my website, I use the local strategy of passport.js to manage named users. For example,
router.post('/login', function (req, res, next) {
if (!req.body.username || !req.body.password)
return res.status(400).json({ message: 'Please fill out all fields' });
passport.authenticate('local', function (err, user, info) {
if (err) return next(err);
if (user) res.json({ token: user.generateJWT() });
else return res.status(401).json(info);
})(req, res, next);
});
And I use a localStorage to store the token. For example,
auth.logIn = function (user) {
return $http.post('/login', user).success(function (token) {
$window.localStorage['account-token'] = token;
})
};
auth.logOut = function () {
$window.localStorage.removeItem('account-token');
};
Does anyone know if passport.js has any strategy or existing tools to manage the anonymous account like what plunker does? Otherwise, is there a conventional way to achieve this?
Passport allows anonymous auth. There is a passport anonymous strategy for the same:
app.get('/',
// Authenticate using HTTP Basic credentials, with session support disabled,
// and allow anonymous requests.
passport.authenticate(['basic', 'anonymous'], { session: false }),
function(req, res){
if (req.user) {
res.json({ username: req.user.username, email: req.user.email });
} else {
res.json({ anonymous: true });
}
});
This uses your basic strategy in place, you can substitute that with a local strategy if you're using local authentication. It falls back to an anonymous strategy in case nothing is supplied, as can be seen here:
passport.use(new BasicStrategy({
},
function(username, password, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// Find the user by username. If there is no user with the given
// username, or the password is not correct, set the user to `false` to
// indicate failure. Otherwise, return the authenticated `user`.
findByUsername(username, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (user.password != password) { return done(null, false); }
return done(null, user);
})
});
}
));
// Use the BasicStrategy within Passport.
// This is used as a fallback in requests that prefer authentication, but
// support unauthenticated clients.
passport.use(new AnonymousStrategy());
The full example may be found here:- https://github.com/jaredhanson/passport-anonymous/blob/master/examples/basic/app.js
Remember cookies with a longer expiration date is how anonymous user is identified. This goes the same way as any server side technology trying to authenticate user by username and password and then just sets a cookie for the http request.

Is hapi-auth-bearer-token for API authentication only?

I've been reading the docs on the bearer token module for hapi and wondering if it is suitable for only API authentication or for general purpose web application authentication as well.
A few things are not clear from the docs.
1) From where does the token originate? IOW, what mechanism creates the token?
2) By what means does the user login in the first place?
Thanks
I've been reading the docs on the bearer token module for hapi and wondering if it is suitable for only API authentication or for general purpose web application authentication as well.
Bearer authentication is used to authorize API requests.
1) From where does the token originate? IOW, what mechanism creates the token?
You need to generate and validate the tokens on your own.
To generate, store and validate tokens, you can use uuid and bcryptjs (tokens are sent to the user and hashed tokens are saved in a database for later validation).
The following examples are in TypeScript.
import uuid from 'uuid';
import bcrypt from 'bcryptjs';
export interface GeneratedToken {
token: string;
hashedToken: string;
}
const generateToken = async function(): Promise<GeneratedToken> {
return new Promise<GeneratedToken>((resolve, reject) => {
let token = uuid.v4().replace(/-/g, '');
bcrypt.genSalt(10, function(error, salt) {
if (error) {
reject(error);
} else {
bcrypt.hash(token, salt, function(error, hashedToken) {
if (error) {
reject(error);
} else {
resolve({
token: token,
hashedToken: hashedToken
});
}
});
}
});
});
};
const validateToken = function(token: string, hashedToken: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
bcrypt.compare(token, hashedToken, function(error, result) {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
2) By what means does the user login in the first place?
You need to use another authentication scheme which can be as simple as storing hashed passwords in your database and comparing them with the data sent to your API route. The same hashing scheme used for the tokens works just fine.