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

Feat/Auth : refreshToken request automatically sent by NbAuthService if isAuthenticatedOrRefresh is called #649

Merged
merged 58 commits into from
Aug 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
cef7a1d
Added password grant_type option
alain-charles Jun 29, 2018
9f0dbab
Added password grant_type option implementation
alain-charles Jun 29, 2018
44d7d2e
Playground sample for oAuth2 password grant-type
alain-charles Jun 29, 2018
f57b737
Added route to oAuth2 password grant-type sample
alain-charles Jun 29, 2018
ed4628e
Addes angular-jwt for decoding jwt token (used in playground only)
alain-charles Jun 29, 2018
d7f01df
Added /api/auth/token endpoint (oAuth2 password grant-type playground)
alain-charles Jun 29, 2018
af52e88
Patched code for passing ci
alain-charles Jun 29, 2018
554f7dc
code optimization for passing ci
alain-charles Jun 29, 2018
fa0548f
Merge branch 'master' into master
nnixaa Jun 29, 2018
b72adae
Changes request by Dmitry (first review)
alain-charles Jun 29, 2018
733efc0
Generalized code in oauth2-strategy
alain-charles Jun 29, 2018
57fab69
Merge remote-tracking branch 'origin/master'
alain-charles Jun 29, 2018
c50df3a
Generalized code in oauth2-strategy
alain-charles Jun 29, 2018
dda5436
Added unit tests for oAuth-strategy password authentication
alain-charles Jul 2, 2018
ef0be25
Merge branch 'master' into master
alain-charles Jul 3, 2018
e180dc3
Merge branch 'master' into master
nnixaa Jul 4, 2018
6aa1fdb
Cleaned code according to nnixaa second review
alain-charles Jul 4, 2018
96c7344
Merge branch 'master' of https://github.com/alain-charles/nebular
alain-charles Jul 4, 2018
cf82c42
Removed package-lock.json from git repo
alain-charles Jul 4, 2018
dcff930
package-lock.json to revert
alain-charles Jul 4, 2018
315662f
reverted package-lock.json
alain-charles Jul 4, 2018
57a0230
Added new grant_type 'PASSWORD' in the block comment for it to be ins…
alain-charles Jul 4, 2018
d69d633
Merge remote-tracking branch 'upstream/master'
alain-charles Jul 5, 2018
1be8d91
Add the refreshtoken request management in NbJwtInterceptor and NbAut…
alain-charles Jul 13, 2018
4b67825
Merge branch 'master' of https://github.com/alain-charles/nebular
alain-charles Jul 19, 2018
deea3e2
Merge branch 'master' into temp
alain-charles Jul 19, 2018
acd0241
The token now contains ownerStrategyName, with is a back link to the …
alain-charles Jul 19, 2018
25250f0
feature/auth:
alain-charles Jul 19, 2018
c0fdd31
feature/auth:
alain-charles Jul 19, 2018
bd564b3
feature/auth:
alain-charles Jul 20, 2018
44ed61a
Merge branch 'master' into master
nnixaa Jul 20, 2018
9c16387
feature/auth
alain-charles Jul 24, 2018
2dd26c4
Merge remote-tracking branch 'origin/master'
alain-charles Jul 24, 2018
b284377
Merge branch 'master' into master
alain-charles Jul 24, 2018
e462c7d
feature/auth
alain-charles Jul 24, 2018
a3ee10b
Merge branch 'master' into master
nnixaa Jul 24, 2018
b38ae55
Merge branch 'master' into master
nnixaa Jul 25, 2018
62a2aff
feature/auth
alain-charles Jul 25, 2018
66ca493
Merge remote-tracking branch 'origin/master'
alain-charles Jul 25, 2018
3bd5a50
feature/auth
alain-charles Jul 25, 2018
398c058
feature/auth
alain-charles Jul 25, 2018
ee706d3
Merge branch 'master' into master
alain-charles Jul 26, 2018
5eb5821
feature/auth
alain-charles Jul 26, 2018
1a08225
Merge remote-tracking branch 'origin/master'
alain-charles Jul 26, 2018
1d697bc
feature/auth
alain-charles Jul 27, 2018
1c25956
Merge remote-tracking branch 'upstream/master'
alain-charles Jul 27, 2018
ff0e8d3
Merge remote-tracking branch 'upstream/master'
alain-charles Jul 27, 2018
e7af99e
Optimized getAccessTokenPayload() calls
alain-charles Jul 28, 2018
95632b1
Optimized getAccessTokenPayload() calls
alain-charles Jul 28, 2018
bc4001f
Merge branch 'master' into master
nnixaa Jul 30, 2018
10dfe71
merged upstream/master
alain-charles Aug 18, 2018
4390bf8
refreshToken request is automatically sent if possible, asked by NbAu…
alain-charles Aug 18, 2018
dc2a96a
Added 3 unit tests for NbAuthJWTInterceptor
alain-charles Aug 19, 2018
9811ed3
Added unit tests for NbAuthService.isAuthenticatedOrRefresh();
alain-charles Aug 19, 2018
1da13c9
Removed unused function argument in spec.ts file
alain-charles Aug 19, 2018
f5a4eda
Removed unused function argument in spec.ts file
alain-charles Aug 20, 2018
49557c2
formatting issue
alain-charles Aug 20, 2018
0e23673
Provides now a NoOp filter for NbAuthJWTInterceptor
alain-charles Aug 21, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/framework/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injector, ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientModule, HttpRequest } from '@angular/common/http';

import {
NbAlertModule,
Expand Down Expand Up @@ -36,6 +36,7 @@ import {
NB_AUTH_INTERCEPTOR_HEADER,
NB_AUTH_OPTIONS,
NB_AUTH_STRATEGIES,
NB_AUTH_TOKEN_INTERCEPTOR_FILTER,
NB_AUTH_TOKENS,
NB_AUTH_USER_OPTIONS,
NbAuthOptions,
Expand Down Expand Up @@ -79,6 +80,10 @@ export function nbOptionsFactory(options) {
return deepExtend(defaultAuthOptions, options);
}

export function nbNoOpInterceptorFilter(req: HttpRequest<any>): boolean {
return true;
}

@NgModule({
imports: [
CommonModule,
Expand Down Expand Up @@ -122,6 +127,7 @@ export class NbAuthModule {
{ provide: NB_AUTH_TOKENS, useFactory: nbTokensFactory, deps: [NB_AUTH_STRATEGIES] },
{ provide: NB_AUTH_FALLBACK_TOKEN, useValue: NbAuthSimpleToken },
{ provide: NB_AUTH_INTERCEPTOR_HEADER, useValue: 'Authorization' },
{ provide: NB_AUTH_TOKEN_INTERCEPTOR_FILTER, useValue: nbNoOpInterceptorFilter },
{ provide: NbTokenStorage, useClass: NbTokenLocalStorage },
NbAuthTokenParceler,
NbAuthService,
Expand Down
6 changes: 5 additions & 1 deletion src/framework/auth/auth.options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { InjectionToken } from '@angular/core';
import { HttpRequest } from '@angular/common/http';
import { NbAuthStrategy, NbAuthStrategyOptions } from './strategies';
import { NbAuthToken, NbAuthTokenClass } from './services/token/token';

Expand Down Expand Up @@ -88,4 +89,7 @@ export const NB_AUTH_OPTIONS = new InjectionToken<NbAuthOptions>('Nebular Auth O
export const NB_AUTH_USER_OPTIONS = new InjectionToken<NbAuthOptions>('Nebular User Auth Options');
export const NB_AUTH_STRATEGIES = new InjectionToken<NbAuthStrategies>('Nebular Auth Strategies');
export const NB_AUTH_TOKENS = new InjectionToken<NbAuthTokenClass<NbAuthToken>[]>('Nebular Auth Tokens');
export const NB_AUTH_INTERCEPTOR_HEADER = new InjectionToken<NbAuthStrategies>('Nebular Simple Interceptor Header');
export const NB_AUTH_INTERCEPTOR_HEADER = new InjectionToken<string>('Nebular Simple Interceptor Header');
export const NB_AUTH_TOKEN_INTERCEPTOR_FILTER =
new InjectionToken<(req: HttpRequest<any>) => boolean>('Nebular Interceptor Filter');

30 changes: 28 additions & 2 deletions src/framework/auth/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,40 @@ export class NbAuthService {
}

/**
* Returns true if auth token is presented in the token storage
* @returns {Observable<any>}
* Returns true if auth token is present in the token storage
* @returns {Observable<boolean>}
*/
isAuthenticated(): Observable<boolean> {
return this.getToken()
.pipe(map((token: NbAuthToken) => token.isValid()));
}

/**
* Returns true if valid auth token is present in the token storage.
* If not, calls the strategy refreshToken, and returns isAuthenticated() if success, false otherwise
* @returns {Observable<boolean>}
*/
isAuthenticatedOrRefresh(): Observable<boolean> {
return this.getToken()
.pipe(
switchMap(token => {
if (!token.isValid()) {
return this.refreshToken(token.getOwnerStrategyName(), token)
Copy link

@cloakedch cloakedch Aug 22, 2018

Choose a reason for hiding this comment

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

Does this work with any kind of token type? Is the refreshToken endpoint always expecting the same token that was stored?

I am using an API that during authentication returns an object consisting of a refresh token, an access token and other properties. However, on refreshing the token I am expected to only supply the refresh token.
To get it working, I had to convert the token to an NbAuthOAuth2Token and explicitly call "oAuth2Token.getRefreshToken()" on this line instead of just "token".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @cloakedch ,

The refresh_token is implemented according to RFC6749 section 1.5.

This RFC specifies the OAuth2 authentication scheme. So the code works with auths server returning tokens according to the RFC section 4.2
The refreshToken endpoint is waiting for the refresh_token that was sent during login or during the latest refresh_token call. Both grant_type=refresh_token and refresh_token=theRefreshToken are sent to the refreshToken endpoint for getting a new access-token, according to the RFC.

I would say that you have to use the NbOAuth2AuthStrategy with password grantType(if you send login/password at login) and NbAuthOAuth2Tokenclass in your case.

Someting like

            NbOAuth2AuthStrategy.setup({
          name: 'password',
          clientId: 'test',
          clientSecret: 'secret',
          baseEndpoint: 'http://your.BaseEnd/Point',
          token: {
            endpoint: 'token',
            grantType: NbOAuth2GrantType.PASSWORD,
            class: NbAuthOAuth2Token,
          },
          refresh: {
            endpoint: 'refresh-token',
            grantType: NbOAuth2GrantType.REFRESH_TOKEN,
          },
        }),

Hope it helps

Choose a reason for hiding this comment

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

OK, I guess the API I am using is then not working according to the RFC.
Thanks for clearing that up!

.pipe(
switchMap(res => {
if (res.isSuccess()) {
return this.isAuthenticated();
} else {
return observableOf(false);
}
}),
)
} else {
return observableOf(token.isValid());
}
}));
}

/**
* Returns tokens stream
* @returns {Observable<NbAuthSimpleToken>}
Expand Down
82 changes: 69 additions & 13 deletions src/framework/auth/services/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('auth-service', () => {
let tokenService: NbTokenService;
let dummyAuthStrategy: NbDummyAuthStrategy;
const testTokenValue = 'test-token';
const ownerStrategyName = 'strategy';
const ownerStrategyName = 'dummy';
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need this change? looks unrelated

Copy link
Contributor Author

@alain-charles alain-charles Aug 20, 2018

Choose a reason for hiding this comment

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

Yes we do since i added isAuthenticatedOrRefresh() calls in the unit tests
The strategy provided in test module is named 'dummy'.
So i modified the ownerStrategyName to dummy and replaced all 'dummy' occurrences by ownerStrategyName.
I know everything is not related to this PR, but is was necessary.



const resp401 = new HttpResponse<Object>({body: {}, status: 401});
Expand Down Expand Up @@ -143,6 +143,62 @@ describe('auth-service', () => {
},
);

it('isAuthenticatedOrRefresh, token valid, strategy refreshToken not called, returns true', (done) => {
const spy = spyOn(dummyAuthStrategy, 'refreshToken')

spyOn(tokenService, 'get')
.and
.returnValue(observableOf(testToken));

authService.isAuthenticatedOrRefresh()
.pipe(first())
.subscribe((isAuth: boolean) => {
expect(spy).not.toHaveBeenCalled();
expect(isAuth).toBeTruthy();
done();
});
},
);

it('isAuthenticatedOrRefresh, token invalid, strategy refreshToken called, returns true', (done) => {

const spy = spyOn(dummyAuthStrategy, 'refreshToken')
.and
.returnValue(observableOf(successResult));

spyOn(tokenService, 'get')
.and
.returnValues(observableOf(emptyToken), observableOf(testToken));

authService.isAuthenticatedOrRefresh()
.pipe(first())
.subscribe((isAuth: boolean) => {
expect(spy).toHaveBeenCalled();
expect(isAuth).toBeTruthy();
done();
});
},
);

it('isAuthenticatedOrRefresh, token invalid, strategy refreshToken called, returns false', (done) => {
const spy = spyOn(dummyAuthStrategy, 'refreshToken')
.and
.returnValue(observableOf(failResult));

spyOn(tokenService, 'get')
.and
.returnValues(observableOf(emptyToken), observableOf(emptyToken));

authService.isAuthenticatedOrRefresh()
.pipe(first())
.subscribe((isAuth: boolean) => {
expect(spy).toHaveBeenCalled();
expect(isAuth).toBeFalsy();
done();
});
},
);

it('onTokenChange return correct stream and gets test token', (done) => {
const spy = spyOn(tokenService, 'tokenChange')
.and
Expand All @@ -166,7 +222,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.authenticate('dummy').subscribe((authRes: NbAuthResult) => {
authService.authenticate(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();
expect(authRes.isFailure()).toBeTruthy();
expect(authRes.isSuccess()).toBeFalsy();
Expand All @@ -193,7 +249,7 @@ describe('auth-service', () => {
.returnValue(observableOf(null));


authService.authenticate('dummy').subscribe((authRes: NbAuthResult) => {
authService.authenticate(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategySpy).toHaveBeenCalled();
expect(tokenServiceSetSpy).toHaveBeenCalled();

Expand All @@ -218,7 +274,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.register('dummy').subscribe((authRes: NbAuthResult) => {
authService.register(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();
expect(authRes.isFailure()).toBeTruthy();
expect(authRes.isSuccess()).toBeFalsy();
Expand All @@ -244,7 +300,7 @@ describe('auth-service', () => {
.and
.returnValue(observableOf(null));

authService.register('dummy').subscribe((authRes: NbAuthResult) => {
authService.register(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategySpy).toHaveBeenCalled();
expect(tokenServiceSetSpy).toHaveBeenCalled();

Expand All @@ -268,7 +324,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.logout('dummy').subscribe((authRes: NbAuthResult) => {
authService.logout(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeTruthy();
Expand All @@ -292,7 +348,7 @@ describe('auth-service', () => {
));
const tokenServiceClearSpy = spyOn(tokenService, 'clear').and.returnValue(observableOf('STUB'));

authService.logout('dummy').subscribe((authRes: NbAuthResult) => {
authService.logout(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategyLogoutSpy).toHaveBeenCalled();
expect(tokenServiceClearSpy).toHaveBeenCalled();

Expand All @@ -316,7 +372,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.requestPassword('dummy').subscribe((authRes: NbAuthResult) => {
authService.requestPassword(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeTruthy();
Expand All @@ -339,7 +395,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.requestPassword('dummy').subscribe((authRes: NbAuthResult) => {
authService.requestPassword(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategyLogoutSpy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeFalsy();
Expand All @@ -362,7 +418,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.resetPassword('dummy').subscribe((authRes: NbAuthResult) => {
authService.resetPassword(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeTruthy();
Expand All @@ -385,7 +441,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.resetPassword('dummy').subscribe((authRes: NbAuthResult) => {
authService.resetPassword(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategyLogoutSpy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeFalsy();
Expand All @@ -408,7 +464,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.refreshToken('dummy').subscribe((authRes: NbAuthResult) => {
authService.refreshToken(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();
expect(authRes.isFailure()).toBeTruthy();
expect(authRes.isSuccess()).toBeFalsy();
Expand All @@ -434,7 +490,7 @@ describe('auth-service', () => {
.and
.returnValue(observableOf(null));

authService.refreshToken('dummy').subscribe((authRes: NbAuthResult) => {
authService.refreshToken(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategySpy).toHaveBeenCalled();
expect(tokenServiceSetSpy).toHaveBeenCalled();

Expand Down
Loading