leonardobazico / angular2-jwt-refresh

Refresh token support for angular2-jwt
MIT License
40 stars 15 forks source link

Problems getting this working #1

Open shopro opened 7 years ago

shopro commented 7 years ago

I have based on the example configuration tried to implement this to my project but it doesn't seem to be working. No matter what the "beforeSeconds" is set it doesn't fire at all.

What is the proper way to have this refresh the token? The API is running Django REST framework and the command to refresh token is the following and the token expiry time is 300seconds.

curl -X POST -H "Content-Type: application/json" -d '{"token":"<EXISTING_TOKEN>"}' http://localhost:8000/api-token-refresh/

Thank you in advance.

leonardobazico commented 7 years ago

Do you has installed peer dependencies?

Check if you are using an JwtHttp object to do your requests.

import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { JwtHttp } from 'angular2-jwt-refresh';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';

import { AppConfig } from '../AppConfig';

@Injectable()
export class DataService {
  private baseUrl: string = AppConfig.baseUrl + '/data';

  constructor(private jwtHttp: JwtHttp) { }

  getData(id: number): Observable<any> {
    const url = this.baseUrl + '/' + id;

    return this.jwtHttp
      .get(url)
      .map((res: Response) => {
        return res.json();
      });
  }

  saveData(data: any): Observable<string> {
    const url = this.baseUrl + '/' (data['id'] ? data['id'] : '');

    return this.jwtHttp
      .post(url, data)
      .map((res: Response) => {
        return res.json();
      });
  }
}
leonardobazico commented 7 years ago

@shopro can you check if your token has exp field? You can check this on https://jwt.io

For the token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0ODc2Nzc5NzV9.-B5WmcOQa8yCdT7nc4Lh-Z-m2to2AFB4Y0p-4WLYDXA I get this data:

{
  "exp": 1487677975
}

If you don't provide exp has no way to verify token validity

shopro commented 7 years ago

The token is like this: email : "" exp : 1487712704 orig_iat : 1487658704 user_id : 2 username : "username"

I am not sure what you meant by using JwtHttp. I implemented it like this. I am new to Angular2 so I don't know if this is correct or not. The initial token is retrieved using AuthHttp from Angular2-jwt.

@NgModule({
...
  providers: [{
    provide: JwtHttp,
    useFactory: getJwtHttp,
    deps: [ Http ]
  }],
...
})
export function getJwtHttp(http: Http, options: RequestOptions) {
  console.log("running jwt")
  let jwtOptions = {
    endPoint: 'https://api.address/api-token-refresh/',
    // optional
    payload: { type: 'refresh' },
    beforeSeconds: 60, // refresh tokeSn before 10 min
    tokenName: 'refresh_token',
    refreshTokenGetter: (() => localStorage.getItem('refresh_token')),
    tokenSetter: ((res: Response): boolean | Promise<void> => {
      res = res.json();

      if (!res['id_token'] || !res['refresh_token']) {
        localStorage.removeItem('id_token');
        localStorage.removeItem('refresh_token');

        return false;
      }

      localStorage.setItem('id_token', res['id_token']);
      localStorage.setItem('refresh_token', res['refresh_token']);

      return true;
    })
  };
  let authConfig = new AuthConfig({
    noJwtError: true,
    globalHeaders: [{'Accept': 'application/json'}],
    tokenGetter: (() => localStorage.getItem('id_token')),
  });

  return new JwtHttp(
    new JwtConfigService(jwtOptions, authConfig),
    http,
    options
  );
}
leonardobazico commented 7 years ago

@shopro how do you do http requests? Are you using Http from @angular/http?

shopro commented 7 years ago

For the token part I'm using the angular2-jwt library.

Basic Configuration

Create a new auth.module.ts file with the following code:

import { NgModule } from '@angular/core';
import { Http, RequestOptions } from '@angular/http';
import { AuthHttp, AuthConfig } from 'angular2-jwt';

function authHttpServiceFactory(http: Http, options: RequestOptions) {
  return new AuthHttp(new AuthConfig(), http, options);
}

@NgModule({
  providers: [
    {
      provide: AuthHttp,
      useFactory: authHttpServiceFactory,
      deps: [Http, RequestOptions]
    }
  ]
})
export class AuthModule {}

We added a factory function to use as a provider for AuthHttp. This will allow you to configure angular2-jwt in the AuthConfig instance later on.

Sending Authenticated Requests

If you wish to only send a JWT on a specific HTTP request, you can use the AuthHttp class. This class is a wrapper for Angular 2's Http and thus supports all the same HTTP methods.

import { AuthHttp } from 'angular2-jwt';
// ...
class App {

  thing: string;

  constructor(public authHttp: AuthHttp) {}

  getThing() {
    this.authHttp.get('http://example.com/api/thing')
      .subscribe(
        data => this.thing = data,
        err => console.log(err),
        () => console.log('Request Complete')
      );
  }
}
leonardobazico commented 7 years ago

@shopro You can replace AuthHttp to JwtHttp.

JwtHttp extends AuthHttp, so JwtHttp has same utility of AuthHttp and adds refresh token funcionability.

You don't need configure angular2-jwt anymore too, only angular2-jwt-refresh.

shopro commented 7 years ago

Thanks for the info, I think it's simply my lag of experience why I can't get this working. Here is my exact setup after I tried to implement based on your tips. credentials variable is being provided by the login form. Maybe my problem is that once logged in it doesn't seem to run the refresh part?

auth.service.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { JwtHelper, tokenNotExpired } from 'angular2-jwt';
import { Router } from '@angular/router';
import { JwtConfigService, JwtHttp } from 'angular2-jwt-refresh';

@Injectable()
export class AuthService {

  constructor(private router: Router, private jwthttp: JwtHttp) {}

  jwtHelper: JwtHelper = new JwtHelper();

  login(credentials) {
    this.jwthttp.post('https://api.address/api-token-auth/', credentials)
      .map(res => res.json())
      .subscribe(
        // We're assuming the response will be an object
        // with the JWT on an id_token key
        data => localStorage.setItem('id_token', data.token),
        // data => localStorage.setItem('id_token', data.id_token),
        error => console.log(error)
      );
    this.router.navigateByUrl('');
  }

app.module.ts

import { BrowserModule, Title } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule, Http, RequestOptions, Response } from '@angular/http';

import { LoginComponent } from './login/login.component';
import {AuthService } from './login/auth.service';
import { AuthGuard } from './login/auth-guard.service';
import { AuthHttp, provideAuth, AuthConfig } from 'angular2-jwt';
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';

export function authHttpServiceFactory(http: Http, options: RequestOptions) {
  return new AuthHttp( new AuthConfig({}), http, options);
}

import { JwtConfigService, JwtHttp } from 'angular2-jwt-refresh';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRoutingModule,
    NgbModule.forRoot(),
  ],
  providers: [ AuthService, AuthGuard, AuthHttp,
    {
      provide: AuthHttp,
      useFactory: authHttpServiceFactory,
      deps: [ Http, RequestOptions ]
    },
    {
      provide: JwtHttp,
      useFactory: getJwtHttp,
      deps: [ Http ]
    }
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }

export function getJwtHttp(http: Http, options: RequestOptions) {
  let jwtOptions = {
    endPoint: 'https://api.address/api-token-refresh/',
    // optional
    payload: { type: 'refresh' },
    beforeSeconds: 60, // refresh tokeSn before 10 min
    tokenName: 'refresh_token',
    refreshTokenGetter: (() => localStorage.getItem('refresh_token')),
    tokenSetter: ((res: Response): boolean | Promise<void> => {
      res = res.json();

      if (!res['id_token'] || !res['refresh_token']) {
        localStorage.removeItem('id_token');
        localStorage.removeItem('refresh_token');

        return false;
      }

      localStorage.setItem('id_token', res['id_token']);
      localStorage.setItem('refresh_token', res['refresh_token']);

      return true;
    })
  };
  let authConfig = new AuthConfig({
    noJwtError: true,
    globalHeaders: [{'Accept': 'application/json'}],
    tokenGetter: (() => localStorage.getItem('id_token')),
  });

  return new JwtHttp(
    new JwtConfigService(jwtOptions, authConfig),
    http,
    options
  );
}
leonardobazico commented 7 years ago

@shopro after login you only set id_token, you forget to set refresh_token.

I'm developing a auth.service for Ionic2 app:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Storage } from '@ionic/storage';
import { JwtHttp } from 'angular2-jwt-refresh';

import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';

import { AppConfig } from '../../app/app.config';

@Injectable()
export class AuthService {

  private baseUrl = AppConfig.url;
  static storage: Storage = new Storage();

  constructor(public jwtHttp: JwtHttp,
              public http: Http) { }

  static tokenSetter(accessToken, refreshToken): Promise<void> {
    return new Promise<void>((resolve, reject) => {
      let idTokenFinish = false;
      let refreshFinish = false;

      AuthService.storage
        .set('accessToken', accessToken)
        .then(() => {
          idTokenFinish = true;

          if (refreshFinish) {
            resolve();
          }
        })
        .catch(reject);

      AuthService.storage
        .set('refreshToken', refreshToken)
        .then(() => {
          refreshFinish = true;

          if (idTokenFinish) {
            resolve();
          }
        })
        .catch(reject);
    });
  }

  login(user: any) {
    return this.http
      .post(this.baseUrl, user, AppConfig.requestOptions)
      .map((res: Response) => {
        let tokens: any = res.json();

        if (!tokens.accessToken || !tokens.refreshToken) {
          return null;
        }

        return tokens;
      })
      .concatMap(tokens => {
        if (!tokens) {
          return Observable.throw('Não foi possível realizar o login!');
        }

        return Observable.fromPromise(AuthService.tokenSetter(tokens.accessToken, tokens.refreshToken));
      })
      .catch((res: string | Response) => {
        let message = 'Erro desconhecido, favor tentar novamente.';

        if (res instanceof Response) {
          res = res.json();
        } else {
          message = res;
        }

        if (res === 'NOT_FOUND') {
          message = 'Email e/ou senha inválido(s)!';
        }

        return Observable.throw(message);
      });
  }

  logout() {
    return this.jwtHttp
      .post(this.baseUrl, { action: 'logout' }, AppConfig.requestOptions)
      .mergeMap(() => {
        return Observable.fromPromise(AuthService.storage.clear());
      });
  }

  loggedIn() {
    return Observable.fromPromise(AuthService.storage.get('id_token'))
  }

  register(user) {
    return this.http
      .post(this.baseUrl, user, AppConfig.requestOptions)
      .map(res => res.json())
      .catch((res: Response) => {
        let message = null;

        if (res.json() === 'DUPLICATE') {
          message = 'Email já cadastrado!';
        }

        return Observable.throw(message || 'Erro desconhecido, favor tentar novamente.');
      });
  }
}
shopro commented 7 years ago

Thank you for the example, I however am still able to figure out how exactly to get it refreshed before it expires. I was able to make it so that both the 'id_token' and 'refresh_token' are being created on login but neither is being refreshed.

Where and how do I "run" it so it refreshes?

leonardobazico commented 7 years ago

@shopro you're using jwthttp for all requests?

JwtHttp checks if token will expire soon at every requests.

Every get, post, put or delete will call the request method of JwtHttp.

You can see the code lines 50:90 for details

shopro commented 7 years ago

Yes, I'm using jwthttp for the login http request, this is the only http request I currently have.

I'm currently only using http for authentication.

leonardobazico commented 7 years ago

So, the token only will update before a new request. If you want schedule the refreshes, you need other strategy.

angular2-jwt-refresh uses a strategy like request interceptors in AngularJS

leonardobazico commented 7 years ago

@shopro One more thing! For requests that's doesn't have a token in headers you can make this with Http.

So for login, you don't need of angula2-jwt-refresh, use this only for requests after authenticated

Ravibs139 commented 7 years ago

Posted in new thread

cantwait commented 6 years ago

Hi @leonardobazico ,

First of all thanks for sharing this great plugin with the community, I am developing an Ionic app but I am having a hard time with the localstorage, this is my use case:

I have the setup suggested on the README, and on the other hand I have a provider (service) in which I am setting my tokens upon login, something such as:

localStorage.setItem('token',token) localstorage.setItem('refresh_token', refresh_token) so forth...

But, the problem is, the first time jwt refresh is triggered my payload goes null, this is my payload:

{'expiredToken' : localstorage.getItem('token'), 'rToken' : localstorage.getItem('refresh_token')}

And the most weird, the next time I do the login process the sent over the previous values for token and refresh_token, would you please let me know what the heck is local storage doing? XD

Thanks mam

romicaiarca commented 6 years ago

Hi @cantwait, this happens because jwtOptions is ran once on instantiation and the value of localStorage is empty or have value of previews instance. The value is changed after a page refresh. Anyway, you don't need to put this in payload, because this refresh token is sent in header on Authentication field.

@leonardobazico, I have a big issue regarding refresh call, it looks like the calls are correctly made, but for some reason after 1-2 calls my call to the refresh token path from server I have a canceled request and after that my app goes down. Here are my setup:

export function getJwtHttp(http: Http, options: RequestOptions) {
  let jwtOptions = {
endPoint: 'http://*/refresh',
    payload: { type: 'refresh' },
    beforeSeconds: 60 * 3,
    tokenName: 'refresh_token',
    refreshTokenGetter: (() =>  localStorage.getItem('refresh_token')),
   tokenSetter: ((res: Response): boolean | Promise<void> => {
        res = res.json();

        if (!res['access_token'] || !res['refresh_token']) {
          localStorage.removeItem('token');
          localStorage.removeItem('refresh_token');

          return false;
        }

        localStorage.setItem('token', res['access_token']);
        localStorage.setItem('refresh_token', res['refresh_token']);

        return true;
    })
  };

Do you have any idea why this happens?