GithubHelp home page GithubHelp logo

Multiple token suggestion about vue-auth HOT 31 CLOSED

websanova avatar websanova commented on May 20, 2024
Multiple token suggestion

from vue-auth.

Comments (31)

neontuna avatar neontuna commented on May 20, 2024 1

Ok, I think I've gotten to the bottom of this. It is due to how devise_token_auth handles "batches". More info here. Sometimes XHR responses come back as 304, instructing the browser to pull cached results for a request. This will eventually screw things up as token information is pulled from cached headers. This is compounded by the fact that devise_token_auth doesn't send back any headers when in batch mode.

One work around I tried from here is to attach some unique string to the request, for instance a timestamp. This prevents caching as every request is unique

Vue.http.interceptors.push((request, next) => {
  request.url += (request.url.indexOf('?') > 0 ? '&' : '?') + `cb=${new Date().getTime()}`
  next()
})

Pretty hacky, but it does work. However, other users of devise_token_auth think the API ought to be sending back headers even when in batch mode, and just set them to blank or something to let the client know not to change its stored token. So I went with a fix that sets "Access-Token" to "".

This will require vue-auth to make sure its not then trying to save a blank value. The current code is going to save something that looks like a bunch of ;;;; so I just added an additional check to make sure Access-Token isn't blank, and if it is the entire response returns false.

response: function (res) {
  var token = [],
    headers = this.options._getHeaders.call(this, res);
  if (headers['Access-Token']) {   
    this.options.deviseAuth.tokens.forEach(function (tokenName) {
      if (headers[tokenName]) {
        token.push(headers[tokenName]);
      }
    });

    return token.join(';');
  } else {
    return false;
  }
 }
}

Kind of annoying. But really just due to devise_token_auth doing some weird stuff. It looks like the caching thing just became an issue in the last couple of months. I imagine the gem will be fixed to not require any sort of work around on the client as it seems to me that normal browser behavior (caching XHR) shouldn't disrupt anything.

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Ya, that's a good idea, I can add that in. Bit swamped for now with some projects so will look into it next week.

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Hey, thx again for the suggestion. So I've update the scheme as per your suggestion.

In the response it will go through the array of token auth schemes and attempt one until it finds a token there and use that.

For the request it will just default to using the name and authType from the first scheme in the array. Thing is it will always do it as a header. Not sure it really makes sense otherwise so I will leave it like that for now.

One thing I'm a bit unclear on in your situation. Is it attempting to validate all those schemes per request, or just to find the first one that works?

from vue-auth.

cjcrawford avatar cjcrawford commented on May 20, 2024

This diagram shows a more technical response.

from vue-auth.

cjcrawford avatar cjcrawford commented on May 20, 2024

In short, I need to capture the API's 3 response headers, update my localStorage with those values, then use them in my next request.

I login to the API just like you would in Laravel via a single email/password post request. The response back on a successful authentication includes three tokens in the response headers: uid, client, access-token.

Every time I make a request to the API, I need to use those values in the request header. The access-token changes on each response. Based on the existing rails session and how long I sit idle vs how long my token is configured to expire, the client and uid tokens may change also. So because i am lazy I just grab, store, inject like this:

// auth.js
export default {
  tokens: [{
    name: 'access-token',
    type: 'bearer',
    header: true,
  }, {
    name: 'uid',
    header: true,
  }, {
    name: 'client',
    header: true,
  }],

  interceptResponse(httpHeaderResponse){
    this.tokens.forEach(token => {
      if (token.header && httpHeaderResponse.has(token.name)){
        window.localStorage.setItem(token.name,httpHeaderResponse.get(token.name))
      }
    })
  },
  // ... more auth stuff
}

...
// main.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
import auth from './auth'

Vue.use(VueRouter)
Vue.use(VueResource)

Vue.http.interceptors.push((request, next) => {

  // modify request
  auth.tokens.forEach(token => {
    if (window.localStorage.getItem(token.name)) {
      let tokenValue = localStorage.getItem(token.name)
      if (token.type) {
        tokenValue = token.type + ' ' + tokenValue
      }
      request.headers.set(token.name, tokenValue)
    }
  })

  // continue to next interceptor
  next(response => { auth.interceptResponse(response.headers) })
})

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Ok, so actually in this case I don't think I would implement this directly into the plugin. Unless there is some actual spec for it (is there?).

So best approach would be to just add a custom auth scheme which is already supported. Here is a sample from the current bearer one:

bearerAuth: {
    request: function (req, token) {
        var data = {};

        data[this.options.token[0].name] = 'Bearer ' + token;

        this.options._setHeaders.call(this, req, data);
    },
    response: function (res, token) {
        token = token.split('Bearer ');
        __token.set.call(this, null, token[token.length > 1 ? 1 : 0]);
    }
},

There is the request and response and you can pretty much do everything there right through the plugin. It might also be a good idea to put the keys together into one string like uid:client:token. Could also base64 encode an object like jwt based authentication but that would require a base64 encoding lib (extra code which may not be necessary).

from vue-auth.

cjcrawford avatar cjcrawford commented on May 20, 2024

Ok so with the new ability to provide an array of tokens in the config, I just define the authType as my own custom scheme like authDevise: ... Set all the tokens to that type, extend your plugin, loop all defined tokens and get/store/set each request.

By the way, the code I have in my last post is working with your plugin. I just configure the one main token with your plugin and then add my own 2 interceptors. So far it's been working fine.

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Ya, well if it works, that's great then :P If there is some official spec for this authentication scheme let me know. Otherwise I will close this out.

from vue-auth.

neontuna avatar neontuna commented on May 20, 2024

I've run into a similar situation. A rails API using "devise_token_auth" gem will send back data similar to what @cjcrawford was dealing with and I've actually used his code to get vue-auth working with the API.

However I'd like to use the custom scheme mentioned here. I was wondering if you could point me in the direction of how I'd actually configure vue-auth with a custom scheme similar to bearerAuth.

In terms of standards, the author of devise_token_auth talks about that here https://github.com/lynndylanhurley/devise_token_auth#token-header-format

from vue-auth.

websanova avatar websanova commented on May 20, 2024

So i see:

"The authentication headers consists of the following params:"

access-token, client, expiry, uid

These are all to be stored locally and are updated on every request?

from vue-auth.

neontuna avatar neontuna commented on May 20, 2024

That's correct. After some more testing last night I'm guessing this presents a problem for vue-auth as its expecting to get/set one header per request.

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Yea, I will have to rethink this a bit.

On Nov 3, 2016 18:13, "Justin Mullis" [email protected] wrote:

That's correct. After some more testing last night I'm guessing this
presents a problem for vue-auth as its expecting to get/set one header per
request.


You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
#30 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABkcy_WMaHboA_7sjmWvCE9um0M1LYCJks5q6cG3gaJpZM4KCS6Q
.

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Ok, I've made an update here and simplified the auth so that it's much easier to add auth drivers. Make sure to get the latest v1.4.0-beta. I've added an Authentication section to the docs but basically you need to setup the request and response for any custom driver. An example of the multiple tokens is in the docs. It should look something like this:

authType: 'custom2',

custom2Auth: {
    request: function (req, token) {
        token = token.split(';');

        req.headers.set('header1', token[0]);
        req.headers.set('header2', token[1]);
        req.headers.set('header3', token[2]);
        req.headers.set('header4', token[3]);
    },
    response: function (res) {
        var headers = this.options._getHeaders.call(this, res);

        if (headers.header1 && headers.header2 && headers.header3 && headers.header4) {
            return headers.header1 + ';' + headers.header2 + ';' + headers.header3 + ';' + headers.header4;
        }
    }
}

Basically you get the req object so you can parse the token and send back whatever you like to the server. Likewise for a response and needs to build the token and send back the "token" that will be stored by the plugin.

Let me know if that works or makes sense at all.

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Note you should also be able to use the internal _setHeaders function.

this.options._setHeaders.call(this, req {
    header1: token[0],
    header2: token[1],
    header3: token[2],
    header4: token[3]
});

from vue-auth.

cjcrawford avatar cjcrawford commented on May 20, 2024

So can I do something like this in the config options?

edit: fixed sample config code to something that might actually compile... :

// vue-auth config example for devise on rails API
deviseTokens: ['Token-Type', 'Access-Token', 'Client', 'Uid', 'Expiry'],
authType: 'devise',
deviseAuth: {
    request(req, token){
        let tokens = token.split(';');
        this.options.deviseTokens.forEach((tokenName, index) => {
            if (tokens[index]){
                req.headers.set(tokenName, tokens[index]);
            }
        })
    },
    response(res) {
        let headers = this.options._getHeaders.call(this, res);
        let return_val = [];
            this.options.deviseTokens.forEach((tokenName, index) => {
            if (headers[tokenName]){
                return_val.push(headers[tokenName]);
            }
        })
        return return_val.join(';');
    }
}

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Ya, in theory :p. I may just add that as a standard auth method. Is it
working?

On Nov 4, 2016 16:10, "Conan Crawford" [email protected] wrote:

So can I do something like this in the config options?

deviseTokens = ['Authorization bearer', 'access-token', 'client', 'uid'],
authType: 'devise',
deviseAuth: {
request(req, token){
let tokens = token.split(';');
this.options.deviseTokens.forEach((index, tokenName) => {
if (tokens[index]){
req.headers.set(tokenName, tokens[index]);
}
})
},
response(res) {
let headers = this.options._getHeaders.call(this, res);
let return_val = [];
this.options.deviseTokens.forEach((index, tokenName) => {
if (headers[tokenName]){
return_val.push(headers[tokenName]);
}
})
return return_val.join(';');
}
}


You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
#30 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABkcy3OTMxksimxM2sJVisfp9UAulGklks5q6vaJgaJpZM4KCS6Q
.

from vue-auth.

cjcrawford avatar cjcrawford commented on May 20, 2024

I'll have some time this weekend to test it against the rails API. v1.4.0-beta right?

from vue-auth.

neontuna avatar neontuna commented on May 20, 2024

I'll also check this weekend. Really appreciate the changes by the way!

from vue-auth.

neontuna avatar neontuna commented on May 20, 2024

This is working! @cjcrawford flip the index and tokenName arguments on forEach. Also on my install the token names were case sensitive. deviseTokens: ['Token-Type', 'Access-Token', 'Client', 'Uid', 'Expiry'],

Thank you guys!

Just for reference I'm using
Rails 4.2.6
devise_token_auth 0.1.39

from vue-auth.

cjcrawford avatar cjcrawford commented on May 20, 2024

I'll fix the code to line up with your fixes and we should have a good prototype for a devise auth. I'll be testing against my rails server later today, too.

from vue-auth.

websanova avatar websanova commented on May 20, 2024

@cjcrawford yes v1.4.0-beta.

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Awesome @cjcrawford and @NonAdmin.

@cjcrawford would you like to submit a PR or should I go ahead and put this in myself?

from vue-auth.

cjcrawford avatar cjcrawford commented on May 20, 2024

I can confirm that the fixed code above is working for me on my rails API. I would rather you implement it your own way with your own patterns. Here's my vue-auth config with standard devise endpoints.

import Vue from 'vue'
import VueRouter from 'vue-router'

const VueAuth = require('@websanova/vue-auth') // "@websanova/vue-auth": "^1.4.0-beta"
const apiEndPoint = 'https://API.YOURDOMAIN.COM/api/v1'

Vue.use(VueRouter)

Vue.router = new VueRouter({
 //... config
})

Vue.use(VueAuth, {
  router: Vue.router,
  deviseTokens: ['Token-Type', 'Access-Token', 'Client', 'Uid', 'Expiry'],
  authType: 'devise',
  deviseAuth: {
    request(req, token){
      let tokens = token.split(';')
      this.options.deviseTokens.forEach((tokenName, index) => {
        if (tokens[index]){
          req.headers.set(tokenName, tokens[index])
        }
      })
    },
    response(res) {
      let headers = this.options._getHeaders.call(this, res)
      let return_val = []
      this.options.deviseTokens.forEach((tokenName) => {
        if (headers[tokenName]){
          return_val.push(headers[tokenName])
        }
      })
      return return_val.join(';')
    }
  },
  registerData: {url: apiEndPoint + '/auth', method: 'POST', redirect: '/account'},
  loginData: {url: apiEndPoint + '/auth/sign_in', method: 'POST', redirect: '/account'},
  logoutData: {url: apiEndPoint + '/auth/sign_out', method: 'DELETE', redirect: '/account/login', makeRequest: true},
  refreshData: {url: apiEndPoint + '/auth/validate_token', method: 'GET', atInit: true},
  fetchData: {url: apiEndPoint + '/auth/validate_token', method: 'GET'},
})

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Ok, I've added this as a standard auth method. v1.5.0-beta. should be able to just set authType: 'devise' now. I changed the code around a bit so please let me know if there are any issues.

from vue-auth.

neontuna avatar neontuna commented on May 20, 2024

I'll have to check out the new version, but I'm running into issues trying to use "rememberMe" on login. It works initially and I can close and re-open the browser, move around my app (currently just the login page and a single route that pulls an index from the API). However eventually the token info that vue-auth sends to the API gets rejected. I can't seem to figure out why. I've tried in Safari and Chrome, and I even downloaded the "demo" API for the devise_token_auth gem and observed the same behavior. It almost seems like sometimes vue-auth isn't recording the latest token information to local storage, and therefore tries to use an old token which the API rejects.

Also once this starts happening vue-auth stops working at all. Login goes through but then it immediately gets rejected by the API on the next request (usually fetch data). If I clear the cache and start again it will work ok for 5-10 requests before bugging out again.

from vue-auth.

neontuna avatar neontuna commented on May 20, 2024

Ok I can confirm that is still an issue in v1.5.0-beta. I can definitely see what's happening. Refreshing the app and watching various requests go, I check and make sure that the data in localStorage matches what I see on the last response. After a few refreshes I can see that localStorage hasn't been updated with the latest token information, on the next refresh I get 401 errors and bounced back to login.

from vue-auth.

neontuna avatar neontuna commented on May 20, 2024

I think this is more than likely some sort of issue with devise_token_auth. It has a setting where requests made within 5 seconds of each other are considered a "batch" and the token doesn't change. However this seems to have caused some outstanding issues for other users and they're seeing the same random logouts I'm seeing.

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Ok, I put that in for you. v1.5.1-beta

Another thing, I'm not sure about devise. But this sounds similar to an issue I had with Laravel JWT Auth library. It was basically invalidating tokens immediately instead of letting expired ones actually still be valid for a minute so that many async requests could still go through without sending a 401.

There was a config setting to change this and all worked after that. Maybe something similar in devise?

from vue-auth.

neontuna avatar neontuna commented on May 20, 2024

Yes devise token auth has some settings related to this that would probably fix things but imo would make things less secure. For instance I like the fact that the token is constantly being refreshed but this could be turned off and that would help here. Ultimately I'm not too sad about the workarounds and I imagine devise_token_auth will fix things up on their end before too long. Thank you the addition!

from vue-auth.

cjcrawford avatar cjcrawford commented on May 20, 2024

v1.5.1-beta working great for me. The only devise thing I'm using is:

  authType: 'devise'

Nice work!

from vue-auth.

websanova avatar websanova commented on May 20, 2024

Note the new driver centric model in 2.x stream now (for anyone still watching the issue). Please check the updated docs and change log.

from vue-auth.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.