1

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
Community
  • 1
  • 1
Deovandski
  • 790
  • 1
  • 8
  • 22
  • 1
    Maybe add a secure checksum of the values to the token so you can validate that nothing changed? – Joachim Isaksson Mar 13 '16 at 17:40
  • Sounds reasonable, but do you know how would I keep tabs on the local storage? Or would I do a checksum every time I retrieved information from there? From my understanding, this would require quite a few changes to the Ember-Simple-Auth which would impact maintenance. – Deovandski Mar 13 '16 at 17:59

2 Answers2

7

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.

  • Fair enough, but do you know if Ember-simple-auth has any capability of recognizing that the local storage has been tampered with? – Deovandski Mar 13 '16 at 18:07
  • 1
    No, there is no way to do that reliably. But there really is no need - authentication ensures that a user can only access what he is supposed to access. All he can do by editing his local data is change/break what he sees in his browser. – Nikolaj Leischner Mar 13 '16 at 18:15
0

Token is already signed on the server side, a standard JWT mechanism.
Having said that, there can be a couple of ways to check tempering in local storage:

  1. Generate a token the way you already do.
  2. Generate a random secret key to be kept on the server.
  3. Generate a corresponding HMAC using this secret key.
  4. Send the token + HMAC to the user.
  5. When the user sends you this token, first check if HMAC is correct, if not then reject the token right away.
  6. If HMAC is correct, validate the token the way you already do.

Another way:
Along with the token, a HMAC checksum too can be stored separately, and when sent back to the server by the client, check if checksum matches.

viking
  • 411
  • 1
  • 5
  • 14