auth0 / node-jsonwebtoken

JsonWebToken implementation for node.js http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
MIT License
17.71k stars 1.23k forks source link

Verify: allow passing a function to `audience` #799

Open codynguyen opened 2 years ago

codynguyen commented 2 years ago

Describe the problem you'd like to have solved

We have a use case where our audience list comes from AWS Parameter Store and could be changed on the fly without having to restart the whole app.

Describe the ideal solution

It would be ideal to be able to pass a function that return the audience(s). Something like this;

// This could be updated elsewhere. 
// Could be an array ['audience1', 'audience2'] or just a string.
let audiences = []; ;

jwt.verify(token, secretOrPublicKey, {
  audience: function() {
    return audiences; return the reference to the list.
  }
});

Alternatives and current work-arounds

We actually are not calling jwt.verify() directly but uses jwt-express to create a middleware for token verification. jwt-express passes through the audience option to jwt.verify() internally. Here's what we currently have:

app.use(jwtExpress({ audience: audiences }));

The middleware only gets the copy of the list at the time the app starts. We could wrap the jwtExpress middleware inside another middleware that always returns the current copy of the audiences list, but it would be an ugly solution.

chaiwa-berian commented 2 years ago

Also, I had an issue where if audience in the token is audience: ["string1", "string2"], when I pass audience: ["string1","string3"] to the verify options it still passes, and it only fails if both strings are wrong like audience: ["string3","string4"] not when one of them is wrong. And I looked at the test, and it looks like it is the intended behavior but my use case requires that all elements of the array match. And why would the audience be okay if one of them is invalid, or am I using the audience wrongly?

Or am I miss-quoting the specs here?

The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.

Airdevelopments commented 1 year ago

Also, I had an issue where if audience in the token is audience: ["string1", "string2"], when I pass audience: ["string1","string3"] to the verify options it still passes, and it only fails if both strings are wrong like audience: ["string3","string4"] not when one of them is wrong. And I looked at the test, and it looks like it is the intended behavior but my use case requires that all elements of the array match. And why would the audience be okay if one of them is invalid, or am I using the audience wrongly?

Or am I miss-quoting the specs here?

The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.

As I understand it, the current behavior is as stated in your quote.

Heres an example use case:

  1. Authorization Server signs a jwt with audience ["app1", "app2"], indicating that this jwt is intended for use with an application that identifies itself as "app1" or "app2"
  2. Token is used to talk to app1. Then app1 checks that the provided token is actually intended for itself by checking for "app1" in the audience field

Now Imagine we introduce an audience "any_app".

  1. Authorization Server signs a jwt with audience ["any_app"], indicating that this jwt may be used with any application identifying with "any_app"
  2. Token is used to talk to app2. Then app2 checks that the provided token is actually intended for itself by checking for "app2" or "any_app" in the audience field (verify audience ["app2", "any_app"])

Granted this latter use case does introduce its own risk as access to new apps may be granted unintentionally, cause of the lack of explicity.

Another use-case I could think of would be a migration / renaming of an audience, to temporarily still allow older jwts to pass.