Currently I am returning the users email to the session as a JWT. This is working just fine. Now I am trying to return the users username with the email as a JWT. If I am to console.log the username, it is appearing in the terminal. Yet, if I am logging the session.user in my getServerSideProps function I am only able to view the email. Any tips?
export default NextAuth({
session: { jwt: true },
providers: [
CredentialsProvider({
async authorize(credentials) {
const client = await connectToDatabase();
const userCollection = client.db().collection("users");
const user = await userCollection.findOne({ email: credentials.email });
console.log(user.userName);
if (!user) {
client.close();
throw new Error("No user found!");
}
const isValid = await comparePasswords(
credentials.password,
user.password
);
if (!isValid) {
client.close();
throw new Error("Invalid password");
}
client.close();
if (user) {
return {
email: user.email,
userName: user.userName,
};
} else {
return null;
}
},
}),
],
});
Related
I have a lambda that creates the tokens for an existing user in my user pool, but when I am to validate the access token returns an error 401 and when I try with ID token it returns a 200.
function asyncAuthenticateUser(cognitoUser, cognitoAuthenticationDetails) {
return new Promise(function(resolve, reject) {
cognitoUser.authenticateUser(cognitoAuthenticationDetails, {
onSuccess: resolve,
onFailure: reject
});
});
}
var authenticationData = {
Username: name,
Password: password,
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var poolData = {
UserPoolId: UserPoolId,
ClientId: ClientId
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: name,
Pool: userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
try {
let session = await asyncAuthenticateUser(cognitoUser, authenticationDetails);
cognitoJWT.session = session;
console.log(session.getIdToken());
cognitoJWT.jwtAccess = session.getAccessToken().getJwtToken();
cognitoJWT.jwtId = session.getIdToken().getJwtToken();
cognitoJWT.jwtRefresh = session.getRefreshToken().getToken();
cognitoJWT.jwtPayloads = {
jwtAccess: session.getAccessToken().decodePayload(),
jwtId: session.getIdToken().decodePayload(),
};
callback(null, cognitoJWT);
} catch (err){callback(err,null); }
My output of the Lambda is:
{
"StatusCode": 200,
"StatusMessage": {
"jwtAccess": "<<jwtAccess>>",
"jwtRefresh":"<<jwtRefresh>>",
"jwtId":<<jwtId>>
}
}
But when I am trying to validate on the API gateway I got this output.
Output API gateway jwtAccess
If I am Try the jwtID it shows the user info.
Output API gateway jwtId
Using the wrong token to validate.
API Gateway actually uses de "idToken" for validate, not the "accessToken". So when you want to test the token, you must use the firstone.
Last week I am trying to configure the IdentityServer4 to get an access token automatically updating.
I had an API:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
My MVC client configuration:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
});
And the IdentityServer's clients configuration:
return new List<Client>
{
new Client
{
ClientId = "mvc",
ClientName = "My mvc",
AllowedGrantTypes = GrantTypes.Hybrid,
RequireConsent = false,
AccessTokenLifetime = 10,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5102/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5102/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"api1"
},
AllowOfflineAccess = true
}
};
On the client side I use AJAX queries to call the API to get/post/put/delete data. I add the access token to the request and get the result.
private async getAuthenticationHeader(): Promise<any> {
return axios.get('/token').then((response: any) => {
return { headers: { Authorization: `Bearer ${response.data}` } };
});
}
async getAsync<T>(url: string): Promise<T> {
return this.httpClient
.get(url, await this.getAuthenticationHeader())
.then((response: any) => response.data as T)
.catch((err: Error) => {
console.error(err);
throw err;
});
}
The access token is provided by the MVC client method:
[HttpGet("token")]
public async Task<string> GetAccessTokenAsync()
{
return await HttpContext.GetTokenAsync("access_token");
}
It works fine. After access token expired I get 401 on the client side, so it would be great to have an opportunity to update access token automatically when it was expired.
According to a documentation I supposed, that It can be reached by setting AllowOfflineAccess to true and adding suitable scope "offline_access".
Maybe I don't understand the right flow of the access and refresh tokens usages. Can I do it automatically or it is impossible? I suppose, that we can use refresh tokens in out queries, but I don't understand how.
I've read a lot of SO answers and github issues but I am still confused. Could you help me to figure out?
After investigation and communicating in comments I've found the answer. Before every API call I get the expite time and according to the result update access_token or return existing:
[HttpGet("config/accesstoken")]
public async Task<string> GetOrUpdateAccessTokenAsync()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var expiredDate = DateTime.Parse(await HttpContext.GetTokenAsync("expires_at"), null, DateTimeStyles.RoundtripKind);
if (!((expiredDate - DateTime.Now).TotalMinutes < 1))
{
return accessToken;
}
lock (LockObject)
{
if (_expiredAt.HasValue && !((_expiredAt.Value - DateTime.Now).TotalMinutes < 1))
{
return accessToken;
}
var response = DiscoveryClient.GetAsync(_identitySettings.Authority).Result;
if (response.IsError)
{
throw new Exception(response.Error);
}
var tokenClient = new TokenClient(response.TokenEndpoint, _identitySettings.Id, _identitySettings.Secret);
var refreshToken = HttpContext.GetTokenAsync("refresh_token").Result;
var tokenResult = tokenClient.RequestRefreshTokenAsync(refreshToken).Result;
if (tokenResult.IsError)
{
throw new Exception();
}
accessToken = tokenResult.AccessToken;
var idToken = HttpContext.GetTokenAsync("id_token").Result;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = idToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = accessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResult.RefreshToken
}
};
var expiredAt = DateTime.UtcNow.AddSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiredAt.ToString("o", CultureInfo.InvariantCulture)
});
var info = HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme).Result;
info.Properties.StoreTokens(tokens);
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, info.Principal, info.Properties).Wait();
_expiredAt = expiredAt.ToLocalTime();
}
return accessToken;
}
}
I call this method to get the access_token and add int to the API call headers:
private async getAuthenticationHeader(): Promise<any> {
return axios.get('config/accesstoken').then((response: any) => {
return { headers: { Authorization: `Bearer ${response.data}` } };
});
}
async getAsync<T>(url: string): Promise<T> {
return this.axios
.get(url, await this.getAuthenticationHeader())
.then((response: any) => response.data as T)
.catch((err: Error) => {
console.error(err);
throw err;
});
}
Double check locking were implemented to prevent simultamious async API calls try to change access_token at the same time. Optionally you can cashe you access_token into static variable or cache, it is up to you.
If you have any advices or alternatives it would be insteresting to discuss. Hope it helps someone.
There's 2 ways of doing this:
Client side - Handle the authentication and obtaining of the token on the client side using a lib like oidc-client-js. This has a feature that allows automatic renewal of the token via a prompt=none call to the authorize endpoint behind the scenes.
Refresh token - store this in your existing cookie and then use it to request a new access token as needed. In this mode your client side code doing the AJAX calls would need to be aware of token errors and automatically request a new token from the server whereby GetAccessTokenAsync() could use the refresh token to get a new access token.
I am using passport-facebook to login in a MEAN stack webapp. After successful login, I want to generate a JSON Web Token (jwt) and redirect to a page in my SPA. (res.redirect('/#/posts/'+ doc.generateJWT()); -- please see the associated code below).
My question is:
How do I send the JWT to the redirect page without showing it in the URL?
Code:
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: FACEBOOK_CALLBACKURL
},
function(accessToken, refreshToken, profile, done) {
process.nextTick(function () {
User.findOne({'fbid':profile.id},function(err, docs) {
if (err){
//console.log('Error in SignUp: '+err);
return res.status(401).json(info);
}
else {
if (docs) {
//console.log('User already exists');
globalid = profile.id;
return done(null,docs);
} else {
// if there is no user with that fbid
// create the user
var newUser = new User();
// set the user's local credentials
newUser.fbid = profile.id;
globalid = profile.id;
newUser.firstname = profile.name.givenName;
newUser.lastname = profile.name.familyName;
newUser.gender = profile.gender;
if(profile.emails){
newUser.fbemail = profile.emails[0].value;
};
newUser.fblink = profile.profileUrl;
newUser.fbverified = profile.verified;
// save the user
newUser.save(function(err) {
if (err){
//console.log('Error in Saving user: '+err);
return res.status(401).json(info);
}
//console.log('User Registration succesful');
return done(null, newUser);
});
}
}
});
});
}));
var router = express.Router();
router.get('/auth/facebook',
passport.authenticate('facebook', { scope : 'email' }
));
router.get('/auth/facebook/callback',
passport.authenticate('facebook', { session: false, failureRedirect: '/'}),
function(req, res,done) {
var redirection = true;
User.findOne({ 'fbid': globalid }, function (err, doc){
//console.log("Generating token");
doc.token = doc.generateJWT();
doc.save(function(err) {
if (err){
//console.log('Error in Saving token for old user: '+err);
return res.status(401).json(info);
}
else
{
//console.log('User Login succesful');
redirection = doc.mobileverified;
//console.log(redirection);
//return done(null, doc);
if(doc.mobileverified === true){
console.log("Token:",doc.generateJWT());
res.redirect('/#/posts/'+ doc.generateJWT());
}
else{
console.log("Token:",doc.generateJWT());
//res.json({token: doc.generateJWT()});
res.redirect('/#/register/' + doc.generateJWT());
}
}
});
});
});
Many Thanks in advance!
If you don't wanna show your token on the url you have to send the response as json
var fbOptions = {
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: FACEBOOK_CALLBACKURL
};
passport.use(new FacebookStrategy(fbOptions, function(token, refreshToken, profile, done) {
var user = profile;
// NOTE: ‘my_token’ we will use later
user.my_token = 'generate your jwt token';
done(null, user);
}));
And then on your router return the token as json
app.get('/auth/facebook/callback', passport.authenticate('facebook', {session: false, failureRedirect : '/'}), function(req, res) {
// The token we have created on FacebookStrategy above
var token = req.user.my_token;
res.json({ token: token });
});
Is it possible to login via ajax with passport.js?
The thing is i'm creating a user via ajax and i want it to be logged in automatically (everything with json in a restful style) but the req.login() does some stuff that i don't know and that apparently sends its own status, headers and even it redirects to the home but and i need is to create my own json response.
The code where i create the user:
signup_facebook: function (req, res) {
var restponse = new Restponse();
var body = req.body;
var obj = {
display_name: body.first_name,
name: body.first_name,
surname: body.last_name,
photos: ['http://graph.facebook.com/'+ body.id+ '/picture?type=normal'],
gender: body.gender,
facebook: {
userID: body.id,
displayName: body.display_name
}
}
User.facebookSignUp(obj, function(user){
if(user !== false){
user = obj;
restponse.location = '/';
restponse.status = HTTPStatus.REST.C201_OK;
}else{
restponse.location = '/';
restponse.status = HTTPStatus.REST.C302_FOUND;
}
restponse.body = user;
req.login(user, {}, function(err) {
APIheart.respondJson(res, restponse);
});
})
Thanks for your time!
I found the answer in this post:
http://toon.io/on-passportjs-specific-use-cases/
// Manually establish the session...
req.login(user, function(err) {
if (err) return next(err);
return res.json({
message: 'user authenticated',
});
});
I have an issue in which I can create hashed passwords in node-bcrypt and passport.js, but am unable to use the hashed password.
Im using nodejs, express, mongodb, mongoose, passport js, bcrypt.
What Im Trying To Do
Be able to login as normal, but using the bcrypt salted paswword etc.
What I have Done
I know my routes, api, and db are working. Since my current set up logs users in and out if I use a normal string for password instead of bcrypt.
I also checked my db and a bcrypt/salted password appears in the password field.
I got the idea to use bcrypt from this article (so using this code):
http://devsmash.com/blog/password-authentication-with-mongoose-and-bcrypt
Here is my relevant code:
var express = require('express'),
routes = require('./routes'),
passport = require('passport'),
util = require('util'),
flash = require('connect-flash'),
LocalStrategy = require('passport-local').Strategy,
mongoose = require('mongoose');
mongoose.connect('mongodb://54.254.96.11/bcrypt')
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
bcrypt = require('bcrypt'),
SALT_WORK_FACTOR = 10;
var user = new Schema({
username: { type: String, required: true, index: { unique: true } },
password: { type: String, required: true },
email: String
});
var user = mongoose.model('user', user);
//Bcrypt Code
user.pre('save', function(next) {
var guest = this;
// only hash the password if it has been modified (or is new)
if (!guest.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
// hash the password using our new salt
bcrypt.hash(guest.password, salt, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
guest.password = hash;
next();
});
});
});
user.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
//
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
user.findById(id, function (err, user) {
done(err, user);
});
});
passport.use(new LocalStrategy(
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 and set a flash message. Otherwise, return the
// authenticated `user`.
user.findOne({ username: username}, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
if (user.password != password) { return done(null, false, { message: 'Invalid password' }); }
return done(null, user);
})
});
}
));
// Relevant Express Routes
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }),
function(req, res) {
res.redirect('/home');
});
app.post('/create', function(req, res, next){
var moot = new user({
"username": req.body.username,
"password" : req.body.password,
"email" : req.body.email});
moot.save(function (err) {
if (!err) {
res.redirect('/home');
}
else {
res.redirect('/');
}
});
});
I would do it this way:
create a new method for User model:
userSchema.statics.authenticate = function(username, password, callback)
{
this.findOne({username: username}, function(err, user)
{
if(err) return callback(err);
if(!user) return callback(null, false);
user.comparePassword(password, function(err, correct)
{
if(!correct) return callback(null, false);
callback(null, user);
});
});
}
then in the passport config:
passport.use(new LocalStrategy(
function(username, password, done)
{
User.authenticate(username, password, function(err, user)
{
if(err) return done(err);
if(!user) return done(null, false);
done(null, user);
}
}
));
This should work (I didn't test it)
PS: please use 'user' for one user
for the model, use 'User'