auth0 / angular2-jwt

Helper library for handling JWTs in Angular apps
MIT License
2.63k stars 485 forks source link

Feature Request: Refresh Token Functionality #172

Closed ahmadqarshi closed 8 years ago

ahmadqarshi commented 8 years ago

I've used refresh token functionality in Angular 1.x using angular-jwt. However, I don't see this feature available in angular2-jwt.

Is there any plan to implement it for Angular2.

chenkie commented 8 years ago

It won't be added to this library but there might be a module released which handles it. If you'd like to include token refreshing for a mobile app in your own service, take a look at this example for Ionic 2.

ghost commented 8 years ago

@chenkie do you have any tutorial on how to implement the refresh token link you just gave?

ahmadqarshi commented 8 years ago

@kelvinrolex The example code is very well written and easy to understand. I've implemented it using exactly the same technique used in the example link provided by @chenkie . Just look at the source code in the example and if you need any help please let me know.

ghost commented 8 years ago

Ok, but pls can you just at least post a line of code on how you implemented it, I can continue from there. Thanks.

ahmadqarshi commented 8 years ago

Below is my modified version example code. There are two functions scheduleRefresh and startupTokenRefresh.

In your login function when you are successfully logged-in, call the scheduleRefresh function. Inside your ngOnInit of AppComponent check if your are already authenticated and the token is valid, if yes, then call startupTokenRefresh.

`

@Injectable()
export class TokenManagementService {

    jwtHelper: JwtHelper = new JwtHelper();
    refreshSubscription: any;

    constructor(private authHttp: AuthHttp, private http: Http) { }

    getToken() {
        return sessionStorage.getItem('id_token');
    }

    private getNewJwt() {
        let refreshTokenId = localStorage.getItem('refresh-token-id');
        var header = new Headers();
        header.append('Content-Type', 'application/x-www-form-urlencoded');
        let body = 'grant_type=refresh_token&refresh_token=' + refreshTokenId;
        return this.http.post('http://localhost:1212121/token', body, { headers: header })
            .map((res) => this.processTokenResponse(res))
            .catch(HttpExceptions.handleErrors);
    }

    get isAuthenticated(): boolean {
        if (tokenNotExpired(undefined, sessionStorage.getItem('id_token'))) {
            return true;
        } else {
            return false;
        }
    }

    public scheduleRefresh() {        
        // If the user is authenticated, use the token stream
        // provided by angular2-jwt and flatMap the token
        let source = this.authHttp.tokenStream.flatMap(
            token => {
                // The delay to generate in this case is the difference
                // between the expiry time and the issued at time                
                let jwtExp = this.jwtHelper.decodeToken(token).exp;
                let iat = new Date(sessionStorage.getItem('.issued')).getTime()/1000;                                
                let refreshTokenThreshold = 10; //seconds
                let delay = ((jwtExp - iat) - refreshTokenThreshold) * 1000;

                return Observable.interval(delay);
            });

        this.refreshSubscription = source.subscribe(() => {
            this.getNewJwt().subscribe((res) =>  console.log('-> Refreshed...'),(error) => console.log('Refresh error: '+ JSON.stringify(error)))
        });
    }

    public startupTokenRefresh() {
        // If the user is authenticated, use the token stream
        // provided by angular2-jwt and flatMap the token
        if (this.isAuthenticated) {
            let source = this.authHttp.tokenStream.flatMap(
                token => {
                    // Get the expiry time to generate
                    // a delay in milliseconds
                    let now: number = new Date().valueOf()/1000;
                    let jwtExp: number = this.jwtHelper.decodeToken(token).exp;
                    let iat = new Date(sessionStorage.getItem('.issued')).getTime()/1000;

                    let refreshTokenThreshold = 10; //seconds

                    let delay: number = jwtExp - now ;
                    let totalLife: number = (jwtExp - iat); 
                    (delay < refreshTokenThreshold ) ? delay = 1 : delay = delay - refreshTokenThreshold;                    

                    // Use the delay in a timer to
                    // run the refresh at the proper time
                    return Observable.timer(delay*1000);
                });

            // Once the delay time from above is
            // reached, get a new JWT and schedule
            // additional refreshes
            source.subscribe(() => {
                this.getNewJwt().subscribe(
                    (res) => {
                    console.log('-> Refreshed on startup')
                    this.scheduleRefresh();
                    },
                    (error) => console.log('-> Refresh error:'+ JSON.stringify(error)))

            });
        }
    }

    public unscheduleRefresh() {
        // Unsubscribe fromt the refresh
        if (this.refreshSubscription) {
            this.refreshSubscription.unsubscribe();
        }
    }

    public persistTokenInformation(token: string, issuedTime: string,  tokenExpiry: string, refreshTokenId: string){
        sessionStorage.setItem('id_token', token);
        sessionStorage.setItem('.issued', issuedTime);
        sessionStorage.setItem('.expires', tokenExpiry);
        localStorage.setItem('refresh-token-id', refreshTokenId);
    }

    private processTokenResponse(res: Response) {
        this.persistTokenInformation(res.json().access_token, res.json()['.issued'], res.json()['.expires'], res.json().refresh_token);        
        return Observable.of(true);
    }

}

`

NOTE:- I am generating the access_token in ASP.NET Web API. But somehow the iat is not populated. I've already posted a question on SO about it. In the mean time for iat I am using .issued which I get along with access_token.

ghost commented 8 years ago

Ok thanks...

joel00 commented 7 years ago

@ahmadqarshi why do you need both startupTokenRefresh() with an Observable.timer and scheduleRefresh() with Observable.interval???

chenkie commented 7 years ago

@ahmadqarshi scheduleRefresh sets an amount of time until the token needs to be refreshed for the case when the user first authenticates. When the user refreshes the page or closes it and later returns, the amount of time until refresh needs to be calculated as well, but this is a different calculation because it needs to be taken from the time that the app first starts up, hence the startupTokenRefresh method.

joel00 commented 7 years ago

@chenkie thanks for the explanation; just to be sure, if I refresh the page, scheduleRefresh() timer is useless and the App takes a new countdown calculation with startupTokenRefresh() and when it's completed retakes a "full" countdown calling the scheduleRefresh() method, right?

abelkbil commented 7 years ago

@chenkie please see http://www.isavecost.in/test/. Token is set in localStorage. On browser it works and in mobile it doesn't. Mobile have localStorage enabled. Tried with 0.2.0 version

I stored and consoled the data from localStorage in mobile. It prints; but No JWT present or has expired comes only on mobile.

Even though this thread is not relevant to my issue, Since you are experience with mobile made me comment here. Thanks

escardin commented 7 years ago

Please don't post unrelated comments, especially on closed issues.

n0490b commented 7 years ago

@ahmadqarshi Where in your code do you use unscheduleRefresh()?

ahmadqarshi commented 7 years ago

I call unscheduleRefresh() on logout.

Sent from my iPhone

On 27 Jun 2017, at 17:12, n0490b notifications@github.com<mailto:notifications@github.com> wrote:

@ahmadqarshihttps://github.com/ahmadqarshi Where in your code do you use unscheduleRefresh()?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/auth0/angular2-jwt/issues/172#issuecomment-311407536, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AFWO3N5T1sFrb65hRBBmUZeB7j2JjTf_ks5sISnUgaJpZM4KBf9-.

n0490b commented 7 years ago

@ahmadqarshi Did you fix the problem with iat not being populated?