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

[DNCR-107] Add token refreshing #31

Merged
merged 3 commits into from
Oct 31, 2016
Merged

[DNCR-107] Add token refreshing #31

merged 3 commits into from
Oct 31, 2016

Conversation

megawebmaster
Copy link
Collaborator

This PR also resolves issues when token is not valid and the backend rejects it - app clears the token and redirects to login page.

Copy link
Owner

@tlegutko tlegutko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job, what's most imporant is that token refreshing seems to work (after expiration token nicely refreshed and I had no trouble using app).

I have some code-style comments, mostly around auth-service class, other are super-minor.

if (response.hasOwnProperty('error')) {
this.error = response.error;
} else {
this.error = 'Nieprawidłowy login lub hasło.';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't 'Nieprawidłowy login lub hasło.' just the case when auth is failed? Let's look at method at backend

      try
    {
      if(!$token = \JWTAuth::attempt($credentials))
      {
        return response()->json(['error' => \Lang::get('auth.failed')], 401);
      }
    }
    catch(JWTException $e)
    {
      return response()->json(['error' => \Lang::get('auth.could_not_create_token')], 500);
    }

    return response()->json(['token' => $token]);
(...)
  'failed' => 'Nieprawidłowy login lub hasło.',

So if it reaches this else condition if it means it's error not related to JWT and message should be something like Wystąpił nieoczekiwany błąd w komunikacji z serwerem.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True.

}
}
);
if (this.service.isLoggedIn()) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a priority for now, but I was wondering if moving this logic to some guard executed in canActivate would prevent homepage from blinking before redirect takes place. I think it should, If I'm not logged I'm cleanly redirected to homepage, so it should work the same the other way around.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it would work, but I will try to find more clever way ;)

courseCreated = this.courseCreatedSource.asObservable();
calendarItemsCreated = this.courseCreated.map(this.courseToCalendarItems);
recentlyClickedTime = this.recentlyClickedTimeSource.asObservable();
courseCreated: Observable<Course> = this.courseCreatedSource.asObservable();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can leave it, but any particular reason for a change? I thought TS infers the type during typed declaration.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I got some weird errors so I added it - does not cost us a thing and shows what are these types explicitly.

this.router.navigate(['/']);
public isLoggedIn(): boolean {
try {
if (!tokenNotExpired()){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, I need some explanation on this one. Why not just

public isLoggedIn(): boolean {
    return tokenNotExpired();

?

  1. router navigation in this method returning boolean is definitely unexpected. especially that i.e. in AuthGuard there is similar redirection once this method returns and I think it's preferable approach to navigate in component calling this method.
  2. what kind of exception do we expect? In code samples in tutorial for angular2-jwt they just return tokenNotExpired() (https://github.com/auth0-samples/auth0-angularjs2-systemjs-sample/search?p=1&q=tokenNotExpired&utf8=%E2%9C%93)

But I might be missing something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot of places where we use or will use isLoggedIn(). Moreover there is also this requirement for URL check (otherwise app hangs on homepage). But maybe you are right and I should move it up.

return;
}

let helper = new JwtHelper();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, took me a while, but I finally understood it. Why not just call this.jwtHelper.getTokenExpirationDate(token) and use Date / Moment for date operations? Getting barely documented exp field from token and then manual operations on milliseconds with some unit conversion along the way seems error-prone. I looked into end result and yes it looks like you didn't make a mistake, but cleaner code will allow us to modify it without fear in the future. And those time-unit operations could be a separate method taking token as argument (whole scheduleTokenRefreshing() could actually take token as parameter)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we need milliseconds for setTimeout() and Date operations in JS are horrible. The exp field is clearly described in JWT RFC so I don't think we need to explain it more, do you? :)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh cmon, date -> moment

var day = new Date(2011, 9, 16);
var dayWrapper = moment(day);

and moment seems to have nice manipulation operations. imo that'd make the code more readable, but I trust your judgement, it's up to you

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And please, tell me how this would be more readable:

let expiration = moment(helper.getTokenExpirationDate(token));
let now = moment();
let difference = expiration.diff(now);
let timeout = difference - 60000; // Subtract 1 minute to be sure token is still valid

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo sticking to moment / duration for all calculations makes it a bit better:

    let expiration = moment(new JwtHelper().getTokenExpirationDate(token));
    let timeout = moment.duration(expiration.diff(moment()))
      .subtract(1, 'minute') // to be sure token is still valid
      .asMilliseconds();


@Injectable()
export class AuthService {
private static TOKEN = 'id_token';
private static refreshTimeout: any;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a number according to https://nodejs.org/docs/v0.6.1/api/timers.html#setTimeout and I think it shouldn't be static - it's related to lifecycle of this service.

token nad known user could be readonly (https://basarat.gitbooks.io/typescript/content/docs/types/readonly.html I recently learned TS2 introduced constants, nice) and known user should be private

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not NodeJS :) I won't be so sure where it doesn't hurt us for it to be any. Please also see where it is cleared and as we have (and always will) have a single instance of AuthService. These statics are required to avoid dependency loop (AuthHttp => AuthService => AuthHttp).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I got confused because our PhpStorms says it returns a NodeJS.Timer when I checked. And it seems to be a common mistake mgechev/angular-seed#901. So I guess we're using https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout and as in linked issue, if we do
refreshTimeout**Id** = window.setTimeout(), then it returns number, so we can add proper type.

We generally use types, so let's stick to it - if this field has Id in it's name and has number type the code will be more clear.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I was unaware of this circular dependency, now that you mention it.. I think Angular should provide some nice way to make service a singleton (apart from defining it top-level in app and having it in all namespaces), but I didn't find a better way, so until I do I'm ok with static.. :P

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, why should we call it an ID as it does not matter to us at all, it's even internal thing of AuthService. As for circular dependency there is no way of resolving it without manual factories with setters.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well you have a point, it's just passed somewhere so it's not super important.

On the other hand in another place in this class, during calculations we name something timeout and it's a duration in milliseconds. So if it's something different (id of timer) and held as a class field and not just local variable then different name (and proper type, just to be consistent, especially when I got the wrong idea inspecting it because of some import / dependency bug) would improve code quality and readability.

But I doubt this class will be modified super-often and it is kind of internal, so if you still disagree I won't push any more, I won't argue about it any more here, we both have better things to do :)

$token = \JWTAuth::refresh();
}
catch(JWTException $e)
{
return response()->json(['error' => \Lang::get('auth.could_not_create_token')], 500);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Message to user is correct for both scenarios, but maybe error could be auth.token_error? seeing create in error with refresh made me go and check error msgs. if you think it's not important you can leave it as it is

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is message also used by JWT library, I don't want to change it in any way.

// TODO: Add notification about invalid response - logout
AuthService.clear();
}
if (response.status === 500) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this! till now we didn't log some 500 errors and this'll be helpful. and it's done just in one place, brilliant :).

@tlegutko tlegutko merged commit bc2c996 into develop Oct 31, 2016
@tlegutko tlegutko deleted the DNCR-107 branch October 31, 2016 13:23
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

Successfully merging this pull request may close these issues.

2 participants