diff --git a/src/framework/auth/strategies/oauth2/oauth2-strategy.spec.ts b/src/framework/auth/strategies/oauth2/oauth2-strategy.spec.ts index c0175cbc1c..f31836df2d 100644 --- a/src/framework/auth/strategies/oauth2/oauth2-strategy.spec.ts +++ b/src/framework/auth/strategies/oauth2/oauth2-strategy.spec.ts @@ -147,12 +147,17 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/token' - && req.body['grant_type'] === NbOAuth2GrantType.AUTHORIZATION_CODE - && req.body['code'] === 'code' - && req.body['client_id'] === 'clientId' - && !req.body['redirect_uri'], - ).flush(tokenSuccessResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/token' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.AUTHORIZATION_CODE + && decodeURIComponent(params['code']) === 'code' + && decodeURIComponent(params['client_id']) === 'clientId' + && !params['redirect_uri']) + }, + ) + .flush(tokenSuccessResponse); }); it('handle error redirect back', (done: DoneFn) => { @@ -189,7 +194,13 @@ describe('oauth2-auth-strategy', () => { done(); }); - httpMock.expectOne('http://example.com/token') + httpMock.expectOne( + req => { + return (req.url === 'http://example.com/token' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + ) + }, + ) .flush(tokenErrorResponse, { status: 400, statusText: 'Bad Request' }); }); @@ -213,12 +224,17 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/token' - && req.headers.get('Authorization') === authHeader - && req.body['grant_type'] === NbOAuth2GrantType.REFRESH_TOKEN - && req.body['refresh_token'] === successToken.getRefreshToken() - && !req.body['scope'], - ).flush(tokenSuccessResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/token' + && req.headers.get('Authorization') === authHeader + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.REFRESH_TOKEN + && decodeURIComponent(params['refresh_token']) === successToken.getRefreshToken() + && !params['scope']) + }, + ) + .flush(tokenSuccessResponse); }); it('handle refresh token with requestBody client auth', (done: DoneFn) => { @@ -241,13 +257,18 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/token' - && req.body['grant_type'] === NbOAuth2GrantType.REFRESH_TOKEN - && req.body['refresh_token'] === successToken.getRefreshToken() - && req.body['client_id'] === strategy.getOption('clientId') - && req.body['client_secret'] === strategy.getOption('clientSecret') - && !req.body['scope'], - ).flush(tokenSuccessResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/token' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.REFRESH_TOKEN + && decodeURIComponent(params['refresh_token']) === successToken.getRefreshToken() + && decodeURIComponent(params['client_id']) === strategy.getOption('clientId') + && decodeURIComponent(params['client_secret']) === strategy.getOption('clientSecret') + && !params['scope']) + }, + ) + .flush(tokenSuccessResponse); }); it('handle refresh token with NO client auth', (done: DoneFn) => { @@ -266,11 +287,16 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/token' - && req.body['grant_type'] === NbOAuth2GrantType.REFRESH_TOKEN - && req.body['refresh_token'] === successToken.getRefreshToken() - && !req.body['scope'], - ).flush(tokenSuccessResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/token' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.REFRESH_TOKEN + && decodeURIComponent(params['refresh_token']) === successToken.getRefreshToken() + && !params['scope']) + }, + ) + .flush(tokenSuccessResponse); }); it('handle refresh token and inserts existing refresh_token if needed', (done: DoneFn) => { @@ -289,11 +315,16 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/token' - && req.body['grant_type'] === NbOAuth2GrantType.REFRESH_TOKEN - && req.body['refresh_token'] === successToken.getRefreshToken() - && !req.body['scope'], - ).flush(tokenWithoutRefreshTokenResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/token' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.REFRESH_TOKEN + && decodeURIComponent(params['refresh_token']) === successToken.getRefreshToken() + && !params['scope']) + }, + ) + .flush(tokenWithoutRefreshTokenResponse); }); it('Handle refresh-token and leaves refresh_token unchanged if present', (done: DoneFn) => { @@ -313,11 +344,16 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/token' - && req.body['grant_type'] === NbOAuth2GrantType.REFRESH_TOKEN - && req.body['refresh_token'] === successToken.getRefreshToken() - && !req.body['scope'], - ).flush(refreshedTokenResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/token' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.REFRESH_TOKEN + && decodeURIComponent(params['refresh_token']) === successToken.getRefreshToken() + && !params['scope']) + }, + ) + .flush(refreshedTokenResponse); }); it('handle error token refresh response', (done: DoneFn) => { @@ -335,7 +371,13 @@ describe('oauth2-auth-strategy', () => { done(); }); - httpMock.expectOne('http://example.com/token') + httpMock.expectOne( + req => { + return (req.url === 'http://example.com/token' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + ) + }, + ) .flush(tokenErrorResponse, { status: 400, statusText: 'Bad Request' }); }); }); @@ -470,12 +512,17 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/custom' - && req.body['grant_type'] === NbOAuth2GrantType.AUTHORIZATION_CODE - && req.body['code'] === 'code' - && req.body['client_id'] === 'clientId' - && req.body['redirect_uri'] === 'http://localhost:4200/callback', - ).flush(tokenSuccessResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/custom' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.AUTHORIZATION_CODE + && decodeURIComponent(params['code']) === 'code' + && decodeURIComponent(params['client_id']) === 'clientId' + && decodeURIComponent(params['redirect_uri']) === 'http://localhost:4200/callback') + }, + ) + .flush(tokenSuccessResponse); }); it('handle success redirect and sends correct token request with BASIC client Auth', (done: DoneFn) => { @@ -500,13 +547,18 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/custom' + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/custom' && req.headers.get('Authorization') === authHeader - && req.body['grant_type'] === NbOAuth2GrantType.AUTHORIZATION_CODE - && req.body['code'] === 'code' - && req.body['client_id'] === 'clientId' - && req.body['redirect_uri'] === 'http://localhost:4200/callback', - ).flush(tokenSuccessResponse); + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.AUTHORIZATION_CODE + && decodeURIComponent(params['code']) === 'code' + && decodeURIComponent(params['client_id']) === 'clientId' + && decodeURIComponent(params['redirect_uri']) === 'http://localhost:4200/callback') + }, + ) + .flush(tokenSuccessResponse); }); it('handle success redirect and sends correct token request with REQUEST_BODY client Auth', (done: DoneFn) => { @@ -531,13 +583,18 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/custom' - && req.body['grant_type'] === NbOAuth2GrantType.AUTHORIZATION_CODE - && req.body['code'] === 'code' - && req.body['client_id'] === strategy.getOption('clientId') - && req.body['client_secret'] === strategy.getOption('clientSecret') - && req.body['redirect_uri'] === 'http://localhost:4200/callback', - ).flush(tokenSuccessResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/custom' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.AUTHORIZATION_CODE + && decodeURIComponent(params['code']) === 'code' + && decodeURIComponent(params['client_id']) === strategy.getOption('clientId') + && decodeURIComponent(params['client_secret']) === strategy.getOption('clientSecret') + && decodeURIComponent(params['redirect_uri']) === 'http://localhost:4200/callback') + }, + ) + .flush(tokenSuccessResponse); }); it('handle error redirect back', (done: DoneFn) => { @@ -573,11 +630,16 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/custom' - && req.body['grant_type'] === NbOAuth2GrantType.REFRESH_TOKEN - && req.body['refresh_token'] === successToken.getRefreshToken() - && req.body['scope'] === 'read', - ).flush(tokenSuccessResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/custom' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.REFRESH_TOKEN + && decodeURIComponent(params['refresh_token']) === successToken.getRefreshToken() + && decodeURIComponent(params['scope']) === 'read') + }, + ) + .flush(tokenSuccessResponse); }); it('handle refresh token with BASIC client auth', (done: DoneFn) => { @@ -601,12 +663,17 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/custom' - && req.headers.get('Authorization') === authHeader - && req.body['grant_type'] === NbOAuth2GrantType.REFRESH_TOKEN - && req.body['refresh_token'] === successToken.getRefreshToken() - && req.body['scope'] === 'read', - ).flush(tokenSuccessResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/custom' + && req.headers.get('Authorization') === authHeader + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.REFRESH_TOKEN + && decodeURIComponent(params['refresh_token']) === successToken.getRefreshToken() + && decodeURIComponent(params['scope']) === 'read') + }, + ) + .flush(tokenSuccessResponse); }); it('handle refresh token with REQUEST_BODY client auth', (done: DoneFn) => { @@ -630,13 +697,18 @@ describe('oauth2-auth-strategy', () => { }); httpMock.expectOne( - req => req.url === 'http://example.com/custom' - && req.body['grant_type'] === NbOAuth2GrantType.REFRESH_TOKEN - && req.body['refresh_token'] === successToken.getRefreshToken() - && req.body['client_id'] === strategy.getOption('clientId') - && req.body['client_secret'] === strategy.getOption('clientSecret') - && req.body['scope'] === 'read', - ).flush(tokenSuccessResponse); + req => { + const params = parseQueryParams(req.body); + return (req.url === 'http://example.com/custom' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + && decodeURIComponent(params['grant_type']) === NbOAuth2GrantType.REFRESH_TOKEN + && decodeURIComponent(params['refresh_token']) === successToken.getRefreshToken() + && decodeURIComponent(params['client_id']) === strategy.getOption('clientId') + && decodeURIComponent(params['client_secret']) === strategy.getOption('clientSecret') + && decodeURIComponent(params['scope']) === 'read') + }, + ) + .flush(tokenSuccessResponse); }); it('handle error token response', (done: DoneFn) => { @@ -656,7 +728,13 @@ describe('oauth2-auth-strategy', () => { done(); }); - httpMock.expectOne('http://example.com/custom') + httpMock.expectOne( + req => { + return (req.url === 'http://example.com/custom' + && req.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + ) + }, + ) .flush(tokenErrorResponse, { status: 400, statusText: 'Bad Request' }); }); }); diff --git a/src/framework/auth/strategies/oauth2/oauth2-strategy.ts b/src/framework/auth/strategies/oauth2/oauth2-strategy.ts index a9427ae7a5..8bc208c550 100644 --- a/src/framework/auth/strategies/oauth2/oauth2-strategy.ts +++ b/src/framework/auth/strategies/oauth2/oauth2-strategy.ts @@ -223,7 +223,10 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy { const url = this.getActionEndpoint(module); const requireValidToken = this.getOption(`${module}.requireValidToken`); - return this.http.post(url, this.buildRefreshRequestData(token), { headers: this.buildAuthHeader() }) + let headers = this.buildAuthHeader() || new HttpHeaders() ; + headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); + + return this.http.post(url, this.buildRefreshRequestData(token), { headers: headers }) .pipe( map((res) => { return new NbAuthResult( @@ -275,7 +278,10 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy { const url = this.getActionEndpoint(module); const requireValidToken = this.getOption(`${module}.requireValidToken`); - return this.http.post(url, this.buildCodeRequestData(code), { headers: this.buildAuthHeader() }) + let headers = this.buildAuthHeader() || new HttpHeaders() ; + headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); + + return this.http.post(url, this.buildCodeRequestData(code), { headers: headers }) .pipe( map((res) => { return new NbAuthResult( @@ -297,7 +303,7 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy { redirect_uri: this.getOption('token.redirectUri'), client_id: this.getOption('clientId'), }; - return this.cleanParams(this.addCredentialsToParams(params)); + return this.urlEncodeParameters(this.cleanParams(this.addCredentialsToParams(params))); } protected buildRefreshRequestData(token: NbAuthRefreshableToken): any { @@ -306,7 +312,7 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy { refresh_token: token.getRefreshToken(), scope: this.getOption('refresh.scope'), }; - return this.cleanParams(this.addCredentialsToParams(params)); + return this.urlEncodeParameters(this.cleanParams(this.addCredentialsToParams(params))); } protected buildPasswordRequestData(username: string, password: string ): string {