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

Confused about sending authentication headers for token validation to API. #145

Closed
91sticker opened this issue Feb 6, 2017 · 18 comments
Closed

Comments

@91sticker
Copy link

91sticker commented Feb 6, 2017

Hello, I have a setup in which I have a rails 4 API having the gem devise_token_auth and hosted as a separate application so I have also rack-cors configured to handle cross origin requests. Using angular2-token on my front end Angular 2 applicaiton I have been able to successfully sign up and sign in as well as sign out users via my API.
The issue however, which I have encountered occurs only when the user is signed in and upon refreshing the browser I get this error in the rails API console as well as in the browser, checked in firefox as well as chrome.

Started GET "/api/v1/auth/validate_token" for 127.0.0.1 at 2017-02-06 17:42:49 +0500
Processing by DeviseTokenAuth::TokenValidationsController#validate_token as JSON

followed by:

SELECT  "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT 1  [["uid", "[email protected]"]]
Completed 401 Unauthorized in 76ms (Views: 0.2ms | ActiveRecord: 0.3ms)

My initial assumption during the configuration of this package in my Angular2 app was that it will implicitly include authentication headers in each request. However after repeatedly going through the gem's documentation I also added the headers myself when I initialize the token service in my app.component.ts file.

this._tokenService.init({
     apiPath: API_PATH,
      globalOptions: {
        headers: {
          'Content-Type':  'application/json',
          'Accept':  'application/json',
          "access_token_name": localStorage.getItem('accessToken'),
          "client_name": localStorage.getItem('client'),
          "uid_name": localStorage.getItem('uid')
        }
      }
 });

Even after that the response hasn't changed to the request and I was unable to receive these headers on the server end as well.

However after hours of inspection an idea finally came to me which was to inspect the headers m getting on the server and when I used ruby's request.header.inspect on my server end application I get the following output with the information required for validation of the token but it seems that the name of the keys of these header values are different form what the devise_token_auth expects to validate token (I went through the source of the devise_auth_token gem here.

"HTTP_ACCESS_TOKEN_NAME"=>"xxxxxxxxxxxxxxxxxx", "HTTP_EXPIRY"=>"xxxxxxxxxxxxxxxxxx", "HTTP_UID"=>"[email protected]", "HTTP_CLIENT_NAME"=>"xxxxxxxxxxxxxxxxxx", "HTTP_TOKEN_TYPE"=>"Bearer"

What I believe is the user is not being set by the devise_token_auth gem based on the headers that are being passed.

After repeatedly going through the documentation of Angular2-token as well as devise_token_auth gem I am confused whether or not to manually add headers for authentication because I believe they are being passed already but with different keys.
I would just like to know if that is the case I am experiencing its been almost a full day and I cannot figure out a way to pin point the reason behind the 401 response.

Thanks a lot.

@neroniaky
Copy link
Owner

Hey @91sticker thanks a lot for your detailed description.

To answer your question about the headers. Angular2-token includes the following headers on each request, which match the standard configuration for the devise_token_auth gem.

'access-token'
'client'
'expiry'
'token-type'
'uid'

According to your description, this seems to work fine. I think the error you're encountering is connected to the after reload .init(). Let me explain how angular2-token handles reloads:

  1. After a reload your entire angular-package is runs again. That means that .init() gets called again.
  2. On .init() angular-token tries to load auth data from the url (for password reset) and from the local storage.
  3. If auth data are found it runs validateToken() to check if the auth data are correct or outdated.

Step 3 seems to fail. Now we need to figure out why its failing. Could you record the get-request after reload (with for example postman or the chrome developer tools) and post which headers are included? Thanks!

@91sticker
Copy link
Author

91sticker commented Feb 6, 2017

Thank u so much for the response @neroniaky , using postman I have sent a GET request to the endpoint(
"http://localhost:3000/api/v1/auth/validate_token ") with all the headers u mentioned set.

The response I downloaded was as follows:
{"success":false,"errors":["Invalid login credentials"]}

Is there any more information that can be of help to pin point the cause, I am happy to provide.

Its a bit confusing because it allows me to login and logout but when I try and check the validation it returns 401 status nor its setting any devise helpers on the server site e.g. current_user, user_signed_in? etc.

@neroniaky
Copy link
Owner

Did you check here?

@91sticker
Copy link
Author

91sticker commented Feb 7, 2017

Yes sir I did in fact, in my set up I had rack-cors integrated on my back end.
Here are the configurations for that in my application.rb


config.middleware.use Rack::Cors do
      allow do
        origins '*'
        resource '/cors',
          :headers => :any,
          :methods => [:post],
          :credentials => true,
          :max_age => 0
        resource '*',
          :headers => :any,
          :expose  => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
          :methods => [:get, :post, :options, :delete, :put]
      end
    end

I have also tried with setting methods to any as well but that doesn't work as well, occasionally the rails api also throws a cookie overflow exception. And upon a little online research I came to know that can occur because of the cookie information being too big however my auth credentials are residing in the localstorage and not the cookies, could this be the cause?

@geoandri
Copy link

geoandri commented Feb 7, 2017

@91sticker Have you added the concern in the base controller as noted here? This absence of the concern would explain user not been set by token.

@91sticker
Copy link
Author

91sticker commented Feb 7, 2017

@geoandri thanks for the response sir, I have that as well in my application_controller.
I also made curl requests to the server

curl -i -H 'Content-Type: application/json' -X GET http://localhost:3000/api/v1/auth/validate_token -d '{"access-token": "*************", "client": "*************", "expiry": "*************", "token-type": "Bearer", "uid": "[email protected]", "provider": "email" }'

returning the similar response


HTTP/1.1 401 Unauthorized 
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Cache-Control: no-cache
X-Request-Id: e91ef9e1-75f3-445b-b151-c151c73c31e5
X-Runtime: 0.129093
Vary: Origin
X-Rack-Cors: preflight-hit; no-origin
Server: WEBrick/1.3.1 (Ruby/2.2.2/2015-04-13)
Date: Tue, 07 Feb 2017 09:02:30 GMT
Content-Length: 56
Connection: Keep-Alive

Which confuses me whether the server is getting the right headers, in the right format or whether it has something to do with the rack-cors gem's configurations.

@neroniaky
Copy link
Owner

I had a similar problem a while back (didn't happen with Rails 5 though). As far as I can remember it was about the capitalization of the headers. So Rails would show the headers as 'Token-Type', instead of 'token-type', which would be blocked by the CORS-Config (I think ?).

So what would be good if you could print out what rails is actually receiving in your controller. Something like this:

puts request.env["access-token"]

@91sticker
Copy link
Author

91sticker commented Feb 7, 2017

When I run the puts request.env["access-token"] it returns blank.
However if I inspect request.headers.inspect

I find these headers in a long list of headers but with different keys as I have shared above as well.

"HTTP_ACCESS_TOKEN_NAME"=>"xxxxxxxxxxxxxxxxxx", "HTTP_EXPIRY"=>"xxxxxxxxxxxxxxxxxx", "HTTP_UID"=>"[email protected]", "HTTP_CLIENT_NAME"=>"xxxxxxxxxxxxxxxxxx", "HTTP_TOKEN_TYPE"=>"Bearer"
Another thing I would like to add on the client end I get true as a result of running
this._tokenService.userSignedIn()

but 401 when it hits validate_token endpoint.

@neroniaky
Copy link
Owner

@91sticker you are right. If you inspect the env variable it adds "HTTP_" converts minus to underscore and capitalizes all characters. I just tried

request.env["HTTP_ACCESS_TOKEN"]

and it returns the access token.

@91sticker
Copy link
Author

91sticker commented Feb 7, 2017

@neroniaky YES!! I can see the access token in the console now.
Now I have 2 questions.

1- Is it the default behavior or did I do something wrong with the configurations
2- How would u suggest I fix it, whether on the server end or the client end?

I have tried this on my end with no luck inside devise_token_auth.rb.


config.headers_names = {:'access-token' => 'HTTP_ACCESS_TOKEN',
                         :'client' => 'HTTP_CLIENT',
                         :'expiry' => 'HTTP_EXPIRY',
                         :'uid' => 'HTTP_UID',
                         :'token-type' => 'HTTP_TOKEN_TYPE' }

Doing this also made it impossible for the user to login successfully.

Next I tried making changes to these headers on the client end and inside the init method in my app.component.ts I added these global options but didnt have any luck as well :

globalOptions: {
       headers: {
         'content-type':  'application/json',
         'accept':  'application/json',
         'access-token': localStorage.getItem('accessToken'),
         'client': localStorage.getItem('client'),
         'uid': localStorage.getItem('uid'),
         'expiry': localStorage.getItem('expiry'),
         'token-type': 'Bearer'
       }
     }

In fact I am getting the same old names in the headers with HTTP prefix and all capitalized. Seems like these headers are not being passed or I am not configuring these options right.
Does it have anything to do with the user's confirmation fields being blank in the database?

@91sticker
Copy link
Author

Here is a link to the response to the curl request I sent along with the headers.

@91sticker
Copy link
Author

After a lot of googling and going through issues repeatedly, in particular this I realized I had rails-api gem create my application.
I wasn't sure if it was actually causing the issue or not but for now after a few recommendations I have upgraded to rails 5 created a new API application using --api default option and upon configuring devise_token_auth and rack-cors gems the set up seems to work fine.
Thanks.

@neroniaky
Copy link
Owner

@91sticker thanks a lot for your work.

I can't see anything wrong with your implementation either, so upgrading to Rails 5.0 would have been my next suggestion too. At least we now know that Rails 4 with the api gem doesn't work. Gonna put that in the README.

@91sticker
Copy link
Author

No problem, Sir!

@VadymBoguslavsky
Copy link

I`m not using api gem and still got the same problem. Rails >5

@gustavogsimas
Copy link

@VadymBoguslavsky Did you find a fix?

@VadymBoguslavsky
Copy link

VadymBoguslavsky commented Mar 27, 2018 via email

@gustavogsimas
Copy link

gustavogsimas commented Mar 27, 2018

Ok, I managed to make it work finally. My problem was that the front-end was not passing the headers to the backend, so I made an interceptor based on this.

import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpHeaders
} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';

export class AppInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Clone the request to add the new header
    const headers = new HttpHeaders({ 
      'client': localStorage.getItem('client'), 
      'access-token': localStorage.getItem('accessToken'),
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'uid': localStorage.getItem('uid'),
      'token-type': localStorage.getItem('tokenType') 
    });

    const cloneReq = req.clone({headers});
    // Pass the cloned request instead of the original request to the next handle
    return next.handle(cloneReq);
  }
 }

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

No branches or pull requests

5 participants