Overcome login middleware in react - javascript

i am login from localhost use react to another localhost use express,
Hi Guys im using this code to post the login data via react:
handleSubmit(event) {
event.preventDefault();
var params = Object.entries(this.state).map(([key, val]) => `${key}=${val}`).join('&')
fetch("http://localhost:3006/login", {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: params
})
.then((response) => response.json())
.then((responseJson) => {
//this.props.history.push("/landing");
console.log("loggedin", responseJson);
})
}
and the other localhost the backend is :
router.post("/login", passport.authenticate("local",{successRedirect: "/landing",failureRedirect: "login"}) ,function(req, res){
console.log(req.body);
});
i need way to save my login data after i sent it so i can login to the page /landing.
normaly the code work when i remove the middleware.isLoggedIn from the landing page
router.get("/landing",middleware.isLoggedIn, function(req, res) {
so is there is any expert in react can help me to do it :)

One way to save user data (user session) is "WebTokens". Yow can use JsonWebToken to let the server recognize the user and know some data about him. This data is hashed and stored with the cookies in the browser. Any query from the browser (the client) to the server will be carrying this data and the server will read it and use any useful data.
For more info on how to use it with your express server take a deeper look at this example.

Related

How to get OAuth token from ebay API using express, node, javascript

What combination of requests and responses are needed to get an Oauth token from eBay? What is a runame and what headers do I need to keep eBay happy?
After three frustrating days of trying to get Ebay's oauth to give me an access token, I have finally worked it out. As the docs are pain and there is little to no help online, I have decided to post my solution here in the hope that it will help others. I am no good at StackOverflow so let me know if I need to improve my formatting.
app.get("/login/ebay", (req, res) => {
res.redirect(`https://auth.sandbox.ebay.com/oauth2/authorize?client_id=DeanSchm-TestApp-SBX-b843acc90-fd663cbb&redirect_uri=Dean_Schmid-DeanSchm-TestAp-kqmgc&response_type=code`
);
});
The first thing you need to do is redirect to this URL.
The format is like this
https://auth.sandbox.ebay.com/oauth2/authorize?client_id=&redirect_uri=&response_type=code
There is also a scope property, but I don't understand that yet, and I got back a token without is so me.
That URL takes you to the eBay login page. If you are using the sandbox, you need to create a sandbox user and login with sandbox credentials.
Once you log in, eBay will redirect you to a URL of your choosing. You enter the URL you want to be redirected to here.
It's in the ebay developer section under Get A Token From Ebay Via your Application.
This URL can be anything. you just have to handle it in node or express or whatever, because as soon as someone signs in that URL is where they are heading.
Here is how I handled it
app.get("/auth/ebay/callback", (req, res) => {
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization:
"Basic " +
btoa(
`client public key:client secret keys`
)
},
data: qs.stringify({
grant_type: "authorization_code",
// parsed from redirect URI after returning from eBay,
code: req.query.code,
// this is set in your dev account, also called RuName
redirect_uri: "Dean_Schmid-DeanSchm-TestAp-kqmgc"
})
})
.then(response => console.log(response))
.catch(err => console.log(err));
});
A few gotchas that got me.
Make sure you have space after "Basic " in the authorisation
header.
bota is a 3rd party library that base 64 encodes your public and
secret keys. There are many ways to do this. I just did it this way because I stole a bunch of code.
With Axios, the request body is called data but with fetch and other
methods it might be called something else like body or param
The Axios method is in a get request because of the redirect from ebay
defaults to an http get.
ebay now uses https. Make sure you are using
sandbox URLs
We also had to use JS for the eBay API and solved your mention problem with developing a new Lib. It's available here. This lib will also automatically try to refresh the token if it's expires.
This is how we obtain the oAuth token:
import eBayApi from 'ebay-api';
const eBay = new eBayApi({
appId: '-- or Client ID --',
certId: '-- or Client Secret',
sandbox: false,
siteId: eBayApi.SiteId.EBAY_US,
ruName: '-- eBay Redirect URL name --' //in this case: Dean_Schmid-DeanSchm-TestAp-kqmgc
});
// This will generate the URL you need to visit
const url = eBay.oAuth2.generateAuthUrl();
// After grant access, eBay will redirect you to RuName page and set the ?code query.
// Grab the ?code and get the token with:
eBay.oAuth2.getToken(code).then((token) => {
console.log('Token', token);
ebay.oAuth2.setCredentials(token);
// Now you can make request to eBay API:
eBay.buy.browse.getItem('v1|382282567190|651094235351')
.then(item => {
console.log(JSON.stringify(item, null, 2));
})
.catch(e => {
console.log(e);
});
});
Another example with scope can we found here.
Some hints:
with "scope" you tell eBay what you plan to use. You can find the
Descriptions here, under Sandbox/Production Keys Box. (OAuth
Scopes)
if you use axios you can use the auth config, so you dont't
need btoa:
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
// ...
auth: {
username: 'appId',
password: 'certId'
}
});
To use sandbox without https, e.g. localhost, you can setup a redirect on a https site and redirec/pass the code to non-https site.

How to Refresh Firebase Session Cookie

I'm developing a web application using Node.js/Express.js for the backend and I use Firebase for user authentication, and to manage user registration etc I use Firebase Admin SDK.
When a user want to login I sign him in using Firebase Client SDK like this:
// Handling User SignIn
$('#signin').on('click', function(e){
e.preventDefault();
let form = $('#signin-form'),
email = form.find('#email').val(),
pass = form.find('#password').val(),
errorWrapper = form.find('.error-wrapper');
if(email && pass){
firebase.auth().signInWithEmailAndPassword(email, pass)
.catch(err => {
showError(errorWrapper, err.code)
});
}else {
showError(errorWrapper, 'auth/required');
}
});
Below this code, I set an observer to watch for when the user successfully sign in, After a successfull sign in I get a Firebase ID token which I send to an endpoint on the server to exchange it for a session cookie that has the same claims the ID token since the later expires after 1 hour.
// POST to session login endpoint.
let postIdTokenToSessionLogin = function(url, idToken, csrfToken) {
return $.ajax({
type: 'POST',
url: url,
data: {
idToken: idToken,
csrfToken: csrfToken
},
contentType: 'application/x-www-form-urlencoded'
});
};
// Handling SignedIn Users
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
user.getIdToken().then(function(idToken) {
let csrfToken = getCookie('csrfToken');
return postIdTokenToSessionLogin('/auth/signin', idToken, csrfToken)
.then(() => {
location.href = '/dashboard';
}).catch(err => {
location.href = '/signin';
});
});
});
} else {
// No user is signed in.
}
});
Sign in endpoint on the server looks like this:
// Session signin endpoint.
router.post('/auth/signin', (req, res) => {
// Omitted Code...
firebase.auth().verifyIdToken(idToken).then(decodedClaims => {
return firebase.auth().createSessionCookie(idToken, {
expiresIn
});
}).then(sessionCookie => {
// Omitted Code...
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({
status: 'success'
}));
}).catch(err => {
res.status(401).send('UNAUTHORIZED REQUEST!');
});
});
I have created a middle ware to verify user session cookie before giving him access to protected content that looks like this:
function isAuthenticated(auth) {
return (req, res, next) => {
let sessionCookie = req.cookies.session || '';
firebase.auth().verifySessionCookie(sessionCookie, true).then(decodedClaims => {
if (auth) {
return res.redirect('/dashboard')
} else {
res.locals.user = decodedClaims;
next();
}
}).catch(err => {
if (auth) next();
else return res.redirect('/signin')
});
}
}
To show user information on the view I set the decoded claims on res.locals.user variable and pass it to the next middle ware where I render the view and passing that variable like this.
router.get('/', (req, res) => {
res.render('dashboard/settings', {
user: res.locals.user
});
});
So far everything is fine, now the problem comes after the user go to his dashboard to change his information (name and email), when he submits the form that has his name and email to an endpoint on the server I update his credentials using Firebase Admin SDK
// Handling User Profile Update
function settingsRouter(req, res) {
// Validate User Information ...
// Update User Info
let displayName = req.body.fullName,
email = req.body.email
let userRecord = {
email,
displayName
}
return updateUser(res.locals.user.sub, userRecord).then(userRecord => {
res.locals.user = userRecord;
return res.render('dashboard/settings', {
user: res.locals.user
});
}).catch(err => {
return res.status(422).render('dashboard/settings', {
user: res.locals.user
});
});
}
Now the view gets updated when the user submits the form because I set the res.locals.user variable to the new userRecord but once he refreshes the page the view shows the old credentials because before any get request for a protected content the middle ware isAuthenticated gets executed and the later gets user information from the session cookie which contains the old user credentials before he updated them.
So far these are the conclusions that I came to and what I tried to do:
If I want the view to render properly I should sign out and sign in again to get a new Firebase ID token to create a new session cookie which is not an option.
I tried to refresh the session cookie by creating a new ID token from the Admin SDK but it doesn't seem to have this option available and I can't do that through the client SDK because the user is already signed in.
Storing the ID token to use later in creating session cookies is not an option as they expire after 1 hour.
I Googled the hell out of this problem before posting here so any help is so much appreciated.
I am facing a very similar scenario with one of my apps. I think the answer lies in these clues.
From Firebase docs
Firebase Auth provides server-side session cookie management for traditional websites that rely on session cookies. This solution has several advantages over client-side short-lived ID tokens, which may require a redirect mechanism each time to update the session cookie on expiration:
So they're hinting here that you want to manage the session and it's lifetime from the server.
Second clue is in the docs
Assuming an application is using httpOnly server side cookies, sign in a user on the login page using the client SDKs. A Firebase ID token is generated, and the ID token is then sent via HTTP POST to a session login endpoint where, using the Admin SDK, a session cookie is generated. On success, the state should be cleared from the client side storage.
If you look at the example code, the even explicitly set persistence to None to clear state from the client using firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
So they are intending there to be no state on the client beyond the initial auth. They explicitly clear that state and expect an httponly cookie so the client can't grab the cookie (which really is just the ID token) and use it to get a new one.
It is odd that there is no clear way of refreshing the token client-side but there it is. You can only really create a session cookie with a super long lifetime and decide on the server when to delete the cookie or revoke the refresh token etc.
So that leaves the other option: manage state client-side. Some examples and tutorials simply send the ID token from the client to the server in a cookie. The satte sits on the client and the client can use the ID token to use all firebase features. The server can verify the user identity and use the token etc.
This scenario should work better. If the server needs to kick the user then it can delete the cookie revoke the refresh token (a bit harsh admittedly).
Hope that helps. Another scheme would be to build custom tokens, then you have complete control.

How to redirect user to a page that requires an access token without exposing token in a URL query

I am trying to implement a way of only allowing authorized admins to access pages with sensitive information within my Web App/API. I am currently using Node.JS with Express to handle routing. Views are generated with Jade/Pug View Engine. CORS requests are handled using the request framework on github. (https://github.com/request/request)
Currently, clients are stored within a MongoDB collection. When they enter their login information, their passwords are compared to a Hash stored in the DB. If everything checks out, a JWT is generated for the user and saved in the client DB and also stored within the browser in localStorage.token.
The user is then redirected to a splash page which has an authentication function as middleware. The authentication function accepts the token in three forms:
var token = req.body.token || req.query.token || req.headers['x-access-token'];
I feel like the way redirection is handled is a bit hacky. I am using window.location = route?token=[token]
Token is handed over to the authentication function within req.query.token, but this is exposed in URL. Here is my login function:
function submitLogIn() {
var credentials = {
email: logInForm.userEmail.value,
password: logInForm.pwField.value
}
fetch('/login', {
headers:{
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(credentials)
}).then(function(res){
if(!res.ok) alert("Error!")
else return res.json().then(function(result){
localStorage.token = result.token;
//This is how I'm doing redirecting:
window.location = '/selection?token=' + result.token;
}).catch(function(err){
console.log(err);
throw err;
});
});
Just for reference, this is the front-end route that it's going to at port 3001 which swings it over to 3000 as a reverse-proxy request:
router.post('/login', function(req, res, next) {
request.post({
url: 'http://localhost:3000/authorize',
form: req.body
}).pipe(res)
});
My main question is this: How can I handle redirection at the front-end within fetch calls?
My splash screen is basically a menu of buttons which will take the user to different pages. The pages will contain information that only admins should see. Let's say I want to click on this navigation button. It goes to a /GET route that requires the token to be sent for an OK status. if no token, it returns a 403.
router.get('/main', authorize.adminRequired, function(req, res, next) {
request.get({
url: 'http://localhost:3000/menus'
}, function(response, sensitiveItems) {
return res.render('index', {menu: JSON.parse(sensitiveItems)});
});
});
That authorize.adminRequired function needs a token in the form of a query, x-access-token, or req.body. Can't send a body in a GET request. Don't want to expose in URL, so:
Here's what I tried. I have this redirect fetch call as an onclick function.
function redirectToSensitivePage() {
fetch('/main', {
headers: {
'x-access-token': localStorage.token
},
method: 'GET'
});
};
This gives me a 200, OK. But I don't load the page. The res.render(index) in the route does not fire. The response does contain all of the data, but I am not redirected to the visual representation.
I could do the same hacky redirect using window.location=/main?token=[token], but I don't want to expose the URL token. I could load everything I need into the webpage upon login instead of having separate pages, have those divs hidden out of sight until keypress to make that div the main visible div, but I would rather figure out how to do it via redirect.
I'm still getting my bearings with using Node, so please let me know if I have made any stupid glaring oversights.
Edit: Should I simply try
var token = localStorage.token || req.body.token || req.query.token || req.headers['x-access-token'];
Is there any disadvantage to simply scraping from the localStorage automatically with each request?

Passport.js - Send back data to react/redux app, while redirecting after login

I'm creating a React/Redux app and am new to setting up the backend with Node/Express and authentication with Passport. What I'd like to have happen is the user logs in, credentials are checked, and if user is found from database, redirect the user to the next page and return user's info so I can update reducer.
My endpoint to login works as I think it does, as I'm able to access req.user object and am getting back the correct user info upon successful authentication. Where I'm stuck is redirecting the user to another page, while also passing along that user's info so I may update redux store.
EDIT: So I updated response by sending user info after authentication as well as the next route to redirect the user. Since I'm using redux, I perform login action, and upon getting a response, I redirect user with window.location. This partially solves my problem as even though I redirect after logging in, I believe that after updating window.location, my redux store gets refreshed, and I lose any information I grabbed server side.
app.post('/api/login', passport.authenticate('local-login', { failureRedirect: '/login'}), (req, res) => {
res.json({
redirect: '/profile
userInfo: req.user.dataValues})
})
})
export const login = (email, password) => {
return(dispatch) => {
axios
.post('/api/login', {
email: email,
password: password
})
.then(resp => {
dispatch({
type: 'LOGIN_USER',
data: resp.data
})
window.location = resp.data.redirect
})
.catch(errors => {
console.log(erros)
})
}
}
You're losing your state because you are forcing a page reload with window.location = resp.data.redirect. You should use a router with your application to do the url changes (such as react-router-dom). You can programmatically change the urls by using pushState (or maybe hashRouting?) instead of redirecting the way you currently are.

How to pass Request cookies through node-fetch in isomorphic app?

I'm trying to build isomorphic project using React, Express and isomorphic fetch (based on whatwg-fetch on client and node-fetch on server), from this common boilerplate. I'm using cookies for my access token, and credentials: 'same-origin' on front-end side to send it to GraphQL -- works pretty well.
The problem is that I can't use the same solution for server side -- node-fetch just don't support using of XMLHttpRequest cookies from the box. My fetch request is under few abstract layers from router, so I can't just use cookie value from req.
Here is my server.js code (full version):
server.get('*', async (req, res, next) => {
try {
// some presettings here..
await Router.dispatch({ path: req.path, query: req.query, context }, (state, component) => {
data.body = ReactDOM.renderToString(component);
});
res.send(template(data));
} catch (err) {
next(err);
}
});
and Route's index.js (full version):
export const action = async (state) => {
const response = await fetch('/graphql?query={me{id,email}}', {
credentials: 'same-origin',
});
const { data } = await response.json();
// ...
return <Login title={title} me={data.me} />;
};
How can I pass my token from server.js to my fetch module? Or, maybe there are some better decisions?
First off, I hope you have found an answer by now!
Secondly, cookies are really just headers. If you need to send a cookie to authorize server-side requests, you can always just create the string that you need for the cookie value and send it as a header.
For an example, take a look at how this server-side node-fetch wrapper appends saved cookies to the outbound request: https://github.com/valeriangalliat/fetch-cookie/blob/master/index.js#L17

Categories

Resources