ember-simple-auth

Is there a way to prevent users from editing the local storage session?


I am creating a relational blog where I make use of ember_simple_auth:session to store the session like

{"authenticated":{"authenticator":"authenticator:devise","token":"rh2f9iy7EjJXESAM5koQ","email":"user@example.com","userId":1}}

However, on the developer tools on Chrome (and possibly on other browsers), it is quite easy to edit the email and userId in order to impersonate another user upon page reload.

EDIT #1

From the conversation with Joachim and Nikolaj, I now realized that the best way to tackle this problem is to probe the localStorage authenticity every time I need it (which is only on page reload) instead of attempting to prevent edits.

In order to validate authenticity, I create a promise that must be solved before the AccountSession can be used. The promise serverValidation() requests to create a token model with the current localStorage info, and when the server gets it, it validates the info and responds 200 with a simple user serialization with type as token if the information is legit. You can check more info on the Source Code.

Session Account

import Ember from 'ember';

const { inject: { service }, RSVP } = Ember;
export default Ember.Service.extend ({
    session: service('session'),
    store: service(),
    serverValidation: false,
    
    // Create a Promise to handle a server request that validates the current LocalStorage
    // If valid, then set SessionAccount User.
    loadCurrentUser() {
        if (!Ember.isEmpty(this.get('session.data.authenticated.userId'))) {
            this.serverValidation().then(() => {
                return new RSVP.Promise((resolve, reject) => {
                    const userId = this.get('session.data.authenticated.userId');
                        // Get User to Session-Account Block
                        if(this.get('serverValidation') === true) {
                            return this.get('store').find('user', userId).then((user) => {
                                this.set('user', user);
                                resolve();
                            }).catch((reason) => {
                                console.log(reason.errors);
                                var possible404 = reason.errors.filterBy('status','404');
                                var possible500 = reason.errors.filterBy('status','500');
                                if(possible404.length !== 0) {
                                    alert('404 | Sign In Not Found Error');
                                    this.get('session').invalidate();
                                }
                                else if(possible500.length !== 0) {
                                    alert('500 | Sign In Server Error');
                                    this.get('session').invalidate();
                                }
                                reject();
                            });
                        }
                        else{
                            alert('Session for Server Validation failed! Logging out!');
                            this.get('session').invalidate();
                            resolve();
                        }
                });
            });
        } else {
            // Session is empty...
        }
    },
    serverValidation() {
        return new RSVP.Promise((resolve) => {
            var tokenAuthentication = this.get('store').createRecord('token', {
                id: this.get('session.data.authenticated.userId'),
                email: this.get('session.data.authenticated.email'),
                authenticity_token: this.get('session.data.authenticated.token'),
            });
            tokenAuthentication.save().then(() => {
                this.set('serverValidation',true);
                console.log('Server Validation complete with 200');
                resolve();
            }).catch((reason) => {
                this.set('serverValidation',false);
                resolve();
            });
        });
    }
});

Token Controller

# Users Controller: JSON response through Active Model Serializers
class Api::V1::TokensController < ApiController
    respond_to :json

    def create
        if token_by_id == token_by_token
            if token_by_email == token_by_id
                render json: token_by_id, serializer: TokenSerializer, status: 200
            else
                render json: {}, status: 404
            end
        else
            render json: {}, status: 404
        end
    end

    private
    
    def token_by_id
        User.find(user_params[:id])
    end
    
    def token_by_email
        User.find_by(email: user_params[:email])
    end
    
    def token_by_token
        User.find_by(authentication_token: user_params[:authenticity_token])
    end
    
    def user_params
        ActiveModelSerializers::Deserialization.jsonapi_parse!(params.to_unsafe_h)
    end
end

Solution

  • There is no way to prevent a user from editing the content of his local storage, session storage, or cookies.

    But this should not worry you. The user is identified through the value of the token. The token is generated and sent to him by the authenticator when he logs in. To impersonate another user by editing the session data he would have to know that the other user is logged in, and know the token of that user.