Skip to content
This repository has been archived by the owner on Jul 26, 2022. It is now read-only.

Commit

Permalink
Added support for an includes operator for assertClaims (which could …
Browse files Browse the repository at this point in the history
…be arrays or space separated strings) (#436)

* Added support for an includes operator for arrays (like groups) and space separated strings (like scopes)

* Refactored unit tests. Separated out those that require api calls and those that use a stub.

* Moved asserted claims business logic into its own class.

* Added documentation for the new .includes feature

* changed let to const per @mraible
  • Loading branch information
dogeared authored and swiftone committed May 28, 2019
1 parent fb7f2ee commit 213e092
Show file tree
Hide file tree
Showing 3 changed files with 397 additions and 158 deletions.
34 changes: 33 additions & 1 deletion packages/jwt-verifier/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,39 @@ const verifier = new OktaJwtVerifier({
});
```

Validation will fail and an error returned if an access token does not have the configured claim.
Validation fails and an error is returned if an access token does not have the configured claim.

For more complex use cases, you can ask the verifier to assert that a claim includes one or more values. This is useful for array type claims as well as claims that have space-separated values in a string.

You use the form: `<claim name>.includes` in the `assertClaims` object with an array of values to validate.

For example, if you want to assert that an array claim named `groups` includes (at least) `Everyone` and `Another`, you'd write code like this:

```javascript
const verifier = new OktaJwtVerifier({
issuer: ISSUER,
clientId: CLIENT_ID,
assertClaims: {
'groups.includes': ['Everyone', 'Another']
}
});
```

If you want to assert that a space-separated string claim name `scp` includes (at least) `promos:write` and `promos:delete`, you'd write code like this:

```javascript
const verifier = new OktaJwtVerifier({
issuer: ISSUER,
clientId: CLIENT_ID,
assertClaims: {
'scp.includes': ['promos:write', 'promos:delete']
}
});
```

The values you want to assert are included are always represented as an array (the right side of the expression). The claim that you're checking against (the left side of the expression) can have either an array (like `groups`) or a space-separated list in a string (like `scp`) as its value type.

NOTE: Currently, `.includes` is the only supported claim operator.

## Caching & Rate Limiting

Expand Down
60 changes: 52 additions & 8 deletions packages/jwt-verifier/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,47 @@ const {
assertClientId
} = require('@okta/configuration-validation');

class AssertedClaimsVerifier {
constructor() {
this.errors = [];
}

extractOperator(claim) {
const idx = claim.indexOf('.');
if (idx >= 0) {
return claim.substring(idx + 1);
}
return undefined;
}

extractClaim(claim) {
const idx = claim.indexOf('.');
if (idx >= 0) {
return claim.substring(0, idx);
}
return claim;
}

isValidOperator(operator) {
// may support more operators in the future
return !operator || operator === 'includes'
}

checkAssertions(op, claim, expectedValue, actualValue) {
if (!op && actualValue !== expectedValue) {
this.errors.push(`claim '${claim}' value '${actualValue}' does not match expected value '${expectedValue}'`);
} else if (op === 'includes' && Array.isArray(expectedValue)) {
expectedValue.forEach(value => {
if (!actualValue || !actualValue.includes(value)) {
this.errors.push(`claim '${claim}' value '${actualValue}' does not include expected value '${value}'`);
}
})
} else if (op === 'includes' && (!actualValue || !actualValue.includes(expectedValue))) {
this.errors.push(`claim '${claim}' value '${actualValue}' does not include expected value '${expectedValue}'`);
}
}
}

class OktaJwtVerifier {
constructor(options = {}) {
// Assert configuration
Expand Down Expand Up @@ -49,16 +90,19 @@ class OktaJwtVerifier {
}
jwt.claims = jwt.body;
delete jwt.body;
const errors = [];
for (let claim of Object.keys(this.claimsToAssert)) {
const actualValue = jwt.claims[claim];
const expectedValue = this.claimsToAssert[claim];
if (actualValue !== expectedValue) {
errors.push(`claim '${claim}' value '${actualValue}' does not match expected value '${expectedValue}'`);
let assertedClaimsVerifier = new AssertedClaimsVerifier();
for (let key of Object.keys(this.claimsToAssert)) {
const expectedValue = this.claimsToAssert[key];
let operator = assertedClaimsVerifier.extractOperator(key);
if (!assertedClaimsVerifier.isValidOperator(operator)) {
return reject(new Error(`operator: '${operator}' invalid. Supported operators: 'includes'.`));
}
let claim = assertedClaimsVerifier.extractClaim(key);
const actualValue = jwt.claims[claim];
assertedClaimsVerifier.checkAssertions(operator, claim, expectedValue, actualValue)
}
if (errors.length) {
return reject(new Error(errors.join(', ')));
if (assertedClaimsVerifier.errors.length) {
return reject(new Error(assertedClaimsVerifier.errors.join(', ')));
}
resolve(jwt);
});
Expand Down
Loading

0 comments on commit 213e092

Please sign in to comment.