-
Notifications
You must be signed in to change notification settings - Fork 0
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
Conversation
There was a problem hiding this 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.'; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()){ |
There was a problem hiding this comment.
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();
?
- 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.
- 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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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? :)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 :).
ed396c7
to
4a9d9af
Compare
This commit also contains some minor changes to how being logged in is checked.
This PR also resolves issues when token is not valid and the backend rejects it - app clears the token and redirects to login page.