This is more of general frontend question, but for context, I'm using Vue 3 and vue-router.
I'm constructing functionality for authentication with Vue 3. Because I'm not using Vuex, I couldn't find a way to access state from the file where the router is instantiated. So I thought maybe I'd stick a flag called isAuthenticated into LocalStorage.
When the user logs in, isAuthenticated gets set to true, and then the router reads from this to validate subsequent redirects. This is very different from storing a token in LocalStorage – I store my tokens in httpOnly cookies.
For those that know Vue3/Vue-router, it looks like this:
Router.beforeEach(async (to, from) => {
// localStorage.isAuthenticated = false
const isAuthenticated = localStorage.isAuthenticated;
if (
// make sure the user is authenticated
isAuthenticated !== 'true' &&
to.meta.requiresAuth &&
// Avoid an infinite redirect
to.path !== '/login'
) {
// redirect the user to the login page
return '/login'
}
})
It would be no big deal if someone hacked this and changed the flag to true; my backend will reject any API call that doesn't have the right JWT token.
But is this bad practice? It feels a little flimsy, and I'm looking to write a robust and secure app.
Related
How to get JWT cookies in our react application, how to check the user is login or not I am unable to find how to handle my react application session.
I really appreciate who helps me out with this problem.
Thanks in advance
The server side API is setting the HTTPOnly cookie which you wont be able to read in JS.
What you need to do it in your react App handle a 401 status error and based on that set a flag isAuthenticated or something as false. Otherwise keep it to be true. With each request to the server HTTPOnly cookie would be sent automatically so you don't need to handle the token inside a cookie.
The backend code needs to send a 401 once the cookie is expired, or the logout is requested or the JWT inside a cookie expires.
Before I say anything, you have included app.use(cookieParser()) in index.js right? Because if not, you're gonna need that once you've installed it with npm i cookie-parser
But anyway, a few things:
You can create a PrivateRoute in React, as far as I'm aware this tends to work well to protect routes from unauthorized users.
You can simply store an isAuthenticated in either the state or localStorage: however this will require that you make absolutely sure that a user shouldn't be able to just change the value in the state or add isAuthenticated in localStorage and just spoof authenticity (this is the part that took me the longest to get right).
Anyway, the way I've handled this is that when a user logs in an access token (and a refresh token if it doesn't already exists) are generated from the server and sent to the client in an httpOnly cookie, while this makes it so that you can't do anything with it on the client side with JavaScript as Pavan pointed out in his answer (which is generally a good thing), you should use res.status for validation when you make the fetch request. Here's what my fetch request kind of looks like:
const login = async (user) => {
const body = JSON.stringify(user);
return fetch(loginURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'include', //this is important
body: body
}).then(function(res) {
if (res.status === 200) {
const id = Date.now();
localStorage.sid = id; //this is temporary
return res.json()
} else {
return res.json()
}
})
// you can ignore this part
.then(async resp => {
return resp
})
}
Side-note: Your browser automatically handles the httpOnly cookies you send from your server however the credentials: 'include' needs to be included in your subsequent fetch requests.
In one of my main parent components, the login function is called:
login = async (user) => {
this.setState({ error: null });
await adapter.login(user).then(data => {
if (!data.error) {
this.setState({session: "active"})
} else if (data.error && data.error.status === 401) {
// this is so I can handle displaying invalid credentials errors and stuff
this.setState({ error: data.error, loading: false });
}
});
}
I also have a middleware on the server-side that is run before any of the code in the routes to verify that the user making the request is actually authorized. This is also what handles access token expiration for me; if the access token has expired, I use the refresh token to generate a new access token and send it back in the httpOnly cookie, again with a status of 200 so the user experience isn't jarring. This does of course mean that your refresh token would have to live longer than your access token (I haven't decided how long in my case yet, but I'm thinking either 7 or 14 days) though as far as I'm aware that is okay to do.
One last thing, the parent component I referred to earlier calls a validate function which is a fetch request to the server within its componentDidMount so that the user is verified each time the component mounts, and then I've included some conditional rendering:
render() {
return (
<div className="container">
{
!localStorage.sid && <LoginForms {...yourPropsHere}/>
}
{
this.state.loading && <Loading />
}
{
localStorage.sid && this.state.session === "active" && <Route path="/" render={(props) => <Root error={this.state.error} {...props}/>}/>
}
</div>
);
}
I've gone the conditional rendering route as I couldn't get PrivateRoute to work properly in the time that I had, but either should be fine.
Hopefully this helps.
Your login API should return JWT token and how long it should be live.
Your login API response would be like :
{
jwt: your jwt token,
duration: in seconds
}
Use universal-cookies NPM to store this result into cookies.
For more details how to manipulate with cookies, visit
https://www.npmjs.com/package/universal-cookie
For setting cookies your code like:
const cookies = new Cookies();
cookies.set(name of cookies, jwt value from API call, {
maxAge: duration,
});
Above code store the jwt cookies in browser and after maxAge automatically remove it from browser.
So for identifying session is present or not, you should check after specific interval cookies has present in browser or not. If cookies has present in browser then session is on, otherwise session has expired.
I would like to create a route guard for protecting routes against unauthorized users.
I am using jsonwebtoken for authorization, and at the moment storing that in localStorage.
My idea is, when a user wants to access a protected admin route, authguard sends the token for validation to the nodeJS/Express server that after validation returns a true or 401 (whether the user is admin) to the client side.
auth service:
isLoggedIn(){
let headers = new HttpHeaders().set('x-auth-token',localStorage.getItem('token') || '');
return this.http.post('http://localhost:3000/api/users/check-auth', {}, { headers: headers }).toPromise();
}
authGuard service:
canActivate(){
return this.sign.isLoggedIn().then(res => {return res;}).catch(ex => {return ex});
}
My purpose would be to avoid manually setting a token key in the localstorage by the user to see the guarded route, even if he would not be able to implement any XHR request.
Could you please verify if its a good or bad idea and come up with better solution on security side?
Many thanks!
A good practice would be to manage roles (or permissions) at the model level on the server-side. For example a User class could have a roles property, such as :
auth.service.ts
myUser.roles = ['ROLE_ADMIN']
This way, when your user logins, you can store the information in your auth.service.ts
// auth.service.ts
get isAdmin() {
return this.user.roles.includes('ROLE_ADMIN')
}
Note that usually you want to store this information in you app state management, whether it be plain rxjs, ngrx, ngxs...
Finally you would add an AuthInterceptor which would redirect your user if your API returns a 401.
I'm adding OAuth into my github clientside application. I have the final auth token being returned successfully, but I feel like I hacked my workflow.
Current Architecture Flow:
1) User clicks href link from component to hit the initial OAUTH route
2) Retrieve token from Github for user identity
3) Github redirects to my server route and my server route sends an additional POST to the /access_token request page with the client_secret, id and code from the above step.
4) Finally I redirect from the above route back to my UI and set a URL parameter in the process
5) In the componentDidMount I strip the final auth token from the window.url and set it in my state
Note: I plan on storing the token in Redux later, but this is the base
level as to what I'm doing.
Actual Code
Server
app.get("/login", async (req, res) => {
// Get the identity token from GitHub origin
return await axios
.post("https://github.com/login/oauth/access_token", {
code: req.query.code,
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
})
.then(async resp => {
// Little hack to parse out the query params into an object
let data = await url.parse("http://parse.com?" + resp.data, {
parseQueryString: true
}).query;
res.redirect(
url.format({
pathname: Environment.getUrl(),
query: {
token: data.access_token
}
})
);
});
});
UI Authentication Component
export default class GithubAuthentication extends React.Component {
state = {
authToken: "DATA"
};
componentDidMount() {
let currUrl = window.location.href;
this.setState({ authToken: currUrl.split("token=")[1] });
}
render() {
return (
<React.Fragment>
<a href="https://github.com/login/oauth/authorize?client_id=b5cd37110eb31620aad7">
{this.state.authToken ? "Logout" : "Login With Github"}
</a>
<span>{this.state.authToken}</span>
</React.Fragment>
);
}
}
Questions
1) The one thing I wasn't able to figure out was to make the href link a controlled component and actually hit the auth URL with something like SuperAgent or Axios. Instead, I'm forced to use this href link, not sure why.
2) Is this actually a sensible flow for getting the final auth token?
Regarding question 2, from a security standpoint, it is better to keep access token on server-side and never send the token to client-side.
I couldn't find good written resources, so I'd like to share this video which sums up how to deal with access token clearly.
https://www.youtube.com/watch?v=CHzERullHe8&list=PL78z2Z3ZApYcKb-GDQt6ikdN2EDqmKJrT&index=12&t=419s
Take away from the video
We still don't have a good way to securely store the token on the browser
By storing the access token on the server-side and using session cookie, you can minimize the risk of access token being compromised.
To actually implement this flow, you can use cookie-session to generate session. You can also use github-passport to simplify the implementation.
https://github.com/expressjs/cookie-session
https://github.com/jaredhanson/passport-github
1) I think you should reorganize your app so that you can use a component instead of an href link. You would know whether you're authenticated or not based on the value on the state property. This value can be passed as prop to your component which is where you would put the logic of authenticated ? "Logout" : "Login" or anything else.
2) the flow is OK but you have to make sure you do server side validation of the token since it's easy to just flip the switch on the UI and the pretend you're authenticated very easily.
Assume you are working on a front end application that performs authentication through 3rd party api. Successful authentication returns a json web token.
What would be best practices to store such token and create some sort of session for user while he is active on the website i.e. didn't close a tab or browser, however refreshing / reloading a page should not destroy such session.
Also, how can this session be used to protect routes? I am working with a stack consisting of react / redux / node / express and quiet a few other libraries. I believe I can perform certain checks within my react-router, however wouldn't it be better to do these on the express side?
You can store the token in localStorage or sessionStorage, and include it in every API request.
Local storage outlives the tab, it's stored there until you explicitly delete from it, so refreshing a page won't be a problem. Even closing a tab and then coming back won't be.
Session storage allows you to store data. Page refreshes are fine, but tab closing isn't, which is closer to the behavior you want.
As for protecting routes, the server should obviously check the token on requests to all protected API routes.
On the browser side, you will probably want to show a login form if a user tries to visit a protected route but the token isn't there (or is invalid).
With react-router, you could do it like the official repo shows in the example, via onEnter hooks: https://github.com/reactjs/react-router/blob/master/examples/auth-flow/app.js
An alternative would be to create two top-level components, one for protected routes, one for public routes (like a landing page or the sign in/sign up forms). The protected handler will then in componentWillMount check if there's a token:
- PublicHandler
+ SignIn
+ SignUp
+ Index
- ProtectedHandler
+ Dashboard
+ MoneyWithdrawal
it may looks like that , with sessionStorage (JWT token is accesseble, untill browser or tab closed)
///action creator redux
export const signupUser = creds => dispatch =>{
dispatch(requestSignup());
return API.auth.signup(creds)
.then(res => {
sessionStorage.setItem('token', res.token);// <------------------
dispatch(receiveSignup(res));
return res;
})
.catch(err => {
dispatch(SignupError(err));
);
});
};
On client : handling auth through HOC redux-auth-wrapper
On server on server you can use passport-jwt strategy
passport.use('jwt',new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({where:{ id: jwt_payload.user.id }}).then(user=>{
if (user) {
done(null, jwt_payload.user);
} else {
done(null, false);
// or you could create a new account
}
},err=>{
console.log('Error ',err);
return done(err,false);
});
}));
then just add route handler
var checkJWT = passport.authenticate('jwt')
router.get('/protected',checkJWT, (req, res) =>{
res.json(req.user);
});
You don't need sessions on server for that
I am looking for a better way to do the transitions between route states in angularjs. Currently I have been following a couple different tutorials to configure angular with a backend api server using authentication on the server side.
http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/
https://vickev.com/#!/article/authentication-in-single-page-applications-node-js-passportjs-angularjs
Both of these describe a similar to identical solution to authentication on server side rather than on the client side. I have a Auth service in angular that uses a method to post to the api server checking whether the user isLoggedIn using an api key.
isLoggedIn: function( event, toState ) {
var user_api = {};
if( "user" in $rootScope ) {
user_api = { "api": $rootScope.user.api };
}
$http.post('http://bac-api/authenticated/', user_api)
.success( function( user ) {
if( toState.url === '/login' && user.authenticated === true) {
$state.transitionTo('dashboard');
}
})
.error( function( user ) {
if( user.authenticated === false ) {
event.preventDefault();
$state.transitionTo('login');
}
});
},
If the api key is not present of course the user is not logged in and the server will send a {"autheticated": false} 401 response. If the user has an api key it uses that api key to check on the server whether it is valid (logged in) or not valid (not logged in).
In order to catch the routes and check whether a user is authenticated I and using "stateChangeStart"
$rootScope.$on("$stateChangeStart", function ( event, toState, toParams, fromState, fromParams ) {
Auth.isLoggedIn( event, toState );
});
The first problem with this is that a state change is not triggered on the intial page load and results in using the routeProvider to transition to the login page. And then throughout the app if a route that is requested is not in the configured routes it will transition to the login page but not trigger a stateChangeStart. So the user could be logged in and sitting at the login page. I would rather it transfer to the dashboard as my configured route.
For the most part this setup seems to be working ok in theory but the routes are choppy. So what will happen is in between checking if the user is logged in the route will start to change and start to show the page but then when the application realizes the user is not logged it will make the switch to the login page. I would like to resolve this choppiness. And instead of getting a glipse of the other pages be able to transfer correctly.
I am using ui-router and states in the application for everything. And then using the Route Provider only to do $routeProvider.otherwise('login') when there isn't a route for the requested route.
I would like to figure out how to stop showing part of new pages (the choppiness) during route transitions while the application is checking for the user being authenticated. Whether that is in a different event that I'm unaware of or whatever.
Also a better way to use routeProvider.otherwise() to trigger a state change and check whether the user isLoggedIn. If logged in transfer to the dashboard ifnot logged in stay on login page until finished logging in.
Sorry if this is so confusing. I am new to angularjs and have been reading everything I can find about routes, states and authentication. This is a new concept for me to learn how to manage authentication on the server side with angular.
-- Edit
After taking the recommendation of MBielski I have implemented resolve into the states to use the Auth.isLoggedIn() service. But this still has not removed the choppiness of switching routes and I even fear that once this is not in local development the choppiness will become worse waiting on the api response. Here is the resolve code for my parent dashboard state.
'dashboard-shell': {
name: 'dashboard-shell',
resolve: {
Auth: function( Auth ) {
return Auth.isLoggedIn();
}
},
views: {
"dashboard-shell": {
controller: 'DashboardController',
templateUrl: 'app/modules/template-manager/partials/dashboard.tpl.html'
}
}
},
Your problem seems to be that every state change results in a POST request that must be handled before you can switch states. I assume you do that to handle session expiry, or because you don't have an authentication cookie to inspect.
You've basically implemented a pessimistic approach to ensure the user is authenticated before doing any other HTTP callbacks.
Another approach might be to use the angular-http-auth HTTP interceptor. This interceptor provides an optimistic approach to ensure the user is authenticated: it just passes every HTTP call on to the backend, but you give it a special callback; when the backend returns a 403 Unauthorized this callback is used to e.g. show a login dialog so the user can (re)authenticate. After that, the interceptor replays all HTTP calls that resulted in a 403.
The ui-router has a resolve option that should clear up the chop. https://github.com/angular-ui/ui-router/wiki. Anything that is contained in the resolve section will be completely resolved before the rest of the route changes happen and the pages load.
isLoggedIn: function( event, toState ) {
var user_api = {};
if( "user" in $rootScope ) {
user_api = { "api": $rootScope.user.api };
}
return $http.post('http://bac-api/authenticated/', user_api);
},
resolve: {
var logged = Auth.isLoggedIn();
logged.then(function(data){
//success
}, function(data){
//failure
});
}