How can I access the logged in user name on Netlify? - authentication

I'm having trouble understanding how handle functions on Netlify. In particular, I want to access the user's id when they login.
I have enabled identity on my netlify site and I can login and log out.
<button data-netlify-identity-button id="login"></button>
I have created a function identity-login that I think should handle the user's details, but I cannot see how to utilise it on the web-page
// functions/identity-login.js
exports.handler = async function (event, context) {
const { identity, user } = context.clientContext;
console.log(identity, user)
return {
statusCode: 200,
body: 'hello'
}
};
The function endpoint is
https://silly-parrot.netlify.app/.netlify/functions/identity-login
I have this in the script on my page, but I don't know how to call it or if it's correct
async function apiCall() {
const url = `/.netlify/functions/identity-login`;
try {
const response = await fetch(url);
const data = await response;
console.log(data)
return data;
} catch (err) {
console.log(err);
}
}
What should I do?

I now realise that I was taking the wrong approach. It is not necessary to use the Netlify identity-login event. The netlifyIdentity object provides the necessary functionality for identifying when the user logs in or logs out and to discover whether or not the user is logged in when the page loads (init). The user identity is contained in the user.token.access_token
The following code is within my main js script (you will of course need to access the netlifyIdentity object)
<script type="text/javascript" src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
And then setup async functions to handle the authorisation events
<script>
var token = '';
var logged_in = false;
async function started() {
logged_in = false;
netlifyIdentity.on('init', async user => {
if(user) {
token = user.token.access_token;
logged_in = true;
}
console.log('init', logged_in, token);
})
netlifyIdentity.on('login', user => {
if(user) {
logged_in = true;
token = user.token.access_token;
}
console.log('log in', logged_in, token);
})
netlifyIdentity.on('logout', () => {
token = '';
logged_in = false;
console.log('log out', logged_in, token);
})
}
started()
</script>

Related

Do I still need users in user pool if I want to work Custom Authentication workflow in AWS Cognito?

I have a webservice which validates user/pwd and returns true/false (valid/invalid). I am trying to leverage Custom Authentication Workflow of AWS Cognito to integrate with the webservice.
I read through the docs and came across the define, create and verify lambda triggers and I tried those as follows:
Define trigger:
exports.handler = async (event) => {
if (!event.request.session || event.request.session.length === 0) {
event.response.challengeName = "CUSTOM_CHALLENGE";
event.response.failAuthentication = false;
event.response.issueTokens = false;
} else if (event.request.session.length === 1) {
// If we passed the CUSTOM_CHALLENGE then issue token
event.response.failAuthentication = false;
event.response.issueTokens = true;
} else {
// Something is wrong. Fail authentication
event.response.failAuthentication = true;
event.response.issueTokens = false;
}
return event;
};;
Create Trigger:
exports.handler = async (event) => {
if (event.request.challengeName == 'CUSTOM_CHALLENGE') {
event.response.publicChallengeParameters = {};
event.response.privateChallengeParameters = {};
}
return event;
}
Verify Trigger:
exports.handler = async (event, context) => {
//call webservice using "event.userName" and "event.request.challengeAnswer" (password)
var result = <bool-result-received-from-webservice>
event.response.answerCorrect = result;
return event;
};
The JS client looks like this:
Amplify.configure({
Auth: {
region: 'dd',
userPoolId: 'eeee',
userPoolWebClientId: 'ffff',
authenticationFlowType: 'CUSTOM_AUTH'
}
})
let user = await Auth.signIn(username)
.then(u => {
console.log(u); //(1) TOKENS ARE ALREADY CREATED HERE WITHOUT VERIFYING PASSWORD. NOT SURE.
if (u.challengeName === 'CUSTOM_CHALLENGE') {
console.log("responding to challenge..");
// to send the answer of the custom challenge
Auth.sendCustomChallengeAnswer(u, password)
.then(u2 => {
console.log("after responding to challenge...");
console.log(u2); //(2) NEW TOKENS ARE CREATED HERE. NOT SURE.
return u2;
})
.catch(err => {
console.log("ERROR with Challenge:");
console.log(err);
});
} else {
console.log("no challenge needed..");
return u;
}
})
.catch(err => {
console.log("ERROR with sign-in:..");
console.log(err);
});
I mentioned 1 and 2 in the comments above. Not sure if it's behaving correctly.
If the username is not in the "users" list of "user pool", it throws it as invalid login. Is it possible to validate username/password only through webservice having no "users" in the "user pool"?

cognito custom auth (CUSTOM_CHALLENGE) ignore retry because session expiration

my goal is to implement otp by sending a sms to user mobile. im able to achieve this using cognito custom auth flow, but, only works if the user success in the firts attemp, if the user enter a bad code, the session is gonna expire and a new code is required to be sent again, bad ux. i do need at least 3 attemps, which in theory are 3 sessions across this cognito auth flow.
im gonna share the four cognito lambdas (cognito triggers) i used for this: preSignUp, defineAuthChallenge, createAuthChallenge and verifyChanllenge
// preSignUp lambda
exports.handler = async (event) => {
event.response.autoConfirmUser = true;
event.response.autoVerifyPhone = true;
return event;
};
// defineAuthChallenge
exports.handler = async (event, context, callback) => {
if (event.request.session.length >= 3 && event.request.session.slice(-1)[0].challengeResult === false) {
// wrong OTP even After 3 sessions? FINISH auth, dont send token
event.response.issueToken = false;
event.response.failAuthentication = true;
} else if (event.request.session.length > 0 && event.request.session.slice(-1)[0].challengeResult === true) {
// Last answer was Correct! send token and FINISH auth
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
// INIT flow - OR - not yet received correct OTP
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
}
return event;
};
// createAuthChallenge
exports.handler = async (event, context) => {
if (!event.request.session || event.request.session.length === 0) {
// create only once the otp, send over sms only once
var otp = generateOtp();
const phone = event.request.userAttributes.phone_number;
sendSMS(phone, otp);
} else {
// get previous challenge answer
const previousChallenge = event.request.session.slice(-1)[0];
otp = previousChallenge.challengeMetadata;
}
event.response = {
...event.response,
privateChallengeParameters: {
answer: otp
},
challengeMetadata: otp // save it here to use across sessions
};
return event
}
// verifyChanllenge
exports.handler = async (event, context) => {
event.response.answerCorrect = event.request.privateChallengeParameters.answer === event.request.challengeAnswer;
return event
}
for the client, which is a RN app, im using amplify, this is the flow in the app:
// SignIn form screen
import { Auth } from "aws-amplify";
const signUp = (phone) => {
Auth.signUp({
username: phone,
/** dummy pass since its required but unused for OTP */
password: "12345678"
}).then(() => {
// after signup, go an automatically login (which trigger sms to be sent)
otpSignIn(phone);
}).catch(({code}) => {
// signup fail because user already exists, ok, just try login it
if (code === SignUpErrCode.USER_EXISTS) {
otpSignIn(phone)
} else {
...
}
})
}
const otpSignIn = async (phoneNumber) => {
const cognitoUser = await Auth.signIn(phoneNumber)
setCognitoUser(cognitoUser);
navigate("ConfirmNumber", {phoneNumber});
}
import { Auth } from "aws-amplify";
let cognitoUser;
export function setCognitoUser(user) {
console.log('setCognitoUser', user)
cognitoUser = user;
}
export function sendChallenge(challengeResponse) {
return Auth.sendCustomChallengeAnswer(cognitoUser, challengeResponse)
}
// Confirm number screen
const onChangeText = (value) => {
if (value.length === 4) {
try {
const user = await sendChallenge(value)
// WEIRD THING NUMBER 1
// when the user send the second attempt, no error is raised, this promise is resolve!
// even when the trigger *verifyChanllenge* is returning false.
} catch (err) {
// WEIRD THING NUMBER 2
// from the trigger *createAuthChallenge* if i define the anser in the if block,
// and not store such answer for future use (i do that in else block), then,
// for the second..third attempt the error raised here is that *Invalid session for user* which mean session has expired,
// what i need is to persist session until third attempt
}
}
}
// this is amplify config: try 1
const awsExports = {
Auth: {
region: ...,
userPoolId: ...,
userPoolWebClientId: ...,
authenticationFlowType: 'CUSTOM_AUTH',
},
...
}
Amplify.configure(awsExports);
// this is amplify config: try 2
import {Auth} from "aws-amplify"
Auth.configure({
authenticationFlowType: 'CUSTOM_AUTH'
});
everything is correct in the code above, and the config for amplify authenticationFlowType: 'CUSTOM_AUTH' is not necessary.
the problem is that Auth.sendCustomChallengeAnswer(cognitoUser, challengeResponse) is not raising an error when the trigger defineAuthChallenge set this combination:
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
which presents the next attempt.
so i found a way to check the error when the user fail the otp:
const sendCode = async (value) => {
try {
// send the answer to the User Pool
// this will throw an error if it's the 3rd wrong answer
const user = await sendChallenge(value);
// the answer was sent successfully, but it doesnt mean it is the right one
// so we should test if the user is authenticated now
// this will throw an error if the user is not yet authenticated:
await Auth.currentSession();
} catch (err) {
setError(true);
}
}

Nextjs Auth0 get data in getServerSideProps

Im using Auth0 to authenticate users.
Im protected api routes like this:
// pages/api/secret.js
import { withApiAuthRequired, getSession } from '#auth0/nextjs-auth0';
export default withApiAuthRequired(function ProtectedRoute(req, res) {
const session = getSession(req, res);
const data = { test: 'test' };
res.json({ data });
});
My problem is when I'm trying to fetch the data from getServerSideProps I'm getting 401 error code.
If I use useEffect Im able to get data from api route.
Im trying to fetch the data like this:
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps(ctx) {
const res = await fetch('http://localhost:3000/api/secret');
const data = await res.json();
return { props: { data } };
},
});
Im getting the following response:
error: "not_authenticated", description: "The user does not have an active session or is not authenticated"
Any idea guys? Thanks!!
When you call from getServerSideProps the protected API end-point you are not passing any user's context (such as Cookies) to the request, therefore, you are not authenticated.
When you call from useEffect it runs inside your browser, which attaches all cookies to the request, one of them is the session cookie.
You need to forward the session cookie that was passed to the getServerSideProps (by the browser) to the API call.
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps(ctx) {
const res = await fetch('http://localhost:3000/api/secret', {
headers: { Cookie: ctx.req.headers.cookie },
// ---------------------------^ this req is the browser request to the getServersideProps
});
const data = await res.json();
return { props: { data } };
},
});
For more info.
#auth0/nextjs-auth0 has useUser hook. This example is from: https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/
// pages/index.js
import { useUser } from '#auth0/nextjs-auth0';
export default () => {
const { user, error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
if (user) {
return (
<div>
Welcome {user.name}! Logout
</div>
);
}
// if not user
return Login;
};
Note that authentication takes place on the server in this model,
meaning that the client isn't aware that the user is logged in. The
useUser hook makes it aware by accessing that information in the
initial state or through the /api/auth/profile endpoint, but it won't
expose any id_token or access_token to the client. That information
remains on the server side.
Custom HOF:
// getData is a callback function
export const withAuth = (getData) => async ({req, res}) => {
const session = await auth0.getSession(req);
if (!session || !session.user) {
res.writeHead(302, {
Location: '/api/v1/login'
});
res.end();
return {props: {}};
}
const data = getData ? await getData({req, res}, session.user) : {};
return {props: {user: session.user, ...data}}
}
Example of using:
export const getServerSideProps = withAuth(async ({req, res}, user) => {
const title = await getTitle();
return title;
});

Sequelize model property undefined Express.js controller after auth with passport-jwt

I am using passport-jwt to verify access to a given route in express.js, and then return a Sequelize model to the final controller. The code looks like:
The auth strategy:
const passportStrategy = passport => {
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.auth.ACCESS_TOKEN_SECRET
};
passport.use(
new Strategy(options, async (payload, done) => {
try {
const user = await User.findOne({ where: { email: payload.email }});
if (user) {
return done(null, {
user
});
}
return done(null, false);
}
catch (error) {
return done(error, false)
}
})
);
};
The route with the auth middleware
router.get('/:user_id/psy', passport.authenticate('jwt', { session: false }), patientsController.getPatientPsy);
The controller function
const getPatientPsy = async (req, res) => {
const authenticatedUser = req.user;
if (authenticatedUser.userType !== "patient") {
res.status(500).send("Big time error");
}
}
If I console.log(authenticatedUser) in the getPatientPsy() controller it successfully prints the Sequelize model with it's dataValues and so on, but when I try to access any property, be it userType or any other it consistently returns undefined.
In the passport-jwt authentication once a User has been found that matches the extracted JWT token, afaik it is returned synchronously and made it available in the req.user object, and I can print it with console.log, but why can't I access the model's properties?
I've tried to make the getPatientPsy() controller a sync function but it doesn't work either.
Thank you.
All right this is embarrassing, by default Passport.js returns the done(null, user) in the req.user property, and since I am returning { user }, I had to access through req.user.user.

using koa and passport for authenication

I'm using koa and passport trying to implement middleware to prevent access to URIs when not authenticated.
var koa = require('koa');
var session = require('koa-generic-session');
var bodyParser = require('koa-bodyparser');
var koaRouter = require('koa-router');
var passport = require('koa-passport');
var views = require('co-views');
var render = views('.', { map: { html: 'swig' }});
var localStrategy = require('passport-local').Strategy;
var app = koa();
var router = koaRouter();
app.keys = ['secret'];
app.use(session());
app.use(bodyParser());
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use(new localStrategy(function(username, password, done) {
if (username === 'user1' && password === 'password2') {
done(null, { userId: 99, userName: 'redBallons' });
} else {
done(null, false);
}
}));
router.get('/login', function *(next) {
this.body = yield render('index.html');
});
router.post('/login', passport.authenticate('local', {
successRedirect: '/secretBankAccount',
failureRedirect: '/login'
}));
router.get('*', function *(next) {
if (! this.isAuthenticated()) {
console.log('not authenticated');
this.redirect('/login');
} else {
console.log('authenticated');
yield next;
}
});
router.get('/secretBankAccount', function *(next) {
this.body = '2 dollars';
});
app.use(router.routes());
app.listen(8080);
however, I can never get to my secretBankAccount. I can enter the correct user and password and can see the authenicated message, but the yield next in router.get('*') does not pass me through to the next routing function
When using koa-router it is expected that only one route is hit. So when you hit the '*' route it won't hit another route even if you yield next.
So you should replace the universal route with your own authentication middleware:
app.use(function*(next) {
if (this.isAuthenticated()) {
yield next
} else {
this.redirect('/login')
}
});
The authentication middleware will force you to do your routing with two routing objects instead of one. This is so you can distinguish between public and secured routes. So something like:
var public = new koaRouter();
public.get('/login', function *(next) {
this.body = yield render('index.html');
});
public.post('/login', passport.authenticate('local', {
successRedirect: '/secretBankAccount',
failureRedirect: '/login'
}));
app.use(public.routes());
app.use(function*(next) {
if (this.isAuthenticated()) {
yield next;
} else {
this.redirect('/login');
}
})
var secured = new koaRouter();
secured.get('/secretBankAccount', function *(next) {
this.body = '2 dollars';
});
app.use(secured.routes());
In the above example a request will first hit the public routing middleware. Then if it doesn't match the current request with a public route it will move onto the authentication middleware. If isAuthenticated() is false a redirect will occur. If isAuthenticated() is true it'll move onto the secured routing.
This approach is based on the koa-passport-example project which was created by the author of koa-passport.
Answer from peadar-doyle is the way to do this but needs updating to avoid the warning: koa deprecated Support for generators will be removed in v3.
Here's the updated version. I'm sending 401 instead of redirecting:
// all requests must now be authenticated
app.use(async (ctx, next) => {
if (ctx.isAuthenticated()) {
await next();
} else {
ctx.body = "access denied";
ctx.status = 401;
}
})