I'm currently using this package amazon-cognito-identity-js to login my user using cognito user pool
import { Config, CognitoIdentityCredentials, CognitoIdentityServiceProvider } from "aws-sdk";
import {
CognitoUser,
CognitoUserPool,
AuthenticationDetails,
CognitoRefreshToken,
} from "amazon-cognito-identity-js";
const region = this.options.REGION
const userPoolId = this.options.USER_POOL_ID // user pool id of User Pool
const clientId = this.options.CLIENT_ID // client id of app client
const identityPoolId = this.options.IDENTITY_POOL_ID // identify pool id of federated identity pool
let authenticationData = { Username: params.username, Password: params.password }
let authenticationDetails = new AuthenticationDetails(authenticationData)
let poolData = {
UserPoolId: userPoolId,
ClientId: clientId
}
let userPool = new CognitoUserPool(poolData)
let userData = {
Username: params.username,
Pool: userPool
}
let cognitoUser = new CognitoUser(userData)
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
var accessToken = result.getAccessToken().getJwtToken()
var logins = {}
logins['cognito-idp.' + region + '.amazonaws.com/' + userPoolId] = accessToken
const credentials = new CognitoIdentityCredentials({
region: region,
IdentityPoolId: identityPoolId,
Logins: logins
})
// **** IM GETTING AN ERROR HERE IN CognitoIdentityServiceProvider ****
var cognitoIdentityProvider = new CognitoIdentityServiceProvider({
apiVersion: '2016-04-18',
region: region,
credentials: credentials
});
var params = {
GroupName: 'STB',
UserPoolId: userPoolId,
};
cognitoIdentityProvider.listUsersInGroup(params, function (err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
},
onFailure: function(err) {
console.log(err)
}
})
I'm able to login successfully but I'm getting an error when calling listUsersInGroup function
CredentialsError: Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1
I also checked that the credentials returns
console.log(credentials)
> CognitoIdentityCredentials {expired: true, expireTime: null, refreshCallbacks: Array(0), accessKeyId: undefined, sessionToken: undefined, …}
What am I missing here? I checked that it's possible to pass credentials instead of AccessKey and Secret according to this documentation
Related
I'm trying to implement JWT authentication, and I can't figure out why in Postman protected-route is available, while in my browser protected-route is Unauthorized.
I'm a beginner and its first time implementing jwt authentication in my project following this tutorial https://www.youtube.com/playlist?list=PLYQSCk-qyTW2ewJ05f_GKHtTIzjynDgjK
Problem
/login and /register work fine and I can see from the log they issue a JWT token in the req.headers
like 'Authorization':'Bearer <token>', however when I try to access GET /protected-route in my browser, it returns Unauthorized, and I can see no logging from JWTStrategy.
I think req.headers.Authorization is not set to all HTTP requests in the app, but only to POST /login, /register routes So my questions are:
Is this req.headers["Authorization"] = 'Bearer <toekn>'; correct way to set req headers to all GET and POST request in the app?
Does the jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken() checks for Authorization property in req.headers or res.headers?
I have provided the relevant code snippets please have a look!
Code:
app.js
//...
require('./config/database'); // require the db
// ...
const passport = require('passport');
const strategy = require('./config/passport').strategy;
// ...
app.use(passport.initialize());
passport.use(strategy);
// ...
module.exports = app
router/index.js
const router = express.Router();
//...
const User = require('../config/database').User;
const passport = require('passport');
const utils = require('../lib/utils');
// register route
router.post('/register', function(req, res, next){
const saltHash = utils.genPassword(req.body.password);
console.log("req.body.password is: " + req.body.password)
const salt = saltHash.salt;
const hash = saltHash.hash;
const newUser = new User({
username: req.body.username,
hash: hash,
salt: salt
})
newUser.save()
.then((user) => {
const jwt = utils.issueJWT(user);
if (jwt){
req.headers["Authorization"] = jwt.token;
}
res.redirect('/login')
})
.catch(err => next(err))
});
// login route
router.post('/login', function(req, res, next){
User.findOne({username: req.body.username})
.then((user) => {
if(!user) {
res.status(401).json({success: false, msg: "Could not find user "})
}
// validate the user
const isValid = utils.validPassword(req.body.password, user.hash, user.salt)
if(isValid) {
// issue a JWT
const jwt = utils.issueJWT(user);
if (jwt){
req.headers["Authorization"] = jwt.token;
}
res.redirect("/")
} else {
res.status(401).json({success: false, msg: "you entered the wrong password"})
}
})
.catch(err => next(err))
});
// GET protected route
router.get("/protectd-route", passport.authenticate('jwt', {session: false}), async (req, res, next) => {
res.render("user/getuser.pug")
next()
})
passport.js
const User = require('../config/database').User;
// ...
const Strategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
/// PUB_KEY
const pathToKey = path.join(__dirname, '..', 'id_rsa_pub.pem');
const PUB_KEY = fs.readFileSync(pathToKey, 'utf8');
// options
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: PUB_KEY,
algorithms: ['RS256']
};
// strategy
const strategy = new Strategy(options, (payload, done) => {
User.findOne({_id: payload.sub})
.then((user) => {
if(user) {
return done(null, user)
} else {
return done(null, false)
}
})
.catch((err) => done(err, null))
});
module.exports.strategy = strategy;
utils.js
genPassword() - Creating a salt and hash out of it to store in db
validPassword() - re-hashing user salt and hash to verify
issueJWT() - signing user with jsonwebtoken
const crypto = require('crypto');
const jsonwebtoken = require('jsonwebtoken');
const User = require('../config/database').User;
//...
const pathToKey = path.join(__dirname, '..', 'id_rsa_priv.pem');
const PRIV_KEY = fs.readFileSync(pathToKey, 'utf8');
// validate in /login
function validPassword(password, hash, salt) {
var hashVerify = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');
return hash === hashVerify;
}
// generate in /register
function genPassword(password) {
var salt = crypto.randomBytes(32).toString('hex');
var genHash = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');
return {
salt: salt,
hash: genHash
};
}
// sign
function issueJWT(user) {
const _id = user._id;
const expiresIn = '86400';
const payload = {
sub: _id,
iat: Date.now()
};
const signedToken = jsonwebtoken.sign(payload, PRIV_KEY, { expiresIn: expiresIn, algorithm: 'RS256' });
return {
token: "Bearer " + signedToken,
expires: expiresIn
}
}
module.exports.validPassword = validPassword;
module.exports.genPassword = genPassword;
module.exports.issueJWT = issueJWT;
Postman
In Postman, the protected route is showing successfully, with Headers set as above.
The browser network is showing this, there is no Authorization property both in response and request headers
Clearly, I'm missing something, but I can't figure it out. Any help is appreciated
here is also my db
database.js
const uri = process.env.MONGO_URI
const connection = mongoose.connection;
mongoose.connect(uri, {
useUnifiedTopology: true,
serverSelectionTimeoutMS: 5000
})
connection.on('error', console.error.bind(console, "connection error"))
connection.once('open', () => {
console.log('MongoDB database connection has been established successfully')
})
const userSchema = new mongoose.Schema({
username: String,
hash: String,
salt: String
});
const User = mongoose.model('User', userSchema )
module.exports.User = User;
I am trying to implement a React - Node js application that authenticates the user with Google and then retrieve its YouTube channel Id with google apis. I'm new to Google APIs, so I need some help to make this code works. The authentication with Google perfectly works, but I have a lot of difficulties in making the request to retrieve the channel id.
This is the code to focus in the React authentication component implemented with react-google-login:
<GoogleLogin
clientId={process.env.REACT_APP_GOOGLE_CLIENT_ID}
buttonText="Log in with Google"
onSuccess={handleGoogleLoginSuccess}
onFailure={handleGoogleLoginFailure}
cookiePolicy={'single_host_origin'}
scope='https://www.googleapis.com/auth/youtube.readonly'
/>
const handleGoogleLoginSuccess = (googleData) => {
//The token id is in googleData.tokenId
console.log(googleData);
axios.post('auth/googleLogin', {
token: googleData.tokenId,
access_token: googleData.accessToken
}).then(response => {
//Login success
if(response.data.loginStatus === 'ok') {
setLoginMessage(''); //Reset message
const user = response.data.user;
console.log(user.email + " " + user.firstName + " " + user.lastName)
registerUser(user); //Register user in the context
//console.log(currentUser.email + " " + currentUser.firstName + " " + currentUser.lastName)
localStorage.setItem('user', JSON.stringify(user)); //Push user in the storage
history.push('/home'); //Redirect to home page
}else{ //Login fail
//Set error messages.
const message = response.data.message;
setLoginMessage(message);
}
});
}
const handleGoogleLoginFailure = () => {
setLoginMessage("Impossible to login with Google at the moment. Please retry later.")
}
While the end point in the express server is:
router.post('/googleLogin', async (req, res) => {
const { token, accessToken } = req.body;
const ticket = await client.verifyIdToken({
idToken: token,
audience: process.env.CLIENT_ID
});
const {email, given_name, family_name} = ticket.getPayload();
const { OAuth2 } = google.auth;
const oauth2Client = new OAuth2();
oauth2Client.setCredentials({ access_token: accessToken });
var service = google.youtube({
version: 'v3',
auth: oauth2Client,
});
service.channels.list({
key: process.env.GOOGLE_API_KEY,
auth: client,
mine: true,
part: 'snippet',
}, (err, response) => {
if(err) {
console.log(err);
return;
}
var channels = response.data.items;
console.log(channels);
});
const [user, created] = await User.upsert({
email: email,
firstName: given_name,
lastName: family_name,
youtubeChannelId: 'TODO'
});
if(user) {
const accessToken = createTokens(user);
res.cookie("access-token", accessToken, {
maxAge: 60 * 60 * 24 * 1000, //one day
httpOnly: true
});
return res.json({
loginStatus: 'ok',
user: user
});
}else{
console.log("Error in login with Google");
}
});
I'm getting the error:
Error: No access, refresh token, API key or refresh handler callback is set.
Some ideas?
if you're using the Google OAuth 2.0 flow, I'm not sure why you're using the API key, since you're sending the user Access Token used to identify the user who completed the OAuth flow with your Client ID.
Also, I recommend using the global service auth, so you don't need to send auth credentials to each service call.
List my YouTube channels
View in Fusebit
const { OAuth2 } = google.auth;
const oauth2Client = new OAuth2();
oauth2Client.setCredentials({ access_token: accessToken });
// Add global service auth
google.options({ auth: oauth2Client });
const youtube = googleClient.youtube('v3');
const channelsResponse = await youtube.channels.list({
part: 'id,statistics,snippet',
mine: true
});
Let there be a landing page that enables the login. This landing page has:
Text field for user ID: LoginUserIDInput
Text field for password: LoginPasswordInput
Button to start the login process: LoginButton
React and Redux are supposed to do the following:
If the credentials are entered in the dialogue and the "Login
Execute" button is pressed the authentication is carried out via
basic authentication to the backend.
If authentication is successful, the token is stored in the Redux
store and redirected to the "private" page.
If the "Logout" button is pressed, the token in the Redux Store is
deleted and redirected to the start page.
The login process takes place via the http protocol. A separate BackEnd was created for the login process.
The login process is successful in that the basic auth is created from the credentials and a bearer token is sent back, but after the bearer token arrives I get an error message from the reducer.
How can a successful login succeed?
I will split the code into client and server side as I am not sure where the error is.
CLIENT SIDE (REACT/REDUX)
AuthenticationAction.js
//here I transform the userID and the password into a hash
//I get the Bearer Response. So the authentication process is
//succesful
function login(userID, password) {
const hash= Buffer.from(`${userID}:${password}`).toString('base64')
const requestOptions = {
method: 'POST',
headers: { 'Authorization': `Basic ${hash}` },
};
console.log(requestOptions)
return fetch('https://localhost:443/login', requestOptions)
.then(handleResponse)
.then(userSession => {
return userSession;
});}
function handleResponse(response){
//the problem starts here
//if everything is ok, I want to dispatch a success message to the
//reducer, if not an error message
const authorizationHeader = response.headers.get('Authorization');
return response.text().then(text => {
console.log('Receive result: '+authorizationHeader.split(" ")[1])
const base64Credentials = authorizationHeader.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
/* console.log(credentials.split(':')[3].split(",")[0]) */
const [username, password] = credentials.split(':');
console.log(username)
console.log(password)
const data= text && JSON.parse(text);
var token = authorizationHeader.split(" ")[1];
let userSession = {
user: credentials.split(':')[3].split(",")[0],
accessToken: token
}
return userSession;
})}
SERVER SIDE (express/node js)
AuthenticationRoute.js
var express = require('express');
var router = express.Router();
var authenticationService = require('./AuthenticationService')
router.post('/', authenticationService.basicAuth);
module.exports = router;
Authentication Service
function basicAuth(req, res, next) {
if(!req.headers.authorization || req.headers.authorization.indexOf('Basic ') === -1) {
return res.status(400).json({
status: 'Fehler',
error: 'Nicht autorisiert',
});
}
const base64Credentials = req.headers.authorization.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
const [username, password] = credentials.split(':');
console.log("AuthenticationService " + username + " " + password);
userService.getByUserId(username, function(error, user) {
user.comparePassword(password.toString(), function(err, isMatch) {
if(err) {
console.log("Fehler")
throw err;
}
/*Passwort richtig*/
if(isMatch) {
console.log("Passwort richtig. Token wird erstellt.")
var issueAt = new Date().getTime();
var expirationTime = config.get('session.timeout')
var expiresAt = issueAt + (expirationTime * 1000);
var privateKey = config.get('session.tokenKey');
let token = jwt.sign({
"user": user.userID
}, privateKey, {
expiresIn: expiresAt,
algorithm: 'HS256'
});
console.log("Token erstellt: " + token);
res.header("Authorization", "Bearer " + token);
res.send("Token erfolgreich gesendet");
}
/*Passwort falsch*/
else {
console.log("Falsch.")
res.status(500).json({
error: "Passwort und userID stimmen nicht überein."
});
}
});
})
}
module.exports = {
basicAuth,
}
I want to use accessToken variable outside the success function. I tried different ways to use variable but it didn't work.
var authenticationData = {
Username : 'username',
Password : 'password',
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var poolData = { UserPoolId : 'us-east-1_ExaMPle',
ClientId : '1example23456789'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username : 'username',
Pool : userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
var accessToken = result.getAccessToken().getJwtToken();
/* Use the idToken for Logins Map when Federating User Pools with identity pools or when passing through an Authorization Header to an API Gateway Authorizer */
var idToken = result.idToken.jwtToken;
},
onFailure: function(err) {
alert(err);
},
});
Why not add the accessToken to the authenticationData object?
var authenticationData = {
Username : 'username',
Password : 'password',
AccessToken : ''
};
// And then set the token in the success method:
authenticationData.AccessToken = result.getAccessToken().getJwtToken();
Once authenticationData.AccessToken has been set, you can use the value anywhere in scope.
Why does the AWS Cognito adminDeleteUser function as shown in the code below give a "User pool does not exist" error message?
const aws = require('aws-sdk');
aws.config.accessKeyId = 'aaaaaaa';
aws.config.secretAccessKey = 'sssssss';
aws.config.region = 'us-west-2';
const CognitoIdentityServiceProvider = new
aws.CognitoIdentityServiceProvider();
global.fetch = require('node-fetch');
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
// this section of code produces a correct userPool object
let poolData =
{
UserPoolId: 'ppppppp',
ClientId: 'ccccccc'
};
let userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
console.log('userPool: ', userPool);
// this section of code reports error: "ResourceNotFoundException:
User pool ppppppp does not exist."
let params =
{
UserPoolId: 'ppppppp',
Username: 'nnnnnnn'
};
CognitoIdentityServiceProvider.adminDeleteUser(params, (err,data) =>
{
if (err) console.log(err);
else console.log('user deleted');
});
Problem has been resolved by changing code from
aws.config.accessKeyId = 'aaaaaaa';
aws.config.secretAccessKey = 'sssssss';
aws.config.region = 'us-west-2';
to the following
aws.config.update(
{
accessKeyId: 'aaaaaaa',
secretAccessKey: 'sssssss',
region: 'us-west-2'
});
Note also that the following code creates a userPool object whether or not a valid UserPoolId value is provided:
let poolData =
{
UserPoolId: 'ppppppp,
ClientId: 'ccccccc'
};
let userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);