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

PKCE Code verifier is incorrect for Angular 12 production build #1120

Closed
Sebastian-G opened this issue Aug 20, 2021 · 16 comments
Closed

PKCE Code verifier is incorrect for Angular 12 production build #1120

Sebastian-G opened this issue Aug 20, 2021 · 16 comments
Labels
bug For tagging faulty or unexpected behavior. investigation-needed Indication that the maintainer or involved community members may need to investigate more.

Comments

@Sebastian-G
Copy link

Description

The bug was found in version angular-oauth2-oidc:12.0.2 in combination with Angular 12.2.1. It has only been
reported since the update of angular version from 12.0.5 -> 12.2.1.
(It is probably duplicate of #1117 )

ERROR

We are unable to login with PKCE Code.

POST https://login.microsoftonline.com/XXXX-YYYYY-42d2-a01a-13edff7f5fe7/oauth2/v2.0/token
[400 Bad Request]

---------

error   | "invalid_grant"
msg     | "AADSTS501481: The Code_Verifier does not match the code_challenge supplied in the authorization request."

Context & Approach

If we run the project with ng serve the SPA is able to connect to our Azure AD, also if we build the application
with --configuration development. Only if we build the application in production mode, we are not able to login
anymore. To further narrow down the problem, we turned off the optimization flags:

{
  // angular.json ...
  "configurations": {
    "fake-prod": {
      "buildOptimizer": false,
      "optimization": false
      // ...
    },
    "development": {
      "buildOptimizer": false,
      "optimization": false,
      "vendorChunk": true,
      "extractLicenses": false,
      "sourceMap": true,
      "namedChunks": true
    }
  }
}

With this setting you can log in again!

System information

Full system information
npx envinfo --system --binaries --browsers --npmPackages --duplicates --npmGlobalPackages
System:
OS: macOS 11.5.2
CPU: (12) x64 Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
Memory: 31.76 MB / 32.00 GB
Shell: 5.8 - /bin/zsh
Binaries:
Node: 14.15.3 - ~/.nvm/versions/node/v14.15.3/bin/node
Yarn: 1.22.10 - ~/.nvm/versions/node/v14.15.3/bin/yarn
npm: 7.16.0 - ~/.nvm/versions/node/v14.15.3/bin/npm
Browsers:
Chrome: 92.0.4515.159
Chrome Canary: 95.0.4612.2
Firefox: 90.0.2
Firefox Developer Edition: 92.0
Safari: 14.1.2
npmPackages:
@angular-devkit/build-angular: ~12.2.1 => 12.2.1
@angular-devkit/build-webpack: ^0.1202.1 => 0.1202.1
@angular-eslint/builder: ~12.2.1 => 12.2.2
@angular-eslint/eslint-plugin: ~12.2.1 => 12.2.2
@angular-eslint/eslint-plugin-template: ~12.2.1 => 12.2.2
@angular-eslint/schematics: ~12.2.1 => 12.2.2
@angular-eslint/template-parser: ~12.2.1 => 12.2.2
@angular/animations: ~12.2.1 => 12.2.1
@angular/animations/browser:  undefined ()
@angular/animations/browser/testing:  undefined ()
@angular/cdk: ~12.2.1 => 12.2.1
@angular/cdk/a11y:  undefined ()
@angular/cdk/accordion:  undefined ()
@angular/cdk/bidi:  undefined ()
@angular/cdk/clipboard:  undefined ()
@angular/cdk/coercion:  undefined ()
@angular/cdk/collections:  undefined ()
@angular/cdk/drag-drop:  undefined ()
@angular/cdk/keycodes:  undefined ()
@angular/cdk/layout:  undefined ()
@angular/cdk/observers:  undefined ()
@angular/cdk/overlay:  undefined ()
@angular/cdk/platform:  undefined ()
@angular/cdk/portal:  undefined ()
@angular/cdk/scrolling:  undefined ()
@angular/cdk/stepper:  undefined ()
@angular/cdk/table:  undefined ()
@angular/cdk/testing:  undefined ()
@angular/cdk/testing/protractor:  undefined ()
@angular/cdk/testing/selenium-webdriver:  undefined ()
@angular/cdk/testing/testbed:  undefined ()
@angular/cdk/text-field:  undefined ()
@angular/cdk/tree:  undefined ()
@angular/cli: ~12.2.1 => 12.2.1
@angular/common: ~12.2.1 => 12.2.1
@angular/common/http:  undefined ()
@angular/common/http/testing:  undefined ()
@angular/common/testing:  undefined ()
@angular/common/upgrade:  undefined ()
@angular/compiler: ~12.2.1 => 12.2.1
@angular/compiler-cli: ~12.2.1 => 12.2.1
@angular/compiler/testing:  undefined ()
@angular/core: ~12.2.1 => 12.2.1
@angular/core/testing:  undefined ()
@angular/flex-layout: ^12.0.0-beta.34 => 12.0.0-beta.34
@angular/flex-layout/core:  undefined ()
@angular/flex-layout/extended:  undefined ()
@angular/flex-layout/flex:  undefined ()
@angular/flex-layout/grid:  undefined ()
@angular/flex-layout/server:  undefined ()
@angular/forms: ~12.2.1 => 12.2.1
@angular/localize: ^12.2.1 => 12.2.1
@angular/localize/init:  undefined ()
@angular/platform-browser: ~12.2.1 => 12.2.1
@angular/platform-browser-dynamic: ~12.2.1 => 12.2.1
@angular/platform-browser-dynamic/testing:  undefined ()
@angular/platform-browser/animations:  undefined ()
@angular/platform-browser/testing:  undefined ()
@angular/router: ~12.2.1 => 12.2.1
@angular/router/testing:  undefined ()
@angular/router/upgrade:  undefined ()
@ngneat/until-destroy: ^8.0.4 => 8.0.4
@ngx-translate/core: ^13.0.0 => 13.0.0
@ngx-translate/http-loader: ^6.0.0 => 6.0.0
@ngxs/store: ^3.7.2 => 3.7.2
@ngxs/store/internals:  undefined ()
@ngxs/store/operators:  undefined ()
@types/jest: ^27.0.1 => 27.0.1
@types/node: ^12.11.1 => 12.20.15 (14.17.4)
@typescript-eslint/eslint-plugin: ^4.23.0 => 4.23.0
@typescript-eslint/parser: ^4.23.0 => 4.23.0 (3.10.1)
angular-oauth2-oidc: ^12.0.2 => 12.0.2
angular-svg-icon: ^12.0.0 => 12.0.0
compodoc: ^0.0.41 => 0.0.41
cypress: 7.6.0 => 7.6.0
eslint: ^7.26.0 => 7.29.0
eslint-config-prettier: ^8.3.0 => 8.3.0
eslint-plugin-prettier: ^3.4.0 => 3.4.0
fake-basic-project:  1.0.0
fake-project:  1.0.0
husky: ^7.0.0 => 7.0.0
jest: ^27.0.6 => 27.0.6
jest-preset-angular: ^9.0.6 => 9.0.6
jwt-decode: ^3.1.2 => 3.1.2
lib:  0.0.1
ng-mocks: ^12.4.0 => 12.4.0
ngx-permissions: ^8.1.1 => 8.1.1
normalize.css: ^8.0.1 => 8.0.1
prettier: ^2.3.2 => 2.3.2
prettier-eslint: ^12.0.0 => 12.0.0
rxjs: ~6.6.0 => 6.6.7 (7.3.0)
rxjs/ajax:  undefined ()
rxjs/fetch:  undefined ()
rxjs/internal-compatibility:  undefined ()
rxjs/operators:  undefined ()
rxjs/testing:  undefined ()
rxjs/webSocket:  undefined ()
sonarqube-scanner: ^2.8.1 => 2.8.1
start-server-and-test: ^1.12.5 => 1.12.5
tippy-headless:  0.1.0
tippy.js: ^6.3.1 => 6.3.1
tslib: ^2.1.0 => 2.3.0 (1.14.1, 2.1.0)
typescript: ~4.2.3 => 4.2.4 (4.3.5, 3.9.10, 2.2.0)
zone-mix:  undefined ()
zone-node:  undefined ()
zone-testing:  undefined ()
zone.js: ~0.11.4 => 0.11.4
zone.js/async-test:  undefined ()
zone.js/async-test.min:  undefined ()
zone.js/fake-async-test:  undefined ()
zone.js/fake-async-test.min:  undefined ()
zone.js/jasmine-patch:  undefined ()
zone.js/jasmine-patch.min:  undefined ()
zone.js/long-stack-trace-zone:  undefined ()
zone.js/long-stack-trace-zone.min:  undefined ()
zone.js/mocha-patch:  undefined ()
zone.js/mocha-patch.min:  undefined ()
zone.js/proxy:  undefined ()
zone.js/proxy.min:  undefined ()
zone.js/sync-test:  undefined ()
zone.js/sync-test.min:  undefined ()
zone.js/task-tracking:  undefined ()
zone.js/task-tracking.min:  undefined ()
zone.js/webapis-media-query:  undefined ()
zone.js/webapis-media-query.min:  undefined ()
zone.js/webapis-notification:  undefined ()
zone.js/webapis-notification.min:  undefined ()
zone.js/webapis-rtc-peer-connection:  undefined ()
zone.js/webapis-rtc-peer-connection.min:  undefined ()
zone.js/webapis-shadydom:  undefined ()
zone.js/webapis-shadydom.min:  undefined ()
zone.js/wtf:  undefined ()
zone.js/wtf.min:  undefined ()
zone.js/zone-bluebird:  undefined ()
zone.js/zone-bluebird.min:  undefined ()
zone.js/zone-error:  undefined ()
zone.js/zone-error.min:  undefined ()
zone.js/zone-legacy:  undefined ()
zone.js/zone-legacy.min:  undefined ()
zone.js/zone-patch-canvas:  undefined ()
zone.js/zone-patch-canvas.min:  undefined ()
zone.js/zone-patch-cordova:  undefined ()
zone.js/zone-patch-cordova.min:  undefined ()
zone.js/zone-patch-electron:  undefined ()
zone.js/zone-patch-electron.min:  undefined ()
zone.js/zone-patch-fetch:  undefined ()
zone.js/zone-patch-fetch.min:  undefined ()
zone.js/zone-patch-jsonp:  undefined ()
zone.js/zone-patch-jsonp.min:  undefined ()
zone.js/zone-patch-message-port:  undefined ()
zone.js/zone-patch-message-port.min:  undefined ()
zone.js/zone-patch-promise-test:  undefined ()
zone.js/zone-patch-promise-test.min:  undefined ()
zone.js/zone-patch-resize-observer:  undefined ()
zone.js/zone-patch-resize-observer.min:  undefined ()
zone.js/zone-patch-rxjs:  undefined ()
zone.js/zone-patch-rxjs-fake-async:  undefined ()
zone.js/zone-patch-rxjs-fake-async.min:  undefined ()
zone.js/zone-patch-rxjs.min:  undefined ()
zone.js/zone-patch-socket-io:  undefined ()
zone.js/zone-patch-socket-io.min:  undefined ()
zone.js/zone-patch-user-media:  undefined ()
zone.js/zone-patch-user-media.min:  undefined ()
npmGlobalPackages:
npm: 7.16.0
yarn: 1.22.10
@manfredsteyer
Copy link
Owner

Thanks for this info. Will look into it asap.

@jeroenheijmans jeroenheijmans added bug For tagging faulty or unexpected behavior. investigation-needed Indication that the maintainer or involved community members may need to investigate more. labels Aug 22, 2021
@valdian
Copy link

valdian commented Aug 23, 2021

We currently use version 10.0.3 of this library and after the Angular upgrade from 12.1.1 to 12.2.1, we also have this problem. I approve, that everything works when build optimization is turned off.

I noticed that the code challenge (which is appended to the authorization URL query) has always the same value, although the code verifier is different each time. This computation of the hash returns always agnmZ7tnroU8bvNypU_1OlEOUn-bBWiMH4PZq1vgzRk , but I do not know why:

const challengeRaw = await this.crypto.calcHash(verifier, 'sha-256');

@manfredsteyer
Copy link
Owner

Hi,

thanks for this issue. I have a hard time reproducing this issue.

  • Does it work with the newest 12.2.2 version of Angular and the Angular CLI?
  • Can you please update the auth configuration of this example so that it shows this very issue?

[1] https://github.com/manfredsteyer/auth-demo.git

@ajurge
Copy link

ajurge commented Aug 23, 2021

We have Angular 12.2.2 and angular-oauth2-oidc: 12.0.2, and it does not work.
I tested Angular 11.2.14 and angular-oauth2-oidc: 12.0.2, and that worked.
I have also tested Angular 12.1.4 and angular-oauth2-oidc: 12.0.2, and that worked.

@manfredsteyer
Copy link
Owner

While I cannot reproduce it, I did an "educated guess".

Can you install the temp. version 12.0.2-issue.1120 and try it again?

npm i angular-oauth2-oidc@12.0.2-issue.1120

@LeonEck
Copy link

LeonEck commented Aug 23, 2021

The version you provided didn't fix the issue in my projects.

I debugged this a bit already when angular 12.2 was released since I was convinced at the time that the problem was to be found in my projects. Here are some of my notes and findings:

  • I think you are on the right path when looking at the sha256 implementation. While comparing different builds the common error was always a messed up hash function: https://github.com/manfredsteyer/angular-oauth2-oidc/blob/master/projects/lib/src/token-validation/js-sha256.js#L264
    • In a broken build it uses the variable K and reassigns new values to it, while in fact K is a set globally to an array of numbers and should not be changed. In a correct build K is only read.
  • In some of my projects it works fine. In others, it doesn't.
  • I am unable to reproduce it in a small application. Only when I add a lot of code and dependencies am I able to trigger the bug.
    • In one of my applications I can toggle between working and broken builds by simply adding or removing one variable somewhere in the project.
  • So far I was unable to reproduce this without using private dependencies, that's why I am currently unable to share a reproduction.
  • Running ng serve --configuration=production was not always enough to trigger the bug. I used https://www.npmjs.com/package/serve to consistently see the bug in a production build.
  • I used different versions of @angular-devkit/build-angular to see where the bug was "introduced". It happened with the usage of esbuild. I can pinpoint it to the exact commit. I even went into the sources and commented out the esbuild call so it only ran terser. In that case, everything is fine.
    • This leads me to the question of who is responsible for fixing this issue? Is it a bug in the way angular calls esbuild? Is it esbuild that messes up? Or is this library including js-sha256 in a way that was never really supposed to work?

For anyone looking for a workaround, you can implement your own HashHandler:

@Injectable()
class CustomHashHandler implements HashHandler {
  async calcHash(valueToHash: string, algorithm: string): Promise<string> {
    const encoder = new TextEncoder();
    const data = encoder.encode(valueToHash);
    const hashArray = await window.crypto.subtle.digest(algorithm, data);
    return this.toHashString(hashArray);
  }

  toHashString(buffer: ArrayBuffer) {
    const byteArray = new Uint8Array(buffer);
    let result = '';
    for (const e of byteArray) {
      result += String.fromCharCode(e);
    }
    return result;
  }
}

// Put this into the providers of your app.module:
{ provide: HashHandler, useClass: CustomHashHandler },

I looked through older issues of this library and saw that the reason you are no longer using the code above is, that crypto is only available in a secure context (localhost, https).

PS: Are you aware that you published "12.0.2-issue.1120" as the latest version to npm?

@ajurge
Copy link

ajurge commented Aug 23, 2021

I can confirm that the version 12.0.2-issue.1120 did not fix the issue.

@manfredsteyer
Copy link
Owner

Thanks for your analysis. Can you please retry with version

12.0.2-issue.1120.1

@ajurge
Copy link

ajurge commented Aug 23, 2021

12.0.2-issue.1120.1 does not work unfortunately.

@LeonEck
Copy link

LeonEck commented Aug 24, 2021

12.0.2-issue.1120.1 doesn't work for me either.

@medixcare
Copy link

Thank you for this issue!
The weird thing I was experiencing: Running everything locally in dev build -> IdentityServer says: Transformed code verifier does not match code challenge
Running a production build an my testserver -> everything is perfect

I recently updated to angular 11 and am currently checking against different versions of this library

@manfredsteyer
Copy link
Owner

Seems like the optimizer used by the CLI is destroying this. The lib for calculating sha256 is still using plain javascript and commonjs. Seems like the optimizer has issues with it.

I've completely switched it out now. Can you please give this version a try:

12.0.2-issue.1120.2

Best wishes,
Manfred

@LeonEck
Copy link

LeonEck commented Aug 24, 2021

"12.0.2-issue.1120.2" seems to fix the issue. All of my projects work with that version.

@ajurge
Copy link

ajurge commented Aug 24, 2021

I can confirm that 12.0.2-issue.1120.2 and Angular 12.2.2 worked for me also. Thanks for your effort to fix it.
I did not test previous Angular versions.

@manfredsteyer
Copy link
Owner

Awesome. Thanks for your help with this. Will release it as 12.1 very soon.

@Lonli-Lokli
Copy link

@manfredsteyer original cause seems like be already fixed angular/angular-cli#21654 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug For tagging faulty or unexpected behavior. investigation-needed Indication that the maintainer or involved community members may need to investigate more.
Projects
None yet
Development

No branches or pull requests

8 participants