awslabs / aws-jwt-verify

JS library for verifying JWTs signed by Amazon Cognito, and any OIDC-compatible IDP that signs JWTs with RS256, RS384, RS512, ES256, ES384, and ES512
Apache License 2.0
621 stars 44 forks source link

feat: allow array of strings as scope #83

Closed dbryar closed 1 year ago

dbryar commented 2 years ago

Issue #, if available:

Description of changes: Allows the scope claim to be made up of an array of strings, as there are some older IDPs still in use that do not correctly use space delimited strings.

This PR still ensures the scope is an array of strings, and handles both string or array-of-string scopes

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

elorzafe commented 2 years ago

@dbryar

Thanks for your PR, we are a little hesitant on adding this because is not part of the spec. But we want to know more about your use case?

Which IDPs you are using that have this behavior?

Can you use customJwtCheck option? https://github.com/awslabs/aws-jwt-verify#custom-jwt-and-jwk-checks

dbryar commented 2 years ago

Thanks for the review. Doing some digging and the IDP is an old Microsoft Authentication Service implemented prior to the release of .NET Core Identity. It is obsolete, I agree, but it is still in production use (for now). Prior to standardization, a scope claim could be an array of strings according to my limited research.

We are implementing custom authorizer in API Gateway using a Lambda Function that accepts multiple tokens from multiple IDPs based on partnerships established with other vendors that wish to leverage our service with their own white labelling. Thus, we have to accept their (non-standard) tokens while we await those IDPs to be brought up to date.

dbryar commented 2 years ago

At present, the token fails header checks due to

if (typeof payload.scope !== "string") {
   throw new JwtParseError("JWT payload scope claim is not a string");
}

So I have modified that check to first check for an Array, and then for strings if true

ottokruse commented 2 years ago

Given it is non-standard, it feels most fair at this point to solve it using a customJwtCheck, and not add support in the library:

import { JwtRsaVerifier } from "aws-jwt-verify";
import { assertStringArraysOverlap } from "aws-jwt-verify/assert";
import { JwtInvalidScopeError } from "aws-jwt-verify/error";

const verifier = JwtRsaVerifier.create({
  issuer: "changeme",
  audience: "changeme",
  jwksUri: "changeme",
  customJwtCheck: ({ payload }) => {
    assertStringArraysOverlap(
      "Scope",
      Array.isArray(payload.scope) ? payload.scope : payload.scope.split(" "),
      ["the", "list", "of", "scopes", "you", "allow"],
      JwtInvalidScopeError
    );
  },
});

Does that make sense?

ottokruse commented 2 years ago

Belay that, would still run into the "JWT payload scope claim is not a string" error, that check is done before customJwtChecks.

ottokruse commented 2 years ago

FYI we are discussing this internally, please bear with us.

hakanson commented 2 years ago

tl;dr - we discussed and are not inclined to make this change

Our discussion on this focused around the topics of standards compliance, minimizing the ability to use library incorrectly, and backwards compatibility.

Therefore, there are too many concerns to adopt this change.

ottokruse commented 1 year ago

Thanks @dbryar for your effort anyway! (We might revisit if this issue turns out to be widespread)