Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tokens seem to have a very short lifespan #73

Closed
dbesserman opened this issue Mar 28, 2020 · 12 comments
Closed

Tokens seem to have a very short lifespan #73

dbesserman opened this issue Mar 28, 2020 · 12 comments
Labels
question Further information is requested

Comments

@dbesserman
Copy link

I have a react app and I use graphql_devise on a rails backend to authenticate.
Authentication works fine except that the token seem to last about a minute.
Is it the expected behaviour?

Here's how I use graphql_devise:

I use an apollo client for authentication and another apollo client for fetching the resources. Fetching resources requires authentication.

First I authenticate by using the userLogin mutation. This allows me to get credentials that I
put in localStorage.

mutation USER_LOGIN($email: String!, $password: String!) {
  userLogin(email: $email, password: $password) {
    credentials {
      accessToken
      uid
      tokenType
      client
      expiry
    }
  }
}

The client that fetches the resources digs into localStorage for each request, takes the credentials, and adds them to the headers of the request.

import {ApolloClient} from 'apollo-client';
import {createHttpLink} from 'apollo-link-http';
import {setContext} from 'apollo-link-context';
import {InMemoryCache} from 'apollo-cache-inmemory';

const httpLink = createHttpLink({
  uri: 'http://localhost:3344/graphql',
});

const authLink = setContext((_, {headers}) => {
  // get the authentication token from local storage if it exists
  const accessToken = localStorage.getItem('accessToken');
  const uid = localStorage.getItem('uid');
  const tokenType = localStorage.getItem('tokenType');
  const client = localStorage.getItem('client');
  const expiry = localStorage.getItem('expiry');

  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      access_token: accessToken,
      uid: uid,
      token_type: tokenType,
      client: client,
      expiry: expiry
    }
  }
});

export const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

Everything works fine except that after about a minute all my requests end in a 401 error. I need the authenticate again. Am I using graphql_devise wrong ?

Also, something worth mentioning is that I removed devise's trackable module. It required some columns to be on the users table and those columns weren't present in the generated migration file.

app/model/user.rb

# frozen_string_literal: true

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :trackable, :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  include DeviseTokenAuth::Concerns::User
end

I reimplement the trackable module to see if that was the issue. It seems it wasn't. I get the same behavior.

I copied those lines from the migration files of another project.

t.integer  :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string   :current_sign_in_ip
t.string   :last_sign_in_ip

Another thing is that for now the client app and the server app run on 2 different ports of localhost. I use the rack_cors to authorize cors in my development environment. But I don't really think that's the issue. Otherwise I probably wouldn't be able to authenticate in the first place.

config/environments/development.rb

  config.middleware.insert_before 0, Rack::Cors do
    allow do
      origins '*'
      resource '*', headers: :any, methods: [:get, :post, :options]
    end
  end
@00dav00
Copy link
Contributor

00dav00 commented Mar 29, 2020

Hi @dbesserman, the token life span is controlled by the devise-token-auth token_lifespan config set in the gem initializer. You can check the docs here.

Please let me know if this solves your problem.

@00dav00 00dav00 added the question Further information is requested label Mar 29, 2020
@dbesserman
Copy link
Author

dbesserman commented Mar 29, 2020

Hi @00dav00
Thanks for helping me. It doesn't work though. I went into config/initializers/devise_token_auth.rb and set the token_lifespan to 2 weeks. Then I restarted the rails server. I still get the same behavior.

localStorage in the browser and User.last.tokens in rails console don't seem to change after I lose the authentication

After a bit of experimentation I realized that that I do not lose the authentication after a certain amount of time. I lose the authentication when I click on another tab of my browser. If I stay on the same tab and I keep calling the API I can use the application for 5 minutes without losing the authentication. I'm using chrome, but I get the same behaviour wether I use private navigation with chrome or firefox.

@mcelicalderon
Copy link
Member

Hey @dbesserman, I think your problem might be related to this configuration change_headers_on_each_request that you can set to false on the same file @00dav00 pointed out. DTA invalidates the old and generates a new one in each request you make.

So just do config.change_headers_on_each_request = false in config/initializers/devise_token_auth.rb. BUt you still need to set an appropriate token lifespan

@mcelicalderon
Copy link
Member

About the trackable module, we just run generator for the underlying gems that this one uses. So the generated migration at least for now, depends on Devise. Here's a link to add the trackable module https://github.com/heartcombo/devise/wiki/How-To:-Add-:trackable-to-Users.

We'll try to make the readme file clearer about how to configure these gems in a better way.

@mcelicalderon
Copy link
Member

Also, if you can still make requests in one tab, that only means that the problem you are having is on the react app. Yo will have to log back in for the life_span setting to be reflected as the expiration time is saved on the database when you login.

@dbesserman
Copy link
Author

Hello @mcelicalderon. Thanks for your input.

The part about the client causing the issue seems a bit odd to me. Correct me if I'm wrong but the way I understand it, the backend identifies the client through credentials that are located in the headers of the request.

Here are the headers of a successful request:

success

Here are the headers for an unauthorized request:

failure

Credentials seem to be the same. Where else can I look to see why both requests get different responses ?

@mcelicalderon
Copy link
Member

Correct, that's how the backend identifies the user. And I'm not sure why that might be happening to you but for me still seems related to how the client is making the request, so to discard that the client is the problem you need to test the requests outside of your react app and report here if the problem persists. I would recommend using Insomnia or Postman which have a friendly interface for graphql requests

@dbesserman
Copy link
Author

I have the same issue with insomnia. I authenticated, copied the credentials in another query.
The first query worked. I did the same query again (by pressing Cmd + r) and got a 401.

@mcelicalderon
Copy link
Member

mcelicalderon commented Mar 30, 2020

ok thank you for testing that. Now, we need to replicate the problem. Is there a way you can upload the code to a repository I can look into? If not, please create an empty rails project where you can replicate the same you are using right now, specially make sure you are using the same versions of gems. Maybe then we can identify the issue.
Also make sure you changed this

@dbesserman
Copy link
Author

dbesserman commented Mar 30, 2020

I'm sorry. I somehow missed this.
Thanks a lot @mcelicalderon !

On a side note. I'm curious about how the client can keep up with headers changing on each request. I wonder how that might work when the client sends asynchronous request. Do you have by any chance a resource in mind on the subject?

Thanks again.

@mcelicalderon
Copy link
Member

This gem is a GQL interface on top of the Devise Token Auth gem so to really use our gem to it's full potential I would recommend reading their docs (something I'll try to make clearer on our readme file). Here is the explanation for each option you can set on the initializer. So for multiple async requests there is another param batch_request_buffer_throttle which you have to set to a time range like 5.seconds, and this will allow the same credentials to be valid for as many requests as you want during that time period. Also the author has an angular client to handle that here and another one here but I don't think you will be able to use those with Apollo, so you might have to manage that yourself if you want to implement it.

@aarona
Copy link
Contributor

aarona commented Apr 7, 2020

@dbesserman an off topic question/request. How are you supporting to GraphQL end points in your React app? Do you have a link to an article or even possibly some sample code? I haven't been able to solve this so I've been using a restful interface for authentication because I can't seem to figure out how to get Apollo to support to GQL clients.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants