I have a simple Backbone.js app with User model with different roles and I use json-server to emulate some backend basics. I need to make a basic authentication -- i.e. I need my User to be able to login and save his session somewhere (for that he wouldn't need to sign in each time he refreshes his browser). I've got a db.json file where I already have some users:
{
"users": [
{
"login": "admin",
"password": "password",
"role": "admin",
"id": 1
}
]
}
and here is my User model:
var User = Backbone.Model.extend({
defaults: {
login: "",
password: "",
role: ""
},
// Updated
url: function () {
return "http://localhost:3000/users?login=" +
this.attributes.login + "&password=" + this.attributes.password;
}
});
I don't get quite good how could I manage authentication (i.e. entering login and password in form and storing the user session without proper backend). I thought about making a token field in my User model and filling in in each time user signs in and saving it in cookies, but I don't get how could I do that either way. I would be very grateful if someone could help me with this task.
ADDDED This is my login function in my view:
signIn: function () {
var login = $('#js-login').val();
var password = $('#js-password').val();
if (login && password) {
var currentUser = new User({
login: login,
password: password
});
currentUser.fetch({
success: function () {
console.log(currentUser.toJSON());
},
error: function (err) {
console.log(err);
}
});
}
}
But instead of finding a user from my json-server it just creates a new user with all empty attributes except of values of #js-login and #js-password input fields
ADDED I guess I should find my users by the query in url above in my collection, but I don't actually get how I would manage that
Repo with my project
This is simplified flow for your app:
Each time user open your website, check his cookies.
If cookies contain user info (saved username, password), check match with the info in your DB. If matched, go to home page. Otherwise, clear cookies, go to login page
If cookies not contain user info, go to login page
In login page, after user success logged in, save user info to cookies for next time check.
You can use some mechanism to encode user info (tokens, encryption...) to secure info stored in cookies/sessions. But store authentication DB in client is really weak security point. Sample code below:
Model:
var User = Backbone.Model.extend({
url: function () {
return "users?login" + this.attributes.login + "&password=" + this.attributes.password;
},
isAdmin: function () {
return (this.get("role") == "admin");
}
});
In your view:
// Load username password from cookie (just simple example)
var username = $.cookie("username"),
password = $.cookie("password");
if (username && password) {
var userModel = new User({
login: username,
password: password
});
userModel.fetch({
success: function () {
if (userModel.isAdmin) {
// e.g. go to admin page
} else {
// e.g. go to normal user page
}
// Save to cookie/session here
},
error: function () {
// Go to login page
}
});
} else {
// Go to login page
}
About cookie, you can refer How do I set/unset cookie with jQuery?
About getting username/password input form, you can just use simple jquery selector (very easy to google for it, e.g. https://www.formget.com/jquery-login-form/)
Here you can refer to this plugin that uses mostly the jquery functions as mentioned in the documentation here
I would not be going into much detail as the documentaion is quite clear.
This refers to the authentication with the jquery
Now IF you want to authenticate the user using backbone.js
if the route came back with {loggedIn: false} the backbone router would send the user to the login/register pages only. But if it came back with a users profile information then it would obviously mean he had a session.
wire up $.ajax to respond to 401 (Unauthorized) status codes.
Also to mention as stated in this stackoverflow thread
Hope it may be able to help you a bit.
Here is the step by step guide to authenticate with backbone.js
Related
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.
I've set-up a Firebase project which I am using for it's user authentication module. I am also using the firebaseui-web project from Github.
My redirect on sign-on is working fine per this code:
// FirebaseUI config.
var uiConfig = {
'signInSuccessUrl': 'MY_REDIRECT.html',
'signInOptions': [
firebase.auth.EmailAuthProvider.PROVIDER_ID
],
// Terms of service url.
'tosUrl': '<your-tos-url>',
};
When the page loads (i.e. MY_REDIRECT.html) I'm checking the status of the user to see if they have verified their e-mail, and if not then invoke the sendEmailVerification method:
checkLoggedInUser = function() {
auth.onAuthStateChanged(function(user) {
if (user) {
// is email verified
if(user.emailVerified) {
// show logged in user in UI
$('#loggedinUserLink').html('Logged in:' + user.email + '<span class="caret"></span>');
} else {
// user e-mail is not verified - send verification mail and redirect
alert('Please check your inbox for a verification e-mail and follow the instructions');
// handle firebase promise and don't redirect until complete i.e. .then
user.sendEmailVerification().then(function() {
window.location.replace('index.html');
});
}
} else {
// no user object - go back to index
window.location.replace("index.html");
}
}, function(error) {
console.log(error);
});
};
window.onload = function() {
checkLoggedInUser()
};
All good so far - Firebase is doing what I want! Thanks guys :)
However, in the Firebase Console UI there doesn't appear to be a way of seeing if a user actually went to their inbox and clicked on the link to perform the verification. The UI looks like this:
I've run basic tests and the User UID doesn't change before and after verification has taken place.
So, here's my question - did I go about the e-mail verification correctly? If so (and therefore the UI doesn't show me verified vs unverified) is there an accepted method of accessing these two sets of users in the Auth module? I can't see the way to access the underlying table of UIDs and their properties (including the emailVerified property). I don't mind having to write more code if the functionality isn't in the Console - just looking to get nudged in the correct direction for my next step.
There is currently no way in the Firebase Console to see whether a specific user's email address has been verified. There is an API to get a list of users, but you can't filter on whether they're verified or not.
You can check whether the currently authenticated user's email address is verified with:
firebase.auth().currentUser.emailVerified
You cannot prevent who signs up. But you can easily ensure that only users with a verified email address can access (certain) data. For example:
{
"rules": {
".read": "auth != null && auth.token.email_verified",
"gmailUsers": {
"$uid": {
".write": "auth.token.email_verified == true &&
auth.token.email.matches(/.*#gmail.com$/)"
}
}
}
}
The above rules ensure that only users with a verified email address can read any data and only users with a verified gmail address can write under gmailUsers.
I have the following functionality at my API and I strumbled upon a few questions:
POST /user (requires fullName, email, password) will create a new user, if the user has been created an unique activation ID is generated and a link to activate the account is send through mail to the user.
PUT /user (requires id, email) will activate the user.
Once the user has activated it's account, it can login.
POST /session (requires email, password) and logs in the user.
GET /session will look at the cookie session id and return user info if auth.
DELETE /session logs the user out.
Once the user is logged in, he is asked to submit their interests (just a HTML textarea) and they can submit a description about their account too (Location, gender, etc but it is all optional so also an HTML textarea just like Twitter account description)
Now my question is:
As you can see 2. PUT /user will activate the user, but how would I handle the submit interests and account description in a proper restful design?
Should I look at the point where at my backend server PUT /user will come in and detect the fields that where submitted?
Or would it make more sence to create a separated PUT /user/activate and PUT /user/interests.
Once this is finished, I want to expand it with restore password, also this would be a PUT /user wouldn't the field detection at the server side will get kinda messy?
Now about backbone, this is my session model:
var Session = Backbone.Model.extend({
url: '/session'
});
var session = new Session();
session.fetch(); // Get the user authentication of the backend server.
my user model:
var User = Backbone.Model.extend({
url: '/user'
});
function signup(fullName, email, password){
var user = new User();
user.save({
fullName: fullName,
email: email,
password: password
});
};
function activate(id, activationId){
var user = new User();
user.save({
id: id,
activationId: activationId
});
};
// Possibility...?
function submitInterests(id, interests){
var user = new User(url: '/user/interests/');
user.save({
id: id,
activationid: activationId
});
}
Thank you for reading.
A rule of thumb in RESTful world is:
Verbs down, nouns up.
That's because the magic 4 [GET, POST, PUT, DELETE] should be enough for all actions: no /users/activate / /user/edit stuff around.
While making a PUT over the whole /users for activation may seem legit, so would be making all the requests to /root and passing "entity = users, id = 3, ..." and so on.
I advice you to use /entityname for the collection [where you can POST to create a new one], then /entityname/:id in order to refer to a single entity [in this case, a single user].
Now you can make a PUT on /users/123 to accomplish whatever you need.
Of course you can nest resources:
/users/:id/interests
This is the route for all interests of :id-th user - you can issue a GET over it to retrieve them all, or a POST to add an element to the list, a PUT to set all the list from scratch.
One last thought about your session resource: a true RESTful service should be *stateless, i.e. it should not rely on session. Authorization has to be made on every request, see HTTP Basic Auth, though you can come with a session sometimes.
To be consistent to your schema, you can define a /user/:id/sessions resource where you can POST in order to make a new login, so you can keep track of user accesses.
I'm creating an ST2 application where you can login/register etc.
I'm wondering what the normal way is of logging in and having the User state across the entire application.
I have a User model with a REST proxy to get/save the data. When you load up the application I'm doing this to grab the user:
launch: function () {
var User = Ext.ModelManager.getModel('App.model.User');
User.load("", {
success: function (user) {
// I have the user here but it's only within this scope
}
});
}
But doing this it's only available within this function... so what do people usually do to get ahold of the user across the whole application? just store it within the application like:
application.user = user;
or do you create a store with an ID of User, using the User model and then retrieve with:
launch: function () {
var User = Ext.StoreManager.get('User');
User.load(function(user) {
// Do application logged in stuff
self.getApplication().fireEvent('userLogsIn');
});
}
someRandomFunction: function () {
var user = Ext.StoreManager.get('User').getAt(0),
email = user.get('email');
console.log(email);
}
Thanks, Dominic
Generally speaking you cannot rely on any information you save locally in your JS application. It can be spoofed and altered relatively easy.
What you need to do is to send username/password combination to your server back end. Server then should return encrypted cookie which application will send back to the server with each following request. Only by decrypting and verifying this cookie server can be sure of identity of logged in user.
Firstly, should the static page that is served for the app be the login page?
Secondly, my server side code is fine (it won't give any data that the user shouldn't be able to see). But how do I make my app know that if the user is not logged in, to go back to a login form?
I use the session concept to control user login state.
I have a SessionModel and SessionCollection like this:
SessionModel = Backbone.Model.extend({
defaults: {
sessionId: "",
userName: "",
password: "",
userId: ""
},
isAuthorized: function(){
return Boolean(this.get("sessionId"));
}
});
On app start, I initialize a globally available variable, activeSession. At start this session is unauthorized and any views binding to this model instance can render accordingly. On login attempt, I first logout by invalidating the session.
logout = function(){
window.activeSession.id = "";
window.activeSession.clear();
}
This will trigger any views that listen to the activeSession and will put my mainView into login mode where it will put up a login prompt. I then get the userName and password from the user and set them on the activeSession like this:
login = function(userName, password){
window.activeSession.set(
{
userName: userName,
password: password
},{
silent:true
}
);
window.activeSession.save();
}
This will trigger an update to the server through backbone.sync. On the server, I have the session resource POST action setup so that it checks the userName and password. If valid, it fills out the user details on the session, sets a unique session id and removes the password and then sends back the result.
My backbone.sync is then setup to add the sessionId of window.activeSession to any outgoing request to the server. If the session Id is invalid on the server, it sends back an HTTP 401, which triggers a logout(), leading to the showing of the login prompt.
We're not quite done implementing this yet, so there may be errors in the logic, but basically, this is how we approach it. Also, the above code is not our actual code, as it contains a little more handling logic, but it's the gist of it.
I have a backend call that my client-side code that my static page (index.php) makes to check whether the current user is logged in. Let's say you have a backend call at api/auth/logged_in which returns HTTP status code 200 if the user is logged in or 400 otherwise (using cookie-based sessions):
appController.checkUser(function(isLoggedIn){
if(!isLoggedIn) {
window.location.hash = "login";
}
Backbone.history.start();
});
...
window.AppController = Backbone.Controller.extend({
checkUser: function(callback) {
var that = this;
$.ajax("api/auth/logged_in", {
type: "GET",
dataType: "json",
success: function() {
return callback(true);
},
error: function() {
return callback(false);
}
});
}
});
Here is a very good tutorial for it http://clintberry.com/2012/backbone-js-apps-authentication-tutorial/
I think you should not only control the html display but also control the display data. Because user can use firefox to change your javascript code.
For detail, you should give user a token after he log in and every time he or she visit your component in page such as data grid or tree or something like that, the page must fetch these data (maybe in json) from your webservice, and the webservice will check this token, if the token is incorrect or past due you shouldn't give user data instead you should give a error message. So that user can't crack your security even if he or she use firebug to change js code.
That might be help to you.
I think you should do this server sided only... There are many chances of getting it hacked unit and unless you have some sort of amazing api responding to it