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

OAuth 2.0 code grant flow expects xml encoded not json payload #959

Open
1 task
ryanpetm opened this issue Nov 2, 2018 · 2 comments
Open
1 task

OAuth 2.0 code grant flow expects xml encoded not json payload #959

ryanpetm opened this issue Nov 2, 2018 · 2 comments

Comments

@ryanpetm
Copy link

ryanpetm commented Nov 2, 2018

Issue type

I'm submitting a ... (check one with "x")

  • [ * ] bug report
  • feature request

Issue description

Attempting to leverage ngx-admin templates to recover azure acess tokens with
Azure OAuth 2.0 code grant flow. https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code

Current behavior:

Plan to use ngx-admin template to qury microsoft graph api
To do so i need my app to recover two tokens

  1. Token 1 : Authorisation token
  2. Token2 : Acess token
    Currently i can successfully negotiate the first token as code request from endpoint oauth2/authorize?
    However when i attempt to post the authorization token to the second endpoint oauth2/token? to recover an acess token Microsoft complains as follows
    {error: "invalid_request",…} correlation_id : "b5cb3397-b008-4bb0-b475-76ea8deab6c7" error : "invalid_request" error_codes : [90014] error_description : "AADSTS90014: The request body must contain the following parameter: 'grant_type'. ↵Trace ID: d74fbb29-bea3-4bca-b663-b09f65713900 ↵Correlation ID: b5cb3397-b008-4bb0-b475-76ea8deab6c7 ↵Timestamp: 2018-11-02 09:18:53Z" timestamp : "2018-11-02 09:18:53Z" trace_id : "d74fbb29-bea3-4bca-b663-b09f65713900"

For a reference here is the post request as recovered from network debugging
POST https://login.microsoftonline.com/xxxxxxxxxxxxxxxxx/oauth2/token?

REQUEST HEADERS

Host: login.microsoftonline.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: application/json, text/plain, */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://localhost:4200/pages/azure/callback?code=AQABAAIAAAC5una0EUFgTIF8ElaxtWjTItiXmuKtpz_KYr3JCsaZfylYKyaMy81yTMl--y5xWN7KkkKbMNdbtgyBeqJy1_0cbmiWZ4pmpVuHzHAp2fZwx6EYi6_Zdg1nCiY0v2y_2PD4EZu3iHWGIE2BCXu7IKw66Oz43lZZmB8hn1x8WRZrOnMUwrxDB_jJDviMAS-LMTed7pE6HoTOy6kUE2kMKCTL_qY2dYMPp16dduOuSvdWV-Suo21wyKaZbAJ37WRiizi0HKr0jWnnWv6KbAXw466fpVkTRyAysw_5bMkHNFCSvf5qmcHZnB_wPmNJnBBl_O4P_SiAbS3ubHuUU-KzyNo1cAijvP5aPssWShmbeTyV1MNnEjjjmfNGaOfGXVk6KKkCGecwbR_QbIpAIJ0-qnR06oqAQOgCSDnTQ204TPQyxehI7Useo7yntddQnt6wnxA26tiCfglCPyBwNUuqpzz8fnwtPydpB7N8pS3FrkOAw3rNS442dDwjqV5L2RpdOqD3GClHtPJgzBiWTEkhayS1CXuxNGr5dAZyr_68b_tXkrmW1LrQ82bvjD-PpvP9a84gAA&session_state=f76e0e48-8613-4c2f-9f53-d10ddd3b5548
Content-Type: application/json
Content-Length: 746
REQUEST BODY

{"grant_type":"authorization_code","code":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxx","redirect_uri":"http://localhost:4200/pages/azure/callback","client_id":"xxxxxxxxxxxxxxxxxxxxxxxxxxxx"}

Stack overflow post point out that the second endpoint expects payload as
xml-endoded
https://stackoverflow.com/questions/48996804/azure-active-directory-aadsts90014-invalid-request

Expected behavior:

Expect to be able to negotiate the second transaction
Is there a option to enable application/x-www-form-urlencoded for the second trasaction
Steps to reproduce:

ngx-admin leveraging oauth2

Related code:

oauth2.module.ts

/**
 * @license
 * Copyright Akveo. All Rights Reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 */

import { NgModule } from '@angular/core';
import { ThemeModule } from '../../../@theme/theme.module';
import { Oauth2RoutingModule, routedComponents } from './oauth2-routing.module';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';


import {
  NbAuthModule,
  NbOAuth2AuthStrategy,
  NbOAuth2ResponseType,
  NbAuthOAuth2Token,
  NbOAuth2GrantType,
  NbAuthJWTToken,
} from '@nebular/auth';




@NgModule({
  imports: [
    ThemeModule,
    FormsModule,
    HttpClientModule,
    Oauth2RoutingModule,
    NbAuthModule.forRoot({
      strategies: [
        NbOAuth2AuthStrategy.setup({
          name: 'azure',
          clientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
          clientSecret: 'xxxxxxxxxxxxxxxxxxxxxxx',
          authorize: {
            endpoint: 'https://login.microsoftonline.com/xxxxxxxxxxxxxxxxxxx/oauth2/authorize?',
            responseType: NbOAuth2ResponseType.CODE,
            scope: 'https://graph.microsoft.com',
            redirectUri: 'http://localhost:4200/pages/azure/callback',
            //params: {'resource' : 'https://graph.microsoft.com'}
          },
        token: {
          endpoint: 'https://login.microsoftonline.com/1d2d8da4-2bcf-4021-a2c2-55cf9b00db5b/oauth2/token?',
          redirectUri: 'http://localhost:4200/pages/azure/callback',
          class:  NbAuthJWTToken, // NbAuthOAuth2Token,
        },

          redirect: {
            success: '/pages/azure',
          },
        },),
      ],
    }),
  ],
  declarations: [
    ...routedComponents,
  ],
})
export class NbOAuth2Module {
}

oauth2-login.component.ts

/**
 * @license
 * Copyright Akveo. All Rights Reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 */

import { NbMenuService } from '@nebular/theme';
import { Component, OnDestroy } from '@angular/core';
import { NbAuthOAuth2Token, NbAuthResult, NbAuthService } from '@nebular/auth';
import { takeWhile } from 'rxjs/operators';


@Component({
  selector: 'ngx-oauth2-login',
  styleUrls: ['./oauth2-login.component.scss'],
  templateUrl: './oauth2-login.component.html',
})


export class NbOAuth2LoginComponent implements OnDestroy {

  token: NbAuthOAuth2Token;

  alive = true;

  constructor(private authService: NbAuthService) {
    this.authService.onTokenChange()
      .pipe(takeWhile(() => this.alive))
      .subscribe((token: NbAuthOAuth2Token) => {
        this.token = null;
        if (token && token.isValid()) {
          this.token = token;
        }
      });
  }



  login() {
    this.authService.authenticate('azure')
      .pipe(takeWhile(() => this.alive))
      .subscribe((authResult: NbAuthResult) => {
      });
  }

  logout() {
    this.authService.logout('azure')
      .pipe(takeWhile(() => this.alive))
      .subscribe((authResult: NbAuthResult) => {
      });
  }

  ngOnDestroy(): void {
    this.alive = false;
  }
}

oauth2-callback.component.ts

/**
 * @license
 * Copyright Akveo. All Rights Reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 */

import { NbMenuService } from '@nebular/theme';
import { Component, OnDestroy } from '@angular/core';
import { NbAuthResult, NbAuthService } from '@nebular/auth';
import { Router } from '@angular/router';
import { takeWhile } from 'rxjs/operators';

@Component({
  selector: 'ngx-oauth2-callback',
  styleUrls: ['./oauth2-callback.component.scss'],
  templateUrl: './oauth2-callback.component.html',
})



export class NbOAuth2CallbackComponent implements OnDestroy {

  alive = true;

  constructor(private authService: NbAuthService, private router: Router) {
    this.authService.authenticate('azure')
      .pipe(takeWhile(() => this.alive))
      .subscribe((authResult: NbAuthResult) => {
        if (authResult.isSuccess() && authResult.getRedirect()) {
          this.router.navigateByUrl(authResult.getRedirect());
        }
      });
  }

  ngOnDestroy(): void {
    this.alive = false;
  }
}

Other information:

npm, node, OS, Browser

<!--
Node : 8.11.3, npm: 5.6.0 
Linux (Kubuntu 18.04)
Browser: Chrome/Firefox/
-->

Angular, Nebular

 "name": "ngx-admin",
  "version": "2.3.0",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/akveo/ngx-admin.git"
  },
  "bugs": {
    "url": "https://github.com/akveo/ngx-admin/issues"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "IE 11"
  ],
  "scripts": {
    "ng": "ng",
    "conventional-changelog": "conventional-changelog",
    "start": "ng serve",
    "build": "ng build",
    "build:prod": "npm run build -- --prod --aot",
    "test": "ng test",
    "test:coverage": "rimraf coverage && npm run test -- --code-coverage",
    "lint": "ng lint",
    "lint:fix": "ng lint ngx-admin-demo --fix",
    "lint:styles": "stylelint ./src/**/*.scss",
    "lint:ci": "npm run lint && npm run lint:styles",
    "pree2e": "webdriver-manager update --standalone false --gecko false",
    "e2e": "ng e2e",
    "docs": "compodoc -p src/tsconfig.app.json -d docs",
    "docs:serve": "compodoc -p src/tsconfig.app.json -d docs -s",
    "prepush": "npm run lint:ci",
    "release:changelog": "npm run conventional-changelog -- -p angular -i CHANGELOG.md -s"
  },
  "dependencies": {
    "@agm/core": "1.0.0-beta.2",
    "@angular/animations": "6.0.0",
    "@angular/common": "6.0.0",
    "@angular/compiler": "6.0.0",
    "@angular/core": "6.0.0",
    "@angular/forms": "6.0.0",
    "@angular/http": "6.0.0",
    "@angular/platform-browser": "6.0.0",
    "@angular/platform-browser-dynamic": "6.0.0",
    "@angular/router": "6.0.0",
    "@asymmetrik/ngx-leaflet": "3.0.1",
    "@nebular/auth": "2.0.0-rc.9",
    "@nebular/security": "2.0.0-rc.9",
    "@nebular/theme": "2.0.0-rc.9",
    "@ng-bootstrap/ng-bootstrap": "1.0.0",
    "@swimlane/ngx-charts": "7.0.1",
    "angular-datatables": "^6.0.0",
    "angular-tree-component": "7.2.0",
    "angular2-chartjs": "0.4.1",
    "angular2-toaster": "6.0.0",
    "bootstrap": "4.0.0",
    "chart.js": "2.7.1",
    "ckeditor": "4.7.3",
    "classlist.js": "1.1.20150312",
    "core-js": "2.5.1",
    "datatables.net": "^1.10.19",
    "datatables.net-bs4": "^1.10.19",
    "datatables.net-dt": "^1.10.19",
    "echarts": "^4.0.2",
    "font-awesome": "4.7.0",
    "intl": "1.2.5",
    "ionicons": "2.0.1",
    "jquery": "^3.3.1",
    "leaflet": "1.2.0",
    "nebular-icons": "1.0.8",
    "ng2-ckeditor": "1.1.13",
    "ng2-smart-table": "1.2.2",
    "ngx-echarts": "2.0.1",
    "normalize.css": "6.0.0",
    "pace-js": "1.0.2",
    "primeicons": "^1.0.0-beta.9",
    "primeng": "^6.0.0",
    "roboto-fontface": "0.8.0",
    "rxjs": "^6.1.0",
    "rxjs-compat": "^6.1.0",
    "socicon": "3.0.5",
    "tinymce": "4.5.7",
    "typeface-exo": "0.0.22",
    "web-animations-js": "2.2.5",
    "zone.js": "^0.8.26"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.6.0",
    "@angular/cli": "6.0.0",
    "@angular/compiler-cli": "6.0.0",
    "@angular/language-service": "6.0.0",
    "@compodoc/compodoc": "1.0.1",
    "@types/d3-color": "1.0.5",
    "@types/datatables.net": "^1.10.12",
    "@types/googlemaps": "3.30.4",
    "@types/jasmine": "2.5.54",
    "@types/jasminewd2": "2.0.3",
    "@types/jquery": "^3.3.4",
    "@types/leaflet": "1.2.3",
    "@types/node": "6.0.90",
    "codelyzer": "4.0.2",
    "conventional-changelog-cli": "1.3.4",
    "husky": "0.13.3",
    "jasmine-core": "2.6.4",
    "jasmine-spec-reporter": "4.1.1",
    "karma": "1.7.1",
    "karma-chrome-launcher": "2.1.1",
    "karma-cli": "1.0.1",
    "karma-coverage-istanbul-reporter": "1.3.0",
    "karma-jasmine": "1.1.0",
    "karma-jasmine-html-reporter": "0.2.2",
    "npm-run-all": "4.0.2",
    "protractor": "5.1.2",
    "rimraf": "2.6.1",
    "stylelint": "7.13.0",
    "ts-node": "3.2.2",
    "tslint": "5.7.0",
    "tslint-language-service": "0.9.6",
    "typescript": "2.7.2"
}
@ryanpetm
Copy link
Author

ryanpetm commented Nov 5, 2018

Had some luck in solving (dirty hack ) this issue myself .
Working of the recent fix #716
I was able to get the azure oauth2 code grant flow working by adding the following to my nebular/auth files

@nebular/auth/strategies/oauth2/oauthe2-stategy.js

    NbOAuth2AuthStrategy.prototype.requestToken = function (code) {
        var _this = this;
        var module = 'token';
        var url = this.getActionEndpoint(module);
        var requireValidToken = this.getOption(module + ".requireValidToken");
        var 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(function (res) {
            return new NbAuthResult(true, res, _this.getOption('redirect.success'), [], _this.getOption('defaultMessages'), _this.createToken(res, requireValidToken));
        }), catchError(function (res) { return _this.handleResponseError(res); }));
    };


    NbOAuth2AuthStrategy.prototype.buildCodeRequestData = function (code) {
        var params = {
            grant_type: this.getOption('token.grantType'),
            code: code,
            redirect_uri: this.getOption('token.redirectUri'),
            client_id: this.getOption('clientId'),
            client_secret: this.getOption('clientSecret'), // axure oauth 2 grant flow auth needs client secret  //
            resource: this.getOption('token.scope'), // axure oauth 2 grant flow auth needs a resource key identifier  //
        };
        //return this.cleanParams(this.addCredentialsToParams(params));
        return this.urlEncodeParameters(this.cleanParams(this.addCredentialsToParams(params))); // xml-encode params
    };


@movlim
Copy link

movlim commented Nov 5, 2018

I recommend you to add an interceptor like this:

@Injectable()
export class LoginRequestInterceptor implements HttpInterceptor {
    constructor() {}
    intercept(req: HttpRequest<any>, next: HttpHandler) {
        if (req.url === 'http://yourserverhere.com/oauth2/token') {
            const xhr = req.clone({
                headers: req.headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8'),
                body: `username=${encodeURIComponent(req.body.email)}&password=${encodeURIComponent(req.body.password)}&grant_type=password&client_id=clientidhere`,
            });
            return next.handle(xhr);
        } else {
            return next.handle(req);
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants