CVE-2021-32701: Incorrect Authorization (CWE-863)
value: ORY Oathkeeper is an Identity & Access Proxy (IAP) and Access Control Decision API that authorizes HTTP requests based on sets of Access Rules. When you make a request to an endpoint that requires the scope `foo` using an access token granted with that `foo` scope, introspection will be valid and that token will be cached. The problem comes when a second requests to an endpoint that requires the scope `bar` is made before the cache has expired. Whether the token is granted or not to the `bar` scope, introspection will be valid. A patch will be released with `v0.38.12-beta.1`. Per default, caching is disabled for the `oauth2_introspection` authenticator. When caching is disabled, this vulnerability does not exist. The cache is checked in [`func (a *AuthenticatorOAuth2Introspection) Authenticate(...)`](https://github.com/ory/oathkeeper/blob/6a31df1c3779425e05db1c2a381166b087cb29a4/pipeline/authn/authenticator_oauth2_introspection.go#L152). From [`tokenFromCache()`](https://github.com/ory/oathkeeper/blob/6a31df1c3779425e05db1c2a381166b087cb29a4/pipeline/authn/authenticator_oauth2_introspection.go#L97) it seems that it only validates the token expiration date, but ignores whether the token has or not the proper scopes. The vulnerability was introduced in PR #424. During review, we failed to require appropriate test coverage by the submitter which is the primary reason that the vulnerability passed the review process.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 7.5
Published: 2021-06-22T20:15Z
Repository: https://github.com/ory/oathkeeper
Language: Go
Files changed: .circleci/config.yml, Makefile, docs/docs/CHANGELOG.md, docs/docs/pipeline/authn.md, driver/configuration/provider.go, pipeline/authn/authenticator_oauth2_introspection.go, pipeline/authn/authenticator_oauth2_introspection_test.go
Commit date: 2021-06-22 15:02:29 +0200
Commit message: Merge pull request from GHSA-qvp4-rpmr-xwrr

This patch addresses a security vulnerability which would bypass token claim validation once a token is in the cache.

For more information please refer to https://github.com/ory/oathkeeper/security/advisories/GHSA-qvp4-rpmr-xwrr
Diff: diff --git a/.circleci/config.yml b/.circleci/config.yml
index 36b8357..29c5149 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -28,12 +28,13 @@ jobs:
       - goreleaser/install
       - goreleaser/check
       - golangci/install
       - golangci/lint
       - run: go install github.com/ory/go-acc github.com/mattn/goveralls
-      - run: go-acc -o coverage.txt ./... -- -failfast -timeout=20m
-      - run: test -z "$CIRCLE_PR_NUMBER" && goveralls -service=circle-ci -coverprofile=coverage.txt -repotoken=$COVERALLS_REPO_TOKEN || echo "forks are not allowed to push to coveralls"
+      - run: go-acc -o coverage.out ./... -- -failfast -timeout=20m
+      - run: |
+          bash <(curl -s https://codecov.io/bash)
       - run: ./test/e2e/run.sh
       - run: ./test/reload/run.sh
 
 workflows:
   "test, build, and relase":
diff --git a/Makefile b/Makefile
index 14e09ad..0f25222 100644
--- a/Makefile
+++ b/Makefile
@@ -16,10 +16,13 @@ define make-go-dependency
   .bin/$(notdir $1): go.sum go.mod
 		GOBIN=$(PWD)/.bin/ go install $1
 endef
 $(foreach dep, $(GO_DEPENDENCIES), $(eval $(call make-go-dependency, $(dep))))
 
+node_modules: package.json package-lock.json
+		npm i
+
 .bin/clidoc: go.mod
 		go build -o .bin/clidoc ./cmd/clidoc/.
 
 # Formats the code
 .PHONY: format
diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md
index a63856b..f4cc90e 100644
--- a/docs/docs/CHANGELOG.md
+++ b/docs/docs/CHANGELOG.md
@@ -23,20 +23,20 @@ custom_edit_url: null
   AuthenticationSession ([#717](https://github.com/ory/oathkeeper/issues/717))
   ([b3d117b](https://github.com/ory/oathkeeper/commit/b3d117b5d7de02cc1e3ab965328cf6c7995f8a6e)):
   The remote authorizers may have useful context from user's permissions. So
   with this changes, custom authorizers using remote and remote_json can return
   some useful headers to be forward into the AuthenticationSession, meaning that
-  these headers will be passed to upstream services.
-  For example, an user containing scopes/branches inside an organization profile
-  has some level of data addressed to him. In this case, the upstream service
-  need to know that, and "filter" the data according to his "branch_id". The
-  permission that is given to the user (and the remote authorizers manages) has
-  a record of the "branch_id", for the following responses will be returned as
-  status code 200 (if granted) and containing a header like X-Branch-Id.
-  The upstream service receives the X-Branch-Id and does your thing.
-  The configuration requires to configure a list of "allowed headers" returning
-  from remote authorizer, that will be accepted in the pipeline.
+  these headers will be passed to upstream services. For example, an user
+  containing scopes/branches inside an organization profile has some level of
+  data addressed to him. In this case, the upstream service need to know that,
+  and "filter" the data according to his "branch_id". The permission that is
+  given to the user (and the remote authorizers manages) has a record of the
+  "branch_id", for the following responses will be returned as status code 200
+  (if granted) and containing a header like X-Branch-Id. The upstream service
+  receives the X-Branch-Id and does your thing. The configuration requires to
+  configure a list of "allowed headers" returning from remote authorizer, that
+  will be accepted in the pipeline.
 - Add request header in match context
   ([#719](https://github.com/ory/oathkeeper/issues/719))
   ([22b0dbe](https://github.com/ory/oathkeeper/commit/22b0dbe6495b9f57206dc8fb2335e1c81906e27d)),
   closes [#512](https://github.com/ory/oathkeeper/issues/512)
 - Improved JWT Authorizer JWKs fetching
@@ -102,13 +102,15 @@ Web Token debuggability, and resolves other issues.
 
 ### Documentation
 
 - Add dotnet sdk ([#683](https://github.com/ory/oathkeeper/issues/683))
   ([05ae925](https://github.com/ory/oathkeeper/commit/05ae9259414882015f30cc8d5c01e64afb65afbc)):
+
   - docs: add dotnet sdk
 
   - docs: add dotnet sdk versioned
+
 - Change forum to discussions readme
   ([#690](https://github.com/ory/oathkeeper/issues/690))
   ([d942c04](https://github.com/ory/oathkeeper/commit/d942c043aa2370b87e0dd822440ad39d809755f9))
 - Consistent authz headers
   ([#699](https://github.com/ory/oathkeeper/issues/699))
@@ -135,12 +137,12 @@ Web Token debuggability, and resolves other issues.
   ([2405810](https://github.com/ory/oathkeeper/commit/2405810a839b9d3015655ced492097d0f130a06f)),
   closes [#677](https://github.com/ory/oathkeeper/issues/677)
 - Additional JWT auth debug information
   ([#681](https://github.com/ory/oathkeeper/issues/681))
   ([d08ab50](https://github.com/ory/oathkeeper/commit/d08ab5034b80736701fb38ee1e55d12c63fd06b2)),
-  closes [#668](https://github.com/ory/oathkeeper/issues/668):
-  JWT Claims added to error details field.
+  closes [#668](https://github.com/ory/oathkeeper/issues/668): JWT Claims added
+  to error details field.
 - Adds audience into Extra at oAuth2 introspection
   ([#480](https://github.com/ory/oathkeeper/issues/480))
   ([c9faecc](https://github.com/ory/oathkeeper/commit/c9faecc5d32800537f07e8e05f9c8872e609c3f9))
 - Global docs sidebar and added cloud pages
   ([#705](https://github.com/ory/oathkeeper/issues/705))
@@ -166,13 +168,13 @@ autogen: pin v0.38.9-beta.1.pre.3 release commit
 ### Bug Fixes
 
 - Make glob patterns match only one path segment.
   ([#664](https://github.com/ory/oathkeeper/issues/664))
   ([c711aac](https://github.com/ory/oathkeeper/commit/c711aacc5fc29664e3825e087557e7baf4e47aa8)),
-  closes [#630](https://github.com/ory/oathkeeper/issues/630):
-  This makes `/` also a separator as well as the presumably default value of
-  `.`. This allows using <\*> for matching only one path segment.
+  closes [#630](https://github.com/ory/oathkeeper/issues/630): This makes `/`
+  also a separator as well as the presumably default value of `.`. This allows
+  using <\*> for matching only one path segment.
 
 ### Code Generation
 
 - Pin v0.38.9-beta.1.pre.3 release commit
   ([7725554](https://github.com/ory/oathkeeper/commit/77255541cafc25fd7adc242aed149837f3fa3141))
@@ -425,15 +427,14 @@ This release stabilizes several features and resolves a couple of bugs.
 - Add and automate version schema
   ([7ab4012](https://github.com/ory/oathkeeper/commit/7ab40128352eb4e6639fe4828da7bdd3690e327e))
 - Add url_param config option to redirect error handler.
   ([#520](https://github.com/ory/oathkeeper/issues/520))
   ([b5bb3bc](https://github.com/ory/oathkeeper/commit/b5bb3bc6b88ea8b26d53f03477fce1b74f113b97)),
-  closes [#511](https://github.com/ory/oathkeeper/issues/511):
-  This change introduces a url_param config option for redirect error handler.
-  If it contains a url paramter name, the redirect url will have this parameter
-  set, containing the current url (from which Oathkeeper has redirected the
-  user).
+  closes [#511](https://github.com/ory/oathkeeper/issues/511): This change
+  introduces a url_param config option for redirect error handler. If it
+  contains a url paramter name, the redirect url will have this parameter set,
+  containing the current url (from which Oathkeeper has redirected the user).
   This can be useful in passing the return_to url to Kratos, so user can be
   redirected to the page they initially wanted to access after a successfull
   sign in.
 - Log invalid credentials on info level instead of error/warning
   ([#517](https://github.com/ory/oathkeeper/issues/517))
@@ -568,13 +569,12 @@ No backwards incompatible changes have been introduced.
   ([0e99045](https://github.com/ory/oathkeeper/commit/0e990459104c7683764f4ed0e0a6b7162b57cd57)):
   We currently copy all original request headers send to the decission endpoint
   back. This can include the Content-Length header which describes the request
   body or response. Including the original request Content-Length causes issues
   for the decission endpoint client if the response body doesn't match the exact
-  size.
-  This change makes sure the Content-Length doesn't get included in the response
-  body and adds a test to prevent future regressions.
+  size. This change makes sure the Content-Length doesn't get included in the
+  response body and adds a test to prevent future regressions.
 - Respect retry in token introspection
   ([#410](https://github.com/ory/oathkeeper/issues/410))
   ([88f7b69](https://github.com/ory/oathkeeper/commit/88f7b69c9ff252ddc3dbe960155883ec98115fd0))
 - Update install.sh script
   ([#429](https://github.com/ory/oathkeeper/issues/429))
@@ -642,22 +642,21 @@ No backwards incompatible changes have been introduced.
 
 ### Features
 
 - Add cache to hydrator ([#418](https://github.com/ory/oathkeeper/issues/418))
   ([1ae6e7a](https://github.com/ory/oathkeeper/commit/1ae6e7a958d602533f54cada5d231bcf1bace093)),
-  closes [#417](https://github.com/ory/oathkeeper/issues/417):
-  This patch introduces new configuration parameters that allow the hydrator
-  mutator to cache requests.
+  closes [#417](https://github.com/ory/oathkeeper/issues/417): This patch
+  introduces new configuration parameters that allow the hydrator mutator to
+  cache requests.
 - Add new remote authorizer that uses request body and headers
   ([#416](https://github.com/ory/oathkeeper/issues/416))
   ([3a20637](https://github.com/ory/oathkeeper/commit/3a206376c0ec4d72d5d6ec66c2d738199a24e0c6)):
   This pull request implements a new authorizer that sends the original request
   body as body to the remote endpoint. This allows the remote endpoint to take
-  the body into account in its decision.
-  The current remote_json authorizer does not have the ability to send the
-  request body of the request to authorize. This means this cannot be taken into
-  account while checking permissions.
+  the body into account in its decision. The current remote_json authorizer does
+  not have the ability to send the request body of the request to authorize.
+  This means this cannot be taken into account while checking permissions.
   Providing the request body as part of the JSON payload won't always work as
   JSON cannot handle binary data.
 - Add prometheus docs ([#427](https://github.com/ory/oathkeeper/issues/427))
   ([117ee6a](https://github.com/ory/oathkeeper/commit/117ee6a4c53035651f41a5bb4a9afe3c8b0c7438))
 - Add prometheus endpoint providing basic request metrics
@@ -728,26 +727,24 @@ https://github.com/ory/oathkeeper/pull/358/commits/fd16ceb230a1b14ebb01a147d2d70
   ([#385](https://github.com/ory/oathkeeper/issues/385))
   ([8f63eda](https://github.com/ory/oathkeeper/commit/8f63eda6370fb389307cd8b313437292965a2107))
 - SendOAuth2 introspection scope only when strategy is none
   ([#379](https://github.com/ory/oathkeeper/issues/379))
   ([5e0c8dc](https://github.com/ory/oathkeeper/commit/5e0c8dcdc4a64662f59372a488ec633bcdbc0d85)),
-  closes [#377](https://github.com/ory/oathkeeper/issues/377):
-  This patch removes the `scope` key from the OAuth2 Introspection request body
-  when a scope strategy other than `none` is set for the OAuth2 Introspection
-  handler. If the scope strategy is `none`, the `scope` key is included in the
-  body.
+  closes [#377](https://github.com/ory/oathkeeper/issues/377): This patch
+  removes the `scope` key from the OAuth2 Introspection request body when a
+  scope strategy other than `none` is set for the OAuth2 Introspection handler.
+  If the scope strategy is `none`, the `scope` key is included in the body.
 - Token expiration error in tests
   ([#390](https://github.com/ory/oathkeeper/issues/390))
   ([9c07a73](https://github.com/ory/oathkeeper/commit/9c07a7349cdf560c0ba29a637aaec93021757d27))
 - **docker:** Improve docker-compose example
   ([#325](https://github.com/ory/oathkeeper/issues/325))
   ([1247381](https://github.com/ory/oathkeeper/commit/12473815dad3fcbc57ea102dd982170960adb9f6)),
-  closes [#324](https://github.com/ory/oathkeeper/issues/324):
-  Add a new file 'Dockerfile-dc' which will primarily be used by Docker Compose
-  to build docker images. Unlike the existing Dockerfile which depends on the
-  Makefile to build the binary, this Dockerfile copies the source code and
-  builds the binary.
+  closes [#324](https://github.com/ory/oathkeeper/issues/324): Add a new file
+  'Dockerfile-dc' which will primarily be used by Docker Compose to build docker
+  images. Unlike the existing Dockerfile which depends on the Makefile to build
+  the binary, this Dockerfile copies the source code and builds the binary.
   Oathkeeper has gone through a couple of changes since the initial draft of the
   docker compose file, considering these changes and the newly introduced
   Dockerfile in the previous commit, make these changes to the
   docker-compose.yml:
   1.  Bump the version of the compose file to 3.
@@ -813,12 +810,12 @@ https://github.com/ory/oathkeeper/pull/358/commits/fd16ceb230a1b14ebb01a147d2d70
   ([bec6af0](https://github.com/ory/oathkeeper/commit/bec6af0a9b78a880296fce59eb150ac21ee3d13b))
 - Update forum and chat links
   ([d9eed10](https://github.com/ory/oathkeeper/commit/d9eed10abd43eb41362fcc0f36e47a6f88658835))
 - Update README.md ([#375](https://github.com/ory/oathkeeper/issues/375))
   ([313d2fe](https://github.com/ory/oathkeeper/commit/313d2fe99f699c441e6f8e24abb096e239a17f83)),
-  closes [#374](https://github.com/ory/oathkeeper/issues/374):
-  Fixed link to Envoy configuration page and added link to AWS API Gateway.
+  closes [#374](https://github.com/ory/oathkeeper/issues/374): Fixed link to
+  Envoy configuration page and added link to AWS API Gateway.
 - Updates issue and pull request templates
   ([#382](https://github.com/ory/oathkeeper/issues/382))
   ([484c406](https://github.com/ory/oathkeeper/commit/484c406785c2633feee3cb9179a94147085cadd6))
 
 ### Features
@@ -876,14 +873,13 @@ docs: Regenerate and update changelog
   ([#354](https://github.com/ory/oathkeeper/issues/354))
   ([de36e40](https://github.com/ory/oathkeeper/commit/de36e401134f09762d5815e3fe37d9cb16dd8d81))
 - **rule:** Add glob matching strategy
   ([#334](https://github.com/ory/oathkeeper/issues/334))
   ([5f983ab](https://github.com/ory/oathkeeper/commit/5f983ab118ce784a49a38e6024b99b8791907d4b)),
-  closes [#321](https://github.com/ory/oathkeeper/issues/321):
-  This patch adds the ability to choose a matching strategy and adds a
-  glob-based matching strategy to the available options (regex is still the
-  default).
+  closes [#321](https://github.com/ory/oathkeeper/issues/321): This patch adds
+  the ability to choose a matching strategy and adds a glob-based matching
+  strategy to the available options (regex is still the default).
 
 ### Unclassified
 
 - Update CHANGELOG [ci skip]
   ([8278b9d](https://github.com/ory/oathkeeper/commit/8278b9db8a43c57d4169e232cb9f25ef9257dd8c))
@@ -1018,26 +1014,25 @@ Update CHANGELOG [ci skip]
 - Move to new SDK pipeline (#333)
   ([6940dc8](https://github.com/ory/oathkeeper/commit/6940dc8de74de9c8be9f872df7cf3bc4bc079aa9)),
   closes [#333](https://github.com/ory/oathkeeper/issues/333)
 - authn/cookie_session: Add subject_from modifier (#336)
   ([6723fb8](https://github.com/ory/oathkeeper/commit/6723fb834c386b72e9525d2dfd661e684bd915d3)),
-  closes [#336](https://github.com/ory/oathkeeper/issues/336):
-  The subject_from modifier is a GJSON path that points to the `subject` field.
-  Useful if the upstream API does not return a `{"subject": "..."}` format.
+  closes [#336](https://github.com/ory/oathkeeper/issues/336): The subject_from
+  modifier is a GJSON path that points to the `subject` field. Useful if the
+  upstream API does not return a `{"subject": "..."}` format.
 - authn/cookie_session: Add extra_from modifier (#335)
   ([ee2b9e7](https://github.com/ory/oathkeeper/commit/ee2b9e743f4f6c56563d791947ffb592cc13394e)),
-  closes [#335](https://github.com/ory/oathkeeper/issues/335):
-  The extra_from modifier is a GJSON path that points to the extra field. Useful
-  if the upstream API does not return a `{"subject": "...", "extra": "..."}`
-  format.
+  closes [#335](https://github.com/ory/oathkeeper/issues/335): The extra_from
+  modifier is a GJSON path that points to the extra field. Useful if the
+  upstream API does not return a `{"subject": "...", "extra": "..."}` format.
 - pipeline/authn: Add tests for cookie sources in jwt and oauth2_intro (#330)
   (#331)
   ([7516eed](https://github.com/ory/oathkeeper/commit/7516eedc1ea97242a18225365898e0cbeafcffbf)),
   closes [#330](https://github.com/ory/oathkeeper/issues/330)
   [#331](https://github.com/ory/oathkeeper/issues/331)
-  [#330](https://github.com/ory/oathkeeper/issues/330):
-  Also updates the schemas to add missing cookie config element.
+  [#330](https://github.com/ory/oathkeeper/issues/330): Also updates the schemas
+  to add missing cookie config element.
 
 # [0.34.0-beta.1](https://github.com/ory/oathkeeper/compare/v0.33.1-beta.1...v0.34.0-beta.1) (2019-12-26)
 
 Prepare v0.34.0-beta.1+oryOS.14 release
 
@@ -1054,30 +1049,29 @@ Prepare v0.34.0-beta.1+oryOS.14 release
 
 - Prepare v0.34.0-beta.1+oryOS.14 release
   ([96f77b2](https://github.com/ory/oathkeeper/commit/96f77b24d8adb160d5c2c3db2f2432e206b99c77))
 - pipe/err: Improve IP and MIME matching (#323)
   ([7e6f636](https://github.com/ory/oathkeeper/commit/7e6f6369f4acc33211d78f2acb1036c610286c2c)),
-  closes [#323](https://github.com/ory/oathkeeper/issues/323):
-  Previously, MIME matching respected the request's wildcards which lead to
-  multiple handlers feeling responsible for a particular request. Now, wildcards
-  coming from the HTTP Request itself are interpreted literally.
-  Additionally, ORY Oathkeeper respected the X-Forwarded-For HTTP Header for
-  matching remote IP addresses. This behavior is now turned off by default
-  because clients were able to fake this header otherwise. It can explicitly be
-  turned on by setting
+  closes [#323](https://github.com/ory/oathkeeper/issues/323): Previously, MIME
+  matching respected the request's wildcards which lead to multiple handlers
+  feeling responsible for a particular request. Now, wildcards coming from the
+  HTTP Request itself are interpreted literally. Additionally, ORY Oathkeeper
+  respected the X-Forwarded-For HTTP Header for matching remote IP addresses.
+  This behavior is now turned off by default because clients were able to fake
+  this header otherwise. It can explicitly be turned on by setting
   `config.when.#.request.remote_ip.RespectForwardedForHeader: true`.
 - Add customizable error handlers (#322)
   ([4033321](https://github.com/ory/oathkeeper/commit/4033321b13671de8d0d5a42846a4e19d6065db62)),
   closes [#322](https://github.com/ory/oathkeeper/issues/322)
   [#204](https://github.com/ory/oathkeeper/issues/204)
   [#252](https://github.com/ory/oathkeeper/issues/252)
-  [#119](https://github.com/ory/oathkeeper/issues/119):
-  This patch adds a new feature called error handlers. It allows to define the
-  error handling logic globally and per rule. It is now possible, for example,
-  to return a JSON response for `Accept: application/json` requests and a HTTP
-  Redirect response for requests that are coming from a user.
-  This also resolves several issues, as noted below:
+  [#119](https://github.com/ory/oathkeeper/issues/119): This patch adds a new
+  feature called error handlers. It allows to define the error handling logic
+  globally and per rule. It is now possible, for example, to return a JSON
+  response for `Accept: application/json` requests and a HTTP Redirect response
+  for requests that are coming from a user. This also resolves several issues,
+  as noted below:
 
 # [0.33.1-beta.1](https://github.com/ory/oathkeeper/compare/v0.33.0-beta.1...v0.33.1-beta.1) (2019-12-18)
 
 docs: Incorporates changes from version v0.33.0-beta.1-12-g0dd3fe3 [ci skip]
 
@@ -1101,39 +1095,37 @@ docs: Incorporates changes from version v0.33.0-beta.1-12-g0dd3fe3 [ci skip]
   ([0dd3fe3](https://github.com/ory/oathkeeper/commit/0dd3fe32a4c76b3b2c14a17108521eb51e5e4ff0))
 - Properly merge env vars into pipeline configs (#320)
   ([3e7936a](https://github.com/ory/oathkeeper/commit/3e7936a41150f367003c81c208910fdb77f556d9)),
   closes [#320](https://github.com/ory/oathkeeper/issues/320)
   [#305](https://github.com/ory/oathkeeper/issues/305)
-  [#317](https://github.com/ory/oathkeeper/issues/317):
-  Previously, some keys did not respect the values set in the environment
-  variables.
+  [#317](https://github.com/ory/oathkeeper/issues/317): Previously, some keys
+  did not respect the values set in the environment variables.
 - Add alpine-based Docker image (#318)
   ([815951b](https://github.com/ory/oathkeeper/commit/815951bb039937acc7be3f8b1b2bb06fe9ecac90)),
   closes [#318](https://github.com/ory/oathkeeper/issues/318)
   [#312](https://github.com/ory/oathkeeper/issues/312)
 - Add more details to decision logging (#316)
   ([f60f525](https://github.com/ory/oathkeeper/commit/f60f52538ff6e66ea98afc89c6c6557ab8c5f93f)),
   closes [#316](https://github.com/ory/oathkeeper/issues/316)
   [#244](https://github.com/ory/oathkeeper/issues/244)
-  [#242](https://github.com/ory/oathkeeper/issues/242):
-  Adds details such as the HTTP Method, User Agent, Subject, and other
-  information to the logging output of both the reverse proxy and the decision
-  API.
+  [#242](https://github.com/ory/oathkeeper/issues/242): Adds details such as the
+  HTTP Method, User Agent, Subject, and other information to the logging output
+  of both the reverse proxy and the decision API.
 - Health endpoints now emit TRACE logs
   ([#314](https://github.com/ory/oathkeeper/issues/314))
   ([9036f8e](https://github.com/ory/oathkeeper/commit/9036f8eec3f264f7bcae46b44286367b8521802a)),
-  closes [#283](https://github.com/ory/oathkeeper/issues/283):
-  Remove health endpoints from the logs to make monitoring easier. Setting
-  `log_level` to `trace` will show these calls.
+  closes [#283](https://github.com/ory/oathkeeper/issues/283): Remove health
+  endpoints from the logs to make monitoring easier. Setting `log_level` to
+  `trace` will show these calls.
 - Improve session endpoint debugability
   ([#315](https://github.com/ory/oathkeeper/issues/315))
   ([2718639](https://github.com/ory/oathkeeper/commit/27186396ccff4ee3a7f8f0a4c703263fcc55afae)),
   closes [#300](https://github.com/ory/oathkeeper/issues/300)
 - Resolve matcher cache ([#313](https://github.com/ory/oathkeeper/issues/313))
   ([1519632](https://github.com/ory/oathkeeper/commit/15196326d2436c2d849d955bf5050766ae6dff0c)),
-  closes [#291](https://github.com/ory/oathkeeper/issues/291):
-  A bug caused the rule matcher to not cache the regular expression result.
+  closes [#291](https://github.com/ory/oathkeeper/issues/291): A bug caused the
+  rule matcher to not cache the regular expression result.
 - Use bearer splitting when header is set to Authorization
   ([#311](https://github.com/ory/oathkeeper/issues/311))
   ([464fa31](https://github.com/ory/oathkeeper/commit/464fa319e84953835b71e16360bab3016b8bfc64)),
   closes [#308](https://github.com/ory/oathkeeper/issues/308)
 
@@ -1206,13 +1198,13 @@ docs: Incorporates changes from version v0.32.0-beta.1-13-g1910bbe [ci skip]
   ([#285](https://github.com/ory/oathkeeper/issues/285))
   ([0ef3bce](https://github.com/ory/oathkeeper/commit/0ef3bce92a3c17a6cffc794f2b08859f0852ee5d)),
   closes [#276](https://github.com/ory/oathkeeper/issues/276)
   [#270](https://github.com/ory/oathkeeper/issues/270)
   [#279](https://github.com/ory/oathkeeper/issues/279)
-  [#280](https://github.com/ory/oathkeeper/issues/280):
-  This patch automatically binds environment variables to configuration keys.
-  This patch resolves several issues:
+  [#280](https://github.com/ory/oathkeeper/issues/280): This patch automatically
+  binds environment variables to configuration keys. This patch resolves several
+  issues:
 - Dereference config schema and resolve issues (#282)
   ([8cf6868](https://github.com/ory/oathkeeper/commit/8cf6868b3e925e686769d43c912d5e52c6589a9b)),
   closes [#282](https://github.com/ory/oathkeeper/issues/282)
   [ory/docs#217](https://github.com/ory/docs/issues/217)
   [#234](https://github.com/ory/oathkeeper/issues/234)
@@ -1243,20 +1235,20 @@ docs: Incorporates changes from version v0.30.0-beta.1 [ci skip]
 ### Unclassified
 
 - pipeline/authn: Add token_from config to introspection and jwt (#271)
   ([fc85ac8](https://github.com/ory/oathkeeper/commit/fc85ac854c3fb4cdd96bbae650f7355400431eac)),
   closes [#271](https://github.com/ory/oathkeeper/issues/271)
-  [#257](https://github.com/ory/oathkeeper/issues/257):
-  Add additional optional configuration to jwt and oauth2_introspection
-  authenticators allowing to set from where (which header or query parameter)
-  the token should be received. The configuration is a token_from field in
-  per-rule-configuration, as described in a linked issue.
+  [#257](https://github.com/ory/oathkeeper/issues/257): Add additional optional
+  configuration to jwt and oauth2_introspection authenticators allowing to set
+  from where (which header or query parameter) the token should be received. The
+  configuration is a token_from field in per-rule-configuration, as described in
+  a linked issue.
 - Add migration capabilities
   ([#268](https://github.com/ory/oathkeeper/issues/268))
   ([bc74e72](https://github.com/ory/oathkeeper/commit/bc74e726712c77955d2013979770c2724af17f20)),
-  closes [#266](https://github.com/ory/oathkeeper/issues/266):
-  Adds the ability to modify rules with backwards compatibility.
+  closes [#266](https://github.com/ory/oathkeeper/issues/266): Adds the ability
+  to modify rules with backwards compatibility.
 - Force auth style in oauth2 client credentials authn
   ([#267](https://github.com/ory/oathkeeper/issues/267))
   ([97d7890](https://github.com/ory/oathkeeper/commit/97d789097b47b50117421f8f4ebd32182de4195c)),
   closes [#260](https://github.com/ory/oathkeeper/issues/260)
 - Update UPGRADE.md
@@ -1425,22 +1417,21 @@ ci: Automate schema confiugration sync
   ([997427d](https://github.com/ory/oathkeeper/commit/997427dc03c7efee476f145677b73a42bbc63c89))
 - Do not fatal when immutable value is changed
   ([717d7f7](https://github.com/ory/oathkeeper/commit/717d7f748abe61014653f3c6519c4aef019d1969))
 - Watch configuration and access rule changes (#217)
   ([a078e89](https://github.com/ory/oathkeeper/commit/a078e891e2fe97bdb6ce8a2264f629a179c9116e)),
-  closes [#217](https://github.com/ory/oathkeeper/issues/217):
-  This patch allows oathkeeper to re-load any changes made to the configuraiton
-  file and/or the access rules to be reloaded without a restart.
-  Some configuration keys like serve._, log._, profiling however require a
-  restart.
+  closes [#217](https://github.com/ory/oathkeeper/issues/217): This patch allows
+  oathkeeper to re-load any changes made to the configuraiton file and/or the
+  access rules to be reloaded without a restart. Some configuration keys like
+  serve._, log._, profiling however require a restart.
 - Create FUNDING.yml
   ([d7da8e2](https://github.com/ory/oathkeeper/commit/d7da8e296205e183140c18ba3cc6269334476a2b))
 - Add support for rules in YAML format (#213)
   ([67face6](https://github.com/ory/oathkeeper/commit/67face611b9f19ed9b6606931c9b7a82df769154)),
-  closes [#213](https://github.com/ory/oathkeeper/issues/213):
-  This commit adds support for defining access rules in YAML format, in addition
-  to existing JSON format.
+  closes [#213](https://github.com/ory/oathkeeper/issues/213): This commit adds
+  support for defining access rules in YAML format, in addition to existing JSON
+  format.
 
 # [0.16.0-beta.5](https://github.com/ory/oathkeeper/compare/v0.16.0-beta.4...v0.16.0-beta.5) (2019-06-28)
 
 authenticator: Add cookie session authenticator (#211)
 
@@ -1463,12 +1454,12 @@ authenticator: Add cookie session authenticator (#211)
 - Remove binary license (#208)
   ([3460d65](https://github.com/ory/oathkeeper/commit/3460d65249783ea1eb6558fbe75cec4c72105f5c)),
   closes [#208](https://github.com/ory/oathkeeper/issues/208)
 - Update config.yaml (#204)
   ([effe9c0](https://github.com/ory/oathkeeper/commit/effe9c025c3a25edf88bc3791ec27cb01e128a1a)),
-  closes [#204](https://github.com/ory/oathkeeper/issues/204):
-  There's no DSN in oathkeeper
+  closes [#204](https://github.com/ory/oathkeeper/issues/204): There's no DSN in
+  oathkeeper
 
 # [0.16.0-beta.4](https://github.com/ory/oathkeeper/compare/v0.16.0-beta.3...v0.16.0-beta.4) (2019-05-28)
 
 server: Properly declare negroni middleware (#200)
 
@@ -1497,15 +1488,14 @@ Closes #199
 ### Unclassified
 
 - Properly declare negroni middleware
   ([#200](https://github.com/ory/oathkeeper/issues/200))
   ([9d3dc54](https://github.com/ory/oathkeeper/commit/9d3dc54e1350fa74fb126cc4761462e83d86548f)),
-  closes [#199](https://github.com/ory/oathkeeper/issues/199):
-  Previously, negroni.With was mistakenly used to add middleware onto the stack.
-  The proper method however is negroni.Use.
-  This patch fixes the use of negroni.With and resolves issues around logging
-  and the decisions endpoint.
+  closes [#199](https://github.com/ory/oathkeeper/issues/199): Previously,
+  negroni.With was mistakenly used to add middleware onto the stack. The proper
+  method however is negroni.Use. This patch fixes the use of negroni.With and
+  resolves issues around logging and the decisions endpoint.
 
 # [0.16.0-beta.3](https://github.com/ory/oathkeeper/compare/v0.15.2...v0.16.0-beta.3) (2019-05-19)
 
 ci: Use golang for docs task
 
@@ -1629,14 +1619,14 @@ Signed-off-by: aeneasr <aeneas@ory.sh>
 - Upgrade dependencies ([#163](https://github.com/ory/oathkeeper/issues/163))
   ([f9fdefb](https://github.com/ory/oathkeeper/commit/f9fdefb5dfe9dbff38f0ae96f82e42fea24d1c93))
 - Use scp,scope,scopes in jwt authenticator
   ([#162](https://github.com/ory/oathkeeper/issues/162))
   ([eebc2f4](https://github.com/ory/oathkeeper/commit/eebc2f44e3e42b7af653f91d9345111e0a280401)),
-  closes [#138](https://github.com/ory/oathkeeper/issues/138):
-  Previously, the JWT authenticator only used the "scope" claim to retrieve
-  scope values from a JWT. Now, "scp", "scope", "scopes" are supported as string
-  arrays and strings separated by spaces.
+  closes [#138](https://github.com/ory/oathkeeper/issues/138): Previously, the
+  JWT authenticator only used the "scope" claim to retrieve scope values from a
+  JWT. Now, "scp", "scope", "scopes" are supported as string arrays and strings
+  separated by spaces.
 
 # [0.14.2+oryOS.10](https://github.com/ory/oathkeeper/compare/v0.14.1+oryOS.10...v0.14.2+oryOS.10) (2018-12-13)
 
 ci: Fix docker push arguments in publish task
 
@@ -1790,18 +1780,17 @@ Signed-off-by: aeneasr <aeneas@ory.sh>
   ([59aabfa](https://github.com/ory/oathkeeper/commit/59aabfa4b2554f03f65d618a7d7bf1c98a634da3))
 - Add cookies credentials issuer
   ([032d88e](https://github.com/ory/oathkeeper/commit/032d88ea8dee24506d277d22b7f4aaef2a502fa7))
 - Add endpoint for answering access requests directly
   ([d211641](https://github.com/ory/oathkeeper/commit/d2116410edf1f5089427858727f155bc0aa4313c)),
-  closes [#42](https://github.com/ory/oathkeeper/issues/42):
-  This patch adds endpoint `/judge` to `oathkeeper serve api`. The `/judge`
-  endpoint mimics the behavior of `oathkeeper serve proxy` but instead of
-  forwarding the request to the upstream server, the endpoint answers directly
-  with a HTTP response.
-  The HTTP response returns status code 200 if the request should be allowed and
-  any other status code (e.g. 401, 403) if not.
-  Assuming you are making the following request:
+  closes [#42](https://github.com/ory/oathkeeper/issues/42): This patch adds
+  endpoint `/judge` to `oathkeeper serve api`. The `/judge` endpoint mimics the
+  behavior of `oathkeeper serve proxy` but instead of forwarding the request to
+  the upstream server, the endpoint answers directly with a HTTP response. The
+  HTTP response returns status code 200 if the request should be allowed and any
+  other status code (e.g. 401, 403) if not. Assuming you are making the
+  following request:
   ```
   PUT /judge/my-service/whatever HTTP/1.1
   Host: oathkeeper-api:4456
   User-Agent: curl/7.54.0
   Authorization: bearer some-token
@@ -1828,13 +1817,13 @@ Signed-off-by: aeneasr <aeneas@ory.sh>
   ([b084c32](https://github.com/ory/oathkeeper/commit/b084c3271ab8ca71c9fe766de030572c69057671))
 - Add http proxy timeout config
   ([#132](https://github.com/ory/oathkeeper/issues/132))
   ([b3718ce](https://github.com/ory/oathkeeper/commit/b3718ce56d4bcfe4610806ae6b15382226adab75)):
   Add environment parameters (and description) to configure timeout settings of
-  a server handled proxy requests.
-  It will help prevent a case of unexpected closing a client connection if an
-  upstream request is executing more than default timeout.
+  a server handled proxy requests. It will help prevent a case of unexpected
+  closing a client connection if an upstream request is executing more than
+  default timeout.
 - Add JWT authenticator
   ([61625bc](https://github.com/ory/oathkeeper/commit/61625bccebe0b478b980c66a29894dc1ffe48b0a))
 - Add NodeJS SDK ([#94](https://github.com/ory/oathkeeper/issues/94))
   ([7505b71](https://github.com/ory/oathkeeper/commit/7505b717f28aaec38e07999ffe1f417484e110f7))
 - Adds ability to specify db url via env var in migrate
@@ -1887,13 +1876,13 @@ Signed-off-by: aeneasr <aeneas@ory.sh>
   ([#139](https://github.com/ory/oathkeeper/issues/139))
   ([07eb99b](https://github.com/ory/oathkeeper/commit/07eb99bdb669121bcd27559d9f11d0633f5a8877))
 - Improve compatibility with ORY Hydra 1.0.0-beta.8
   ([#108](https://github.com/ory/oathkeeper/issues/108))
   ([296e012](https://github.com/ory/oathkeeper/commit/296e01254b50b645fae67e51aa668d39652b0778)),
-  closes [#101](https://github.com/ory/oathkeeper/issues/101):
-  This patch improves compatibility with ORY Hydra 1.0.0-beta.8 and updates
-  vendored dependencies.
+  closes [#101](https://github.com/ory/oathkeeper/issues/101): This patch
+  improves compatibility with ORY Hydra 1.0.0-beta.8 and updates vendored
+  dependencies.
 - Improve hydra integration tests
   ([e8b7a58](https://github.com/ory/oathkeeper/commit/e8b7a586c5fcb86efddf57d4f2f97a0ff915b869))
 - Improve refresh subroutines
   ([cc33538](https://github.com/ory/oathkeeper/commit/cc33538f11d9292465bc2fdf0275233b1ff7df9e))
 - Improves cors parsing
@@ -1914,14 +1903,13 @@ Signed-off-by: aeneasr <aeneas@ory.sh>
 - Properly document JWT refresh
   ([#117](https://github.com/ory/oathkeeper/issues/117))
   ([2e024f9](https://github.com/ory/oathkeeper/commit/2e024f91640bf4182d1b1a6fb143d77c523f4596))
 - Properly handle conflicts on PUT and POST
   ([83b591d](https://github.com/ory/oathkeeper/commit/83b591d8cf3d180ad9d48a72bd92ffdb3a8192ac)),
-  closes [#38](https://github.com/ory/oathkeeper/issues/38):
-  Previously, PUT and POST did not result in errors (409) when non-existing
-  resources were modified, or existing resources were created. This patch
-  resolves that.
+  closes [#38](https://github.com/ory/oathkeeper/issues/38): Previously, PUT and
+  POST did not result in errors (409) when non-existing resources were modified,
+  or existing resources were created. This patch resolves that.
 - Refactors Oathkeeper into new ecosystem
   ([#60](https://github.com/ory/oathkeeper/issues/60))
   ([7acfbca](https://github.com/ory/oathkeeper/commit/7acfbcaca36645a984baded2dc3cbb689154ef8c))
 - Refresh rules in api mode
   ([08204e8](https://github.com/ory/oathkeeper/commit/08204e8eb60745d8449b2da9780e460f504710c9))
@@ -1972,13 +1960,13 @@ Signed-off-by: aeneasr <aeneas@ory.sh>
   ([84f1313](https://github.com/ory/oathkeeper/commit/84f131387845a1f0246d40b074d446ec58b014c0))
 - Removes obsolete benchmark
   ([3f259da](https://github.com/ory/oathkeeper/commit/3f259da7766eb6a42b54bb3a6f3ddeb49d9363a1))
 - Resolves an issue with cached matchers
   ([951da25](https://github.com/ory/oathkeeper/commit/951da251e3e862f2d0a1e5076c028a481f0235dd)),
-  closes [#73](https://github.com/ory/oathkeeper/issues/73):
-  This patch resolves an issue where updates would not properly propagate. This
-  caused deleted rules to still be available in the proxy.
+  closes [#73](https://github.com/ory/oathkeeper/issues/73): This patch resolves
+  an issue where updates would not properly propagate. This caused deleted rules
+  to still be available in the proxy.
 - Resolves issues with broken tests
   ([6604045](https://github.com/ory/oathkeeper/commit/6604045191446baca03791940ddf746aed4799d1))
 - Resolves naming issues and updates readme
   ([5495d4a](https://github.com/ory/oathkeeper/commit/5495d4aa6d23a04891b53694e4fc0e0857c2f955))
 - Resolves potential panic in request handler
@@ -1987,16 +1975,15 @@ Signed-off-by: aeneasr <aeneas@ory.sh>
   ([#81](https://github.com/ory/oathkeeper/issues/81))
   ([0594cda](https://github.com/ory/oathkeeper/commit/0594cda346f7ce5af1dc86c6335c1b782632d9eb)),
   closes [#80](https://github.com/ory/oathkeeper/issues/80)
 - Reduces setup complexity by making strategies configurable
   ([6626f8f](https://github.com/ory/oathkeeper/commit/6626f8f2aa98f8ee05e5b1f63c1b698083f9ae78)),
-  closes [#71](https://github.com/ory/oathkeeper/issues/71):
-  This patch adds another ID Token signing algorithm (HS256) which is easier to
-  set up as it does not rely on ORY Hydra but instead on a shared secret.
-  Additionally the ability to specify which ID Token singing algorithm to use
-  has been added. Environmental variables to configure the behvaiour have been
-  added as well.
+  closes [#71](https://github.com/ory/oathkeeper/issues/71): This patch adds
+  another ID Token signing algorithm (HS256) which is easier to set up as it
+  does not rely on ORY Hydra but instead on a shared secret. Additionally the
+  ability to specify which ID Token singing algorithm to use has been added.
+  Environmental variables to configure the behvaiour have been added as well.
   Further, the ORY Keto Warden Authorizer strategy is now optional and disabled
   when the environment variable `AUTHORIZER_KETO_WARDEN_KETO_URL` is empty.
 - Updates to ORY Hydra v1.0.0-beta.2
   ([e4c9f2e](https://github.com/ory/oathkeeper/commit/e4c9f2eeed41ab8deeb54f2137ea1b2d90a3bdc3))
 - Tells linguist to ignore SDK files
@@ -2344,10 +2331,11 @@ Build oathekeeper docker image statically (#14)
 ### Unclassified
 
 - Build oathekeeper docker image statically (#14)
   ([dbd2037](https://github.com/ory/oathkeeper/commit/dbd2037b56b6104b79607b20394be0e9a30e67e1)),
   closes [#14](https://github.com/ory/oathkeeper/issues/14):
+
   - Build oathekeeper docker image statically
 
   - Build oathekeeper docker image statically
 
 # [0.0.6](https://github.com/ory/oathkeeper/compare/v0.0.5...v0.0.6) (2017-11-03)
diff --git a/docs/docs/pipeline/authn.md b/docs/docs/pipeline/authn.md
index 20bdef6..19f85f4 100644
--- a/docs/docs/pipeline/authn.md
+++ b/docs/docs/pipeline/authn.md
@@ -620,10 +620,14 @@ was granted the requested scope.
   - `enabled` (bool, optional) - Enable the cache, will use exp time of token to
     determine when to evict from cache. Defaults to false.
   - `ttl` (string) - Can override the default behaviour of using the token exp
     time, and specify a set time to live for the token in the cache.
 
+Please note that caching will not be used if the scope strategy is `none` and
+`required_scope` is not empty. In that case, the configured introspection URL
+will always be called and is expected to check if the scope is valid or not.
+
 ```yaml
 # Global configuration file oathkeeper.yml
 authenticators:
   oauth2_introspection:
     # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false.
diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go
index 7812f3b..e6b15d9 100644
--- a/driver/configuration/provider.go
+++ b/driver/configuration/provider.go
@@ -4,10 +4,11 @@ import (
 	"encoding/json"
 	"net/url"
 	"time"
 
 	"github.com/gobuffalo/packr/v2"
+
 	"github.com/ory/fosite"
 	"github.com/ory/x/tracing"
 
 	"github.com/rs/cors"
 )
diff --git a/pipeline/authn/authenticator_oauth2_introspection.go b/pipeline/authn/authenticator_oauth2_introspection.go
index 285cbd9..b4b039e 100644
--- a/pipeline/authn/authenticator_oauth2_introspection.go
+++ b/pipeline/authn/authenticator_oauth2_introspection.go
@@ -9,10 +9,12 @@ import (
 	"net/url"
 	"strings"
 	"sync"
 	"time"
 
+	"github.com/ory/fosite"
+
 	"github.com/dgraph-io/ristretto"
 
 	"github.com/opentracing/opentracing-go"
 	"github.com/opentracing/opentracing-go/ext"
 
@@ -92,35 +94,41 @@ type AuthenticatorOAuth2IntrospectionResult struct {
 	Scope     string                 `json:"scope,omitempty"`
 	Expires   int64                  `json:"exp"`
 	TokenUse  string                 `json:"token_use"`
 }
 
-func (a *AuthenticatorOAuth2Introspection) tokenFromCache(config *AuthenticatorOAuth2IntrospectionConfiguration, token string) (*AuthenticatorOAuth2IntrospectionResult, bool) {
+func (a *AuthenticatorOAuth2Introspection) tokenFromCache(config *AuthenticatorOAuth2IntrospectionConfiguration, token string, ss fosite.ScopeStrategy) *AuthenticatorOAuth2IntrospectionResult {
 	if !config.Cache.Enabled {
-		return nil, false
+		return nil
+	}
+
+	if ss == nil && len(config.Scopes) > 0 {
+		return nil
 	}
 
 	item, found := a.tokenCache.Get(token)
 	if !found {
-		return nil, false
+		return nil
 	}
 
-	i := item.(*AuthenticatorOAuth2IntrospectionResult)
-	expires := time.Unix(i.Expires, 0)
-	if expires.Before(time.Now()) {
-		a.tokenCache.Del(token)
-		return nil, false
+	i, ok := item.(*AuthenticatorOAuth2IntrospectionResult)
+	if !ok {
+		return nil
 	}
 
-	return i, true
+	return i
 }
 
-func (a *AuthenticatorOAuth2Introspection) tokenToCache(config *AuthenticatorOAuth2IntrospectionConfiguration, i *AuthenticatorOAuth2IntrospectionResult, token string) {
+func (a *AuthenticatorOAuth2Introspection) tokenToCache(config *AuthenticatorOAuth2IntrospectionConfiguration, i *AuthenticatorOAuth2IntrospectionResult, token string, ss fosite.ScopeStrategy) {
 	if !config.Cache.Enabled {
 		return
 	}
 
+	if ss == nil && len(config.Scopes) > 0 {
+		return
+	}
+
 	if a.cacheTTL != nil {
 		a.tokenCache.SetWithTTL(token, i, 1, *a.cacheTTL)
 	} else {
 		a.tokenCache.Set(token, i, 1)
 	}
@@ -143,11 +151,11 @@ func (a *AuthenticatorOAuth2Introspection) traceRequest(ctx context.Context, req
 
 	ext.SpanKindRPCClient.Set(clientSpan)
 	ext.HTTPUrl.Set(clientSpan, urlStr)
 	ext.HTTPMethod.Set(clientSpan, req.Method)
 
-	tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
+	_ = tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
 	return clientSpan.Finish
 }
 
 func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session *AuthenticationSession, config json.RawMessage, _ pipeline.Rule) error {
 	cf, client, err := a.Config(config)
@@ -158,24 +166,27 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session
 	token := helper.BearerTokenFromRequest(r, cf.BearerTokenLocation)
 	if token == "" {
 		return errors.WithStack(ErrAuthenticatorNotResponsible)
 	}
 
-	ss := a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.oauth2_introspection.scope_strategy")
+	ss := a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.oauth2_introspection.config.scope_strategy")
 
-	i, ok := a.tokenFromCache(cf, token)
-	if !ok {
-		body := url.Values{"token": {token}}
+	i := a.tokenFromCache(cf, token, ss)
 
+	// If the token can not be found, and the scope strategy is nil, and the required scope list
+	// is not empty, then we can not use the cache.
+	if i == nil {
+		body := url.Values{"token": {token}}
 		if ss == nil {
 			body.Add("scope", strings.Join(cf.Scopes, " "))
 		}
 
 		introspectReq, err := http.NewRequest(http.MethodPost, cf.IntrospectionURL, strings.NewReader(body.Encode()))
 		if err != nil {
 			return errors.WithStack(err)
 		}
+
 		for key, value := range cf.IntrospectionRequestHeaders {
 			introspectReq.Header.Set(key, value)
 		}
 		// set/override the content-type header
 		introspectReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@@ -197,52 +208,56 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session
 		}
 
 		if err := json.NewDecoder(resp.Body).Decode(&i); err != nil {
 			return errors.WithStack(err)
 		}
+	}
 
-		if len(i.TokenUse) > 0 && i.TokenUse != "access_token" {
-			return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Use of introspected token is not an access token but \"%s\"", i.TokenUse)))
-		}
+	if len(i.TokenUse) > 0 && i.TokenUse != "access_token" {
+		return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Use of introspected token is not an access token but \"%s\"", i.TokenUse)))
+	}
 
-		if !i.Active {
-			return errors.WithStack(helper.ErrUnauthorized.WithReason("Access token i says token is not active"))
-		}
+	if !i.Active {
+		return errors.WithStack(helper.ErrUnauthorized.WithReason("Access token is not active"))
+	}
 
-		for _, audience := range cf.Audience {
-			if !stringslice.Has(i.Audience, audience) {
-				return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token audience is not intended for target audience %s", audience)))
-			}
+	if i.Expires > 0 && time.Unix(i.Expires, 0).Before(time.Now()) {
+		return errors.WithStack(helper.ErrUnauthorized.WithReason("Access token expired"))
+	}
+
+	for _, audience := range cf.Audience {
+		if !stringslice.Has(i.Audience, audience) {
+			return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token audience is not intended for target audience %s", audience)))
 		}
+	}
 
-		if len(cf.Issuers) > 0 {
-			if !stringslice.Has(cf.Issuers, i.Issuer) {
-				return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token issuer does not match any trusted issuer")))
-			}
+	if len(cf.Issuers) > 0 {
+		if !stringslice.Has(cf.Issuers, i.Issuer) {
+			return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token issuer does not match any trusted issuer")))
 		}
+	}
 
-		if ss != nil {
-			for _, scope := range cf.Scopes {
-				if !ss(strings.Split(i.Scope, " "), scope) {
-					return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Scope %s was not granted", scope)))
-				}
+	if ss != nil {
+		for _, scope := range cf.Scopes {
+			if !ss(strings.Split(i.Scope, " "), scope) {
+				return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Scope %s was not granted", scope)))
 			}
 		}
+	}
 
-		if len(i.Extra) == 0 {
-			i.Extra = map[string]interface{}{}
-		}
+	a.tokenToCache(cf, i, token, ss)
 
-		i.Extra["username"] = i.Username
-		i.Extra["client_id"] = i.ClientID
-		i.Extra["scope"] = i.Scope
+	if len(i.Extra) == 0 {
+		i.Extra = map[string]interface{}{}
+	}
 
-		if len(i.Audience) != 0 {
-			i.Extra["aud"] = i.Audience
-		}
+	i.Extra["username"] = i.Username
+	i.Extra["client_id"] = i.ClientID
+	i.Extra["scope"] = i.Scope
 
-		a.tokenToCache(cf, i, token)
+	if len(i.Audience) != 0 {
+		i.Extra["aud"] = i.Audience
 	}
 
 	session.Subject = i.Subject
 	session.Extra = i.Extra
 
@@ -322,19 +337,26 @@ func (a *AuthenticatorOAuth2Introspection) Config(config json.RawMessage) (*Auth
 		}
 		a.cacheTTL = &cacheTTL
 	}
 
 	if a.tokenCache == nil {
+		cost := int64(c.Cache.MaxCost)
+		if cost == 0 {
+			cost = 100000000
+		}
 		a.logger.Debugf("Creating cache with max cost: %d", c.Cache.MaxCost)
-		cache, _ := ristretto.NewCache(&ristretto.Config{
+		cache, err := ristretto.NewCache(&ristretto.Config{
 			// This will hold about 1000 unique mutation responses.
 			NumCounters: 10000,
 			// Allocate a max
-			MaxCost: int64(c.Cache.MaxCost),
+			MaxCost: cost,
 			// This is a best-practice value.
 			BufferItems: 64,
 		})
+		if err != nil {
+			return nil, nil, err
+		}
 
 		a.tokenCache = cache
 	}
 
 	return &c, client, nil
diff --git a/pipeline/authn/authenticator_oauth2_introspection_test.go b/pipeline/authn/authenticator_oauth2_introspection_test.go
index df3b58f..05c9ee1 100644
--- a/pipeline/authn/authenticator_oauth2_introspection_test.go
+++ b/pipeline/authn/authenticator_oauth2_introspection_test.go
@@ -23,11 +23,15 @@ package authn_test
 import (
 	"encoding/json"
 	"fmt"
 	"net/http"
 	"net/http/httptest"
+	"sync"
 	"testing"
+	"time"
+
+	"github.com/ory/x/assertx"
 
 	"github.com/julienschmidt/httprouter"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/tidwall/sjson"
@@ -46,11 +50,10 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) {
 	a, err := reg.PipelineAuthenticator("oauth2_introspection")
 	require.NoError(t, err)
 	assert.Equal(t, "oauth2_introspection", a.GetID())
 
 	t.Run("method=authenticate", func(t *testing.T) {
-
 		for k, tc := range []struct {
 			d              string
 			setup          func(*testing.T, *httprouter.Router)
 			r              *http.Request
 			config         json.RawMessage
@@ -572,10 +575,204 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) {
 				}
 			})
 		}
 	})
 
+	t.Run("method=authenticate-with-cache", func(t *testing.T) {
+		viper.Set("authenticators.oauth2_introspection.config.cache.enabled", true)
+		t.Cleanup(func() {
+			viper.Set("authenticators.oauth2_introspection.config.cache.enabled", false)
+		})
+
+		var didNotUseCache sync.WaitGroup
+
+		setup := func(t *testing.T, config string) []byte {
+			router := httprouter.New()
+			router.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+				defer didNotUseCache.Done()
+				require.NoError(t, r.ParseForm())
+				switch r.Form.Get("token") {
+				case "inactive-scope-b":
+					require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
+						Active: false,
+					}))
+				case "another-active-scope-a":
+					fallthrough
+				case "active-scope-a":
+					if r.Form.Get("scope") != "" && r.Form.Get("scope") != "scope-a" {
+						require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
+							Active: false,
+						}))
+						return
+					}
+					require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
+						Active:   true,
+						Scope:    "scope-a",
+						Subject:  "subject",
+						Audience: []string{"audience"},
+						Issuer:   "foo",
+						Username: "username",
+						Expires:  time.Now().Add(time.Second).Unix(),
+						Extra:    map[string]interface{}{"extra": "foo"},
+					}))
+				case "refresh-token":
+					require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
+						Active:   true,
+						Scope:    "scope-a",
+						Subject:  "subject",
+						Audience: []string{"audience"},
+						Issuer:   "foo",
+						Username: "username",
+						TokenUse: "refresh_token",
+						Extra:    map[string]interface{}{"extra": "foo"},
+					}))
+				default:
+					require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
+						Active: false,
+					}))
+				}
+			})
+			ts := httptest.NewServer(router)
+			t.Cleanup(ts.Close)
+
+			config, err = sjson.Set(config, "introspection_url", ts.URL+"/oauth2/introspect")
+			require.NoError(t, err)
+			config, err = sjson.Set(config, "pre_authorization.token_url", ts.URL+"/oauth2/token")
+			require.NoError(t, err)
+
+			return []byte(config)
+		}
+
+		t.Run("case=with none scope strategy", func(t *testing.T) {
+			viper.Set("authenticators.oauth2_introspection.config.scope_strategy", "none")
+			r := &http.Request{Header: http.Header{"Authorization": {"bearer active-scope-a"}}}
+			expected := new(AuthenticationSession)
+			t.Run("case=initial request succeeds", func(t *testing.T) {
+				config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
+
+				didNotUseCache.Add(1)
+				err = a.Authenticate(r, expected, config, nil)
+				didNotUseCache.Wait()
+				require.NoError(t, err)
+			})
+
+			// We expect to use the cache here because we are not interested to validate the scope. Usually we would
+			// expect to make the upstream call if the upstream has to validate the scope.
+			t.Run("case=second request does use cache because no scope was requested and strategy is nil", func(t *testing.T) {
+				config := setup(t, `{ "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
+				sess := new(AuthenticationSession)
+
+				err = a.Authenticate(r, sess, config, nil)
+				didNotUseCache.Wait() // Would result in a panic if wg.done was called!
+				require.NoError(t, err)
+				assertx.EqualAsJSON(t, expected, sess)
+			})
+
+			t.Run("case=second request does not use cache because scope strategy is disabled and scope was requested request succeeds", func(t *testing.T) {
+				config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
+				sess := new(AuthenticationSession)
+
+				didNotUseCache.Add(1)
+				err = a.Authenticate(r, sess, config, nil)
+				didNotUseCache.Wait()
+				require.NoError(t, err)
+				assertx.EqualAsJSON(t, expected, sess)
+			})
+
+			t.Run("case=request fails because we requested a scope which the upstream does not validate", func(t *testing.T) {
+				config := setup(t, `{ "required_scope": ["scope-b"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
+				sess := new(AuthenticationSession)
+
+				didNotUseCache.Add(1)
+				err = a.Authenticate(r, sess, config, nil)
+				didNotUseCache.Wait()
+				require.Error(t, err)
+			})
+		})
+
+		t.Run("case=does not use cache for refresh tokens", func(t *testing.T) {
+			for _, strategy := range []string{"wildcard", "none"} {
+				t.Run("scope_strategy="+strategy, func(t *testing.T) {
+					viper.Set("authenticators.oauth2_introspection.config.scope_strategy", strategy)
+					r := &http.Request{Header: http.Header{"Authorization": {"bearer refresh_token"}}}
+					expected := new(AuthenticationSession)
+
+					// The initial request
+					config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
+
+					// Also doesn't use the cache the second time
+					didNotUseCache.Add(2)
+					require.Error(t, a.Authenticate(r, expected, config, nil))
+					require.Error(t, a.Authenticate(r, expected, config, nil))
+					didNotUseCache.Wait()
+				})
+			}
+		})
+
+		t.Run("case=with a scope scope strategy", func(t *testing.T) {
+			viper.Set("authenticators.oauth2_introspection.config.scope_strategy", "wildcard")
+			r := &http.Request{Header: http.Header{"Authorization": {"bearer another-active-scope-a"}}}
+			expected := new(AuthenticationSession)
+
+			// The initial request
+			config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
+
+			didNotUseCache.Add(1)
+			require.NoError(t, a.Authenticate(r, expected, config, nil))
+			didNotUseCache.Wait()
+
+			t.Run("case=request succeeds and uses the cache", func(t *testing.T) {
+				config := setup(t, `{ "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
+				sess := new(AuthenticationSession)
+
+				err = a.Authenticate(r, sess, config, nil)
+				didNotUseCache.Wait()
+				require.NoError(t, err)
+				assertx.EqualAsJSON(t, expected, sess)
+			})
+
+			t.Run("case=request the initial request which also passes", func(t *testing.T) {
+				config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
+				sess := new(AuthenticationSession)
+
+				err = a.Authenticate(r, sess, config, nil)
+				didNotUseCache.Wait()
+				require.NoError(t, err)
+				assertx.EqualAsJSON(t, expected, sess)
+			})
+
+			t.Run("case=requests a scope the token does not have", func(t *testing.T) {
+				require.Error(t, a.Authenticate(r, new(AuthenticationSession),
+					setup(t, `{ "required_scope": ["scope-b"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`),
+					nil))
+				didNotUseCache.Wait()
+			})
+
+			t.Run("case=requests an audience which the token does not have", func(t *testing.T) {
+				require.Error(t, a.Authenticate(r, new(AuthenticationSession),
+					setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["not-audience"] }`),
+					nil))
+				didNotUseCache.Wait()
+			})
+
+			t.Run("case=does not trust the issuer", func(t *testing.T) {
+				require.Error(t, a.Authenticate(r, new(AuthenticationSession),
+					setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["not-foo", "bar"], "target_audience": ["audience"] }`),
+					nil))
+				didNotUseCache.Wait()
+			})
+
+			t.Run("case=respects the expiry time", func(t *testing.T) {
+				setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
+				require.NoError(t, a.Authenticate(r, new(AuthenticationSession), config, nil))
+				time.Sleep(time.Second)
+				require.Error(t, a.Authenticate(r, new(AuthenticationSession), config, nil))
+				didNotUseCache.Wait()
+			})
+		})
+	})
+
 	t.Run("method=validate", func(t *testing.T) {
 		viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled, false)
 		require.Error(t, a.Validate(json.RawMessage(`{"introspection_url":""}`)))
 
 		viper.Reset()

Vulnerable code:       - run: go-acc -o coverage.txt ./... -- -failfast -timeout=20m
      - run: test -z "$CIRCLE_PR_NUMBER" && goveralls -service=circle-ci -coverprofile=coverage.txt -repotoken=$COVERALLS_REPO_TOKEN || echo "forks are not allowed to push to coveralls"
  these headers will be passed to upstream services.
  For example, an user containing scopes/branches inside an organization profile
  has some level of data addressed to him. In this case, the upstream service
  need to know that, and "filter" the data according to his "branch_id". The
  permission that is given to the user (and the remote authorizers manages) has
  a record of the "branch_id", for the following responses will be returned as
  status code 200 (if granted) and containing a header like X-Branch-Id.
  The upstream service receives the X-Branch-Id and does your thing.
  The configuration requires to configure a list of "allowed headers" returning
  from remote authorizer, that will be accepted in the pipeline.
  closes [#668](https://github.com/ory/oathkeeper/issues/668):
  JWT Claims added to error details field.
  closes [#630](https://github.com/ory/oathkeeper/issues/630):
  This makes `/` also a separator as well as the presumably default value of
  `.`. This allows using <\*> for matching only one path segment.
  closes [#511](https://github.com/ory/oathkeeper/issues/511):
  This change introduces a url_param config option for redirect error handler.
  If it contains a url paramter name, the redirect url will have this parameter
  set, containing the current url (from which Oathkeeper has redirected the
  user).
  size.
  This change makes sure the Content-Length doesn't get included in the response
  body and adds a test to prevent future regressions.
  closes [#417](https://github.com/ory/oathkeeper/issues/417):
  This patch introduces new configuration parameters that allow the hydrator
  mutator to cache requests.
  the body into account in its decision.
  The current remote_json authorizer does not have the ability to send the
  request body of the request to authorize. This means this cannot be taken into
  account while checking permissions.
  closes [#377](https://github.com/ory/oathkeeper/issues/377):
  This patch removes the `scope` key from the OAuth2 Introspection request body
  when a scope strategy other than `none` is set for the OAuth2 Introspection
  handler. If the scope strategy is `none`, the `scope` key is included in the
  body.
  closes [#324](https://github.com/ory/oathkeeper/issues/324):
  Add a new file 'Dockerfile-dc' which will primarily be used by Docker Compose
  to build docker images. Unlike the existing Dockerfile which depends on the
  Makefile to build the binary, this Dockerfile copies the source code and
  builds the binary.
  closes [#374](https://github.com/ory/oathkeeper/issues/374):
  Fixed link to Envoy configuration page and added link to AWS API Gateway.
  closes [#321](https://github.com/ory/oathkeeper/issues/321):
  This patch adds the ability to choose a matching strategy and adds a
  glob-based matching strategy to the available options (regex is still the
  default).
  closes [#336](https://github.com/ory/oathkeeper/issues/336):
  The subject_from modifier is a GJSON path that points to the `subject` field.
  Useful if the upstream API does not return a `{"subject": "..."}` format.
  closes [#335](https://github.com/ory/oathkeeper/issues/335):
  The extra_from modifier is a GJSON path that points to the extra field. Useful
  if the upstream API does not return a `{"subject": "...", "extra": "..."}`
  format.
  [#330](https://github.com/ory/oathkeeper/issues/330):
  Also updates the schemas to add missing cookie config element.
  closes [#323](https://github.com/ory/oathkeeper/issues/323):
  Previously, MIME matching respected the request's wildcards which lead to
  multiple handlers feeling responsible for a particular request. Now, wildcards
  coming from the HTTP Request itself are interpreted literally.
  Additionally, ORY Oathkeeper respected the X-Forwarded-For HTTP Header for
  matching remote IP addresses. This behavior is now turned off by default
  because clients were able to fake this header otherwise. It can explicitly be
  turned on by setting
  [#119](https://github.com/ory/oathkeeper/issues/119):
  This patch adds a new feature called error handlers. It allows to define the
  error handling logic globally and per rule. It is now possible, for example,
  to return a JSON response for `Accept: application/json` requests and a HTTP
  Redirect response for requests that are coming from a user.
  This also resolves several issues, as noted below:
  [#317](https://github.com/ory/oathkeeper/issues/317):
  Previously, some keys did not respect the values set in the environment
  variables.
  [#242](https://github.com/ory/oathkeeper/issues/242):
  Adds details such as the HTTP Method, User Agent, Subject, and other
  information to the logging output of both the reverse proxy and the decision
  API.
  closes [#283](https://github.com/ory/oathkeeper/issues/283):
  Remove health endpoints from the logs to make monitoring easier. Setting
  `log_level` to `trace` will show these calls.
  closes [#291](https://github.com/ory/oathkeeper/issues/291):
  A bug caused the rule matcher to not cache the regular expression result.
  [#280](https://github.com/ory/oathkeeper/issues/280):
  This patch automatically binds environment variables to configuration keys.
  This patch resolves several issues:
  [#257](https://github.com/ory/oathkeeper/issues/257):
  Add additional optional configuration to jwt and oauth2_introspection
  authenticators allowing to set from where (which header or query parameter)
  the token should be received. The configuration is a token_from field in
  per-rule-configuration, as described in a linked issue.
  closes [#266](https://github.com/ory/oathkeeper/issues/266):
  Adds the ability to modify rules with backwards compatibility.
  closes [#217](https://github.com/ory/oathkeeper/issues/217):
  This patch allows oathkeeper to re-load any changes made to the configuraiton
  file and/or the access rules to be reloaded without a restart.
  Some configuration keys like serve._, log._, profiling however require a
  restart.
  closes [#213](https://github.com/ory/oathkeeper/issues/213):
  This commit adds support for defining access rules in YAML format, in addition
  to existing JSON format.
  closes [#204](https://github.com/ory/oathkeeper/issues/204):
  There's no DSN in oathkeeper
  closes [#199](https://github.com/ory/oathkeeper/issues/199):
  Previously, negroni.With was mistakenly used to add middleware onto the stack.
  The proper method however is negroni.Use.
  This patch fixes the use of negroni.With and resolves issues around logging
  and the decisions endpoint.
  closes [#138](https://github.com/ory/oathkeeper/issues/138):
  Previously, the JWT authenticator only used the "scope" claim to retrieve
  scope values from a JWT. Now, "scp", "scope", "scopes" are supported as string
  arrays and strings separated by spaces.
  closes [#42](https://github.com/ory/oathkeeper/issues/42):
  This patch adds endpoint `/judge` to `oathkeeper serve api`. The `/judge`
  endpoint mimics the behavior of `oathkeeper serve proxy` but instead of
  forwarding the request to the upstream server, the endpoint answers directly
  with a HTTP response.
  The HTTP response returns status code 200 if the request should be allowed and
  any other status code (e.g. 401, 403) if not.
  Assuming you are making the following request:
  a server handled proxy requests.
  It will help prevent a case of unexpected closing a client connection if an
  upstream request is executing more than default timeout.
  closes [#101](https://github.com/ory/oathkeeper/issues/101):
  This patch improves compatibility with ORY Hydra 1.0.0-beta.8 and updates
  vendored dependencies.
  closes [#38](https://github.com/ory/oathkeeper/issues/38):
  Previously, PUT and POST did not result in errors (409) when non-existing
  resources were modified, or existing resources were created. This patch
  resolves that.
  closes [#73](https://github.com/ory/oathkeeper/issues/73):
  This patch resolves an issue where updates would not properly propagate. This
  caused deleted rules to still be available in the proxy.
  closes [#71](https://github.com/ory/oathkeeper/issues/71):
  This patch adds another ID Token signing algorithm (HS256) which is easier to
  set up as it does not rely on ORY Hydra but instead on a shared secret.
  Additionally the ability to specify which ID Token singing algorithm to use
  has been added. Environmental variables to configure the behvaiour have been
  added as well.
func (a *AuthenticatorOAuth2Introspection) tokenFromCache(config *AuthenticatorOAuth2IntrospectionConfiguration, token string) (*AuthenticatorOAuth2IntrospectionResult, bool) {
		return nil, false
		return nil, false
	i := item.(*AuthenticatorOAuth2IntrospectionResult)
	expires := time.Unix(i.Expires, 0)
	if expires.Before(time.Now()) {
		a.tokenCache.Del(token)
		return nil, false
	return i, true
func (a *AuthenticatorOAuth2Introspection) tokenToCache(config *AuthenticatorOAuth2IntrospectionConfiguration, i *AuthenticatorOAuth2IntrospectionResult, token string) {
	tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
	ss := a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.oauth2_introspection.scope_strategy")
	i, ok := a.tokenFromCache(cf, token)
	if !ok {
		body := url.Values{"token": {token}}
		if len(i.TokenUse) > 0 && i.TokenUse != "access_token" {
			return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Use of introspected token is not an access token but \"%s\"", i.TokenUse)))
		}
		if !i.Active {
			return errors.WithStack(helper.ErrUnauthorized.WithReason("Access token i says token is not active"))
		}
		for _, audience := range cf.Audience {
			if !stringslice.Has(i.Audience, audience) {
				return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token audience is not intended for target audience %s", audience)))
			}
		if len(cf.Issuers) > 0 {
			if !stringslice.Has(cf.Issuers, i.Issuer) {
				return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token issuer does not match any trusted issuer")))
			}
		if ss != nil {
			for _, scope := range cf.Scopes {
				if !ss(strings.Split(i.Scope, " "), scope) {
					return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Scope %s was not granted", scope)))
				}
		if len(i.Extra) == 0 {
			i.Extra = map[string]interface{}{}
		}
		i.Extra["username"] = i.Username
		i.Extra["client_id"] = i.ClientID
		i.Extra["scope"] = i.Scope
		if len(i.Audience) != 0 {
			i.Extra["aud"] = i.Audience
		}
		a.tokenToCache(cf, i, token)
		cache, _ := ristretto.NewCache(&ristretto.Config{
			MaxCost: int64(c.Cache.MaxCost),

Fixed code:       - run: go-acc -o coverage.out ./... -- -failfast -timeout=20m
      - run: |
          bash <(curl -s https://codecov.io/bash)
node_modules: package.json package-lock.json
		npm i

  these headers will be passed to upstream services. For example, an user
  containing scopes/branches inside an organization profile has some level of
  data addressed to him. In this case, the upstream service need to know that,
  and "filter" the data according to his "branch_id". The permission that is
  given to the user (and the remote authorizers manages) has a record of the
  "branch_id", for the following responses will be returned as status code 200
  (if granted) and containing a header like X-Branch-Id. The upstream service
  receives the X-Branch-Id and does your thing. The configuration requires to
  configure a list of "allowed headers" returning from remote authorizer, that
  will be accepted in the pipeline.


  closes [#668](https://github.com/ory/oathkeeper/issues/668): JWT Claims added
  to error details field.
  closes [#630](https://github.com/ory/oathkeeper/issues/630): This makes `/`
  also a separator as well as the presumably default value of `.`. This allows
  using <\*> for matching only one path segment.
  closes [#511](https://github.com/ory/oathkeeper/issues/511): This change
  introduces a url_param config option for redirect error handler. If it
  contains a url paramter name, the redirect url will have this parameter set,
  containing the current url (from which Oathkeeper has redirected the user).
  size. This change makes sure the Content-Length doesn't get included in the
  response body and adds a test to prevent future regressions.
  closes [#417](https://github.com/ory/oathkeeper/issues/417): This patch
  introduces new configuration parameters that allow the hydrator mutator to
  cache requests.
  the body into account in its decision. The current remote_json authorizer does
  not have the ability to send the request body of the request to authorize.
  This means this cannot be taken into account while checking permissions.
  closes [#377](https://github.com/ory/oathkeeper/issues/377): This patch
  removes the `scope` key from the OAuth2 Introspection request body when a
  scope strategy other than `none` is set for the OAuth2 Introspection handler.
  If the scope strategy is `none`, the `scope` key is included in the body.
  closes [#324](https://github.com/ory/oathkeeper/issues/324): Add a new file
  'Dockerfile-dc' which will primarily be used by Docker Compose to build docker
  images. Unlike the existing Dockerfile which depends on the Makefile to build
  the binary, this Dockerfile copies the source code and builds the binary.
  closes [#374](https://github.com/ory/oathkeeper/issues/374): Fixed link to
  Envoy configuration page and added link to AWS API Gateway.
  closes [#321](https://github.com/ory/oathkeeper/issues/321): This patch adds
  the ability to choose a matching strategy and adds a glob-based matching
  strategy to the available options (regex is still the default).
  closes [#336](https://github.com/ory/oathkeeper/issues/336): The subject_from
  modifier is a GJSON path that points to the `subject` field. Useful if the
  upstream API does not return a `{"subject": "..."}` format.
  closes [#335](https://github.com/ory/oathkeeper/issues/335): The extra_from
  modifier is a GJSON path that points to the extra field. Useful if the
  upstream API does not return a `{"subject": "...", "extra": "..."}` format.
  [#330](https://github.com/ory/oathkeeper/issues/330): Also updates the schemas
  to add missing cookie config element.
  closes [#323](https://github.com/ory/oathkeeper/issues/323): Previously, MIME
  matching respected the request's wildcards which lead to multiple handlers
  feeling responsible for a particular request. Now, wildcards coming from the
  HTTP Request itself are interpreted literally. Additionally, ORY Oathkeeper
  respected the X-Forwarded-For HTTP Header for matching remote IP addresses.
  This behavior is now turned off by default because clients were able to fake
  this header otherwise. It can explicitly be turned on by setting
  [#119](https://github.com/ory/oathkeeper/issues/119): This patch adds a new
  feature called error handlers. It allows to define the error handling logic
  globally and per rule. It is now possible, for example, to return a JSON
  response for `Accept: application/json` requests and a HTTP Redirect response
  for requests that are coming from a user. This also resolves several issues,
  as noted below:
  [#317](https://github.com/ory/oathkeeper/issues/317): Previously, some keys
  did not respect the values set in the environment variables.
  [#242](https://github.com/ory/oathkeeper/issues/242): Adds details such as the
  HTTP Method, User Agent, Subject, and other information to the logging output
  of both the reverse proxy and the decision API.
  closes [#283](https://github.com/ory/oathkeeper/issues/283): Remove health
  endpoints from the logs to make monitoring easier. Setting `log_level` to
  `trace` will show these calls.
  closes [#291](https://github.com/ory/oathkeeper/issues/291): A bug caused the
  rule matcher to not cache the regular expression result.
  [#280](https://github.com/ory/oathkeeper/issues/280): This patch automatically
  binds environment variables to configuration keys. This patch resolves several
  issues:
  [#257](https://github.com/ory/oathkeeper/issues/257): Add additional optional
  configuration to jwt and oauth2_introspection authenticators allowing to set
  from where (which header or query parameter) the token should be received. The
  configuration is a token_from field in per-rule-configuration, as described in
  a linked issue.
  closes [#266](https://github.com/ory/oathkeeper/issues/266): Adds the ability
  to modify rules with backwards compatibility.
  closes [#217](https://github.com/ory/oathkeeper/issues/217): This patch allows
  oathkeeper to re-load any changes made to the configuraiton file and/or the
  access rules to be reloaded without a restart. Some configuration keys like
  serve._, log._, profiling however require a restart.
  closes [#213](https://github.com/ory/oathkeeper/issues/213): This commit adds
  support for defining access rules in YAML format, in addition to existing JSON
  format.
  closes [#204](https://github.com/ory/oathkeeper/issues/204): There's no DSN in
  oathkeeper
  closes [#199](https://github.com/ory/oathkeeper/issues/199): Previously,
  negroni.With was mistakenly used to add middleware onto the stack. The proper
  method however is negroni.Use. This patch fixes the use of negroni.With and
  resolves issues around logging and the decisions endpoint.
  closes [#138](https://github.com/ory/oathkeeper/issues/138): Previously, the
  JWT authenticator only used the "scope" claim to retrieve scope values from a
  JWT. Now, "scp", "scope", "scopes" are supported as string arrays and strings
  separated by spaces.
  closes [#42](https://github.com/ory/oathkeeper/issues/42): This patch adds
  endpoint `/judge` to `oathkeeper serve api`. The `/judge` endpoint mimics the
  behavior of `oathkeeper serve proxy` but instead of forwarding the request to
  the upstream server, the endpoint answers directly with a HTTP response. The
  HTTP response returns status code 200 if the request should be allowed and any
  other status code (e.g. 401, 403) if not. Assuming you are making the
  following request:
  a server handled proxy requests. It will help prevent a case of unexpected
  closing a client connection if an upstream request is executing more than
  default timeout.
  closes [#101](https://github.com/ory/oathkeeper/issues/101): This patch
  improves compatibility with ORY Hydra 1.0.0-beta.8 and updates vendored
  dependencies.
  closes [#38](https://github.com/ory/oathkeeper/issues/38): Previously, PUT and
  POST did not result in errors (409) when non-existing resources were modified,
  or existing resources were created. This patch resolves that.
  closes [#73](https://github.com/ory/oathkeeper/issues/73): This patch resolves
  an issue where updates would not properly propagate. This caused deleted rules
  to still be available in the proxy.
  closes [#71](https://github.com/ory/oathkeeper/issues/71): This patch adds
  another ID Token signing algorithm (HS256) which is easier to set up as it
  does not rely on ORY Hydra but instead on a shared secret. Additionally the
  ability to specify which ID Token singing algorithm to use has been added.
  Environmental variables to configure the behvaiour have been added as well.

Please note that caching will not be used if the scope strategy is `none` and
`required_scope` is not empty. In that case, the configured introspection URL
will always be called and is expected to check if the scope is valid or not.


	"github.com/ory/fosite"

func (a *AuthenticatorOAuth2Introspection) tokenFromCache(config *AuthenticatorOAuth2IntrospectionConfiguration, token string, ss fosite.ScopeStrategy) *AuthenticatorOAuth2IntrospectionResult {
		return nil
	}

	if ss == nil && len(config.Scopes) > 0 {
		return nil
		return nil
	i, ok := item.(*AuthenticatorOAuth2IntrospectionResult)
	if !ok {
		return nil
	return i
func (a *AuthenticatorOAuth2Introspection) tokenToCache(config *AuthenticatorOAuth2IntrospectionConfiguration, i *AuthenticatorOAuth2IntrospectionResult, token string, ss fosite.ScopeStrategy) {
	if ss == nil && len(config.Scopes) > 0 {
		return
	}

	_ = tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
	ss := a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.oauth2_introspection.config.scope_strategy")
	i := a.tokenFromCache(cf, token, ss)
	// If the token can not be found, and the scope strategy is nil, and the required scope list
	// is not empty, then we can not use the cache.
	if i == nil {
		body := url.Values{"token": {token}}

	}
	if len(i.TokenUse) > 0 && i.TokenUse != "access_token" {
		return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Use of introspected token is not an access token but \"%s\"", i.TokenUse)))
	}
	if !i.Active {
		return errors.WithStack(helper.ErrUnauthorized.WithReason("Access token is not active"))
	}
	if i.Expires > 0 && time.Unix(i.Expires, 0).Before(time.Now()) {
		return errors.WithStack(helper.ErrUnauthorized.WithReason("Access token expired"))
	}

	for _, audience := range cf.Audience {
		if !stringslice.Has(i.Audience, audience) {
			return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token audience is not intended for target audience %s", audience)))
	}
	if len(cf.Issuers) > 0 {
		if !stringslice.Has(cf.Issuers, i.Issuer) {
			return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token issuer does not match any trusted issuer")))
	}
	if ss != nil {
		for _, scope := range cf.Scopes {
			if !ss(strings.Split(i.Scope, " "), scope) {
				return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Scope %s was not granted", scope)))
	}
	a.tokenToCache(cf, i, token, ss)
	if len(i.Extra) == 0 {
		i.Extra = map[string]interface{}{}
	}
	i.Extra["username"] = i.Username
	i.Extra["client_id"] = i.ClientID
	i.Extra["scope"] = i.Scope
	if len(i.Audience) != 0 {
		i.Extra["aud"] = i.Audience
		cost := int64(c.Cache.MaxCost)
		if cost == 0 {
			cost = 100000000
		}
		cache, err := ristretto.NewCache(&ristretto.Config{
			MaxCost: cost,
		if err != nil {
			return nil, nil, err
		}
	"sync"
	"time"

	"github.com/ory/x/assertx"
	t.Run("method=authenticate-with-cache", func(t *testing.T) {
		viper.Set("authenticators.oauth2_introspection.config.cache.enabled", true)
		t.Cleanup(func() {
			viper.Set("authenticators.oauth2_introspection.config.cache.enabled", false)
		})

		var didNotUseCache sync.WaitGroup

		setup := func(t *testing.T, config string) []byte {
			router := httprouter.New()
			router.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
				defer didNotUseCache.Done()
				require.NoError(t, r.ParseForm())
				switch r.Form.Get("token") {
				case "inactive-scope-b":
					require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
						Active: false,
					}))
				case "another-active-scope-a":
					fallthrough
				case "active-scope-a":
					if r.Form.Get("scope") != "" && r.Form.Get("scope") != "scope-a" {
						require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
							Active: false,
						}))
						return
					}
					require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
						Active:   true,
						Scope:    "scope-a",
						Subject:  "subject",
						Audience: []string{"audience"},
						Issuer:   "foo",
						Username: "username",
						Expires:  time.Now().Add(time.Second).Unix(),
						Extra:    map[string]interface{}{"extra": "foo"},
					}))
				case "refresh-token":
					require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
						Active:   true,
						Scope:    "scope-a",
						Subject:  "subject",
						Audience: []string{"audience"},
						Issuer:   "foo",
						Username: "username",
						TokenUse: "refresh_token",
						Extra:    map[string]interface{}{"extra": "foo"},
					}))
				default:
					require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
						Active: false,
					}))
				}
			})
			ts := httptest.NewServer(router)
			t.Cleanup(ts.Close)

			config, err = sjson.Set(config, "introspection_url", ts.URL+"/oauth2/introspect")
			require.NoError(t, err)
			config, err = sjson.Set(config, "pre_authorization.token_url", ts.URL+"/oauth2/token")
			require.NoError(t, err)

			return []byte(config)
		}

		t.Run("case=with none scope strategy", func(t *testing.T) {
			viper.Set("authenticators.oauth2_introspection.config.scope_strategy", "none")
			r := &http.Request{Header: http.Header{"Authorization": {"bearer active-scope-a"}}}
			expected := new(AuthenticationSession)
			t.Run("case=initial request succeeds", func(t *testing.T) {
				config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)

				didNotUseCache.Add(1)
				err = a.Authenticate(r, expected, config, nil)
				didNotUseCache.Wait()
				require.NoError(t, err)
			})

			// We expect to use the cache here because we are not interested to validate the scope. Usually we would
			// expect to make the upstream call if the upstream has to validate the scope.
			t.Run("case=second request does use cache because no scope was requested and strategy is nil", func(t *testing.T) {
				config := setup(t, `{ "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
				sess := new(AuthenticationSession)

				err = a.Authenticate(r, sess, config, nil)
				didNotUseCache.Wait() // Would result in a panic if wg.done was called!
				require.NoError(t, err)
				assertx.EqualAsJSON(t, expected, sess)
			})

			t.Run("case=second request does not use cache because scope strategy is disabled and scope was requested request succeeds", func(t *testing.T) {
				config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
				sess := new(AuthenticationSession)

				didNotUseCache.Add(1)
				err = a.Authenticate(r, sess, config, nil)
				didNotUseCache.Wait()
				require.NoError(t, err)
				assertx.EqualAsJSON(t, expected, sess)
			})

			t.Run("case=request fails because we requested a scope which the upstream does not validate", func(t *testing.T) {
				config := setup(t, `{ "required_scope": ["scope-b"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
				sess := new(AuthenticationSession)

				didNotUseCache.Add(1)
				err = a.Authenticate(r, sess, config, nil)
				didNotUseCache.Wait()
				require.Error(t, err)
			})
		})

		t.Run("case=does not use cache for refresh tokens", func(t *testing.T) {
			for _, strategy := range []string{"wildcard", "none"} {
				t.Run("scope_strategy="+strategy, func(t *testing.T) {
					viper.Set("authenticators.oauth2_introspection.config.scope_strategy", strategy)
					r := &http.Request{Header: http.Header{"Authorization": {"bearer refresh_token"}}}
					expected := new(AuthenticationSession)

					// The initial request
					config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)

					// Also doesn't use the cache the second time
					didNotUseCache.Add(2)
					require.Error(t, a.Authenticate(r, expected, config, nil))
					require.Error(t, a.Authenticate(r, expected, config, nil))
					didNotUseCache.Wait()
				})
			}
		})

		t.Run("case=with a scope scope strategy", func(t *testing.T) {
			viper.Set("authenticators.oauth2_introspection.config.scope_strategy", "wildcard")
			r := &http.Request{Header: http.Header{"Authorization": {"bearer another-active-scope-a"}}}
			expected := new(AuthenticationSession)

			// The initial request
			config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)

			didNotUseCache.Add(1)
			require.NoError(t, a.Authenticate(r, expected, config, nil))
			didNotUseCache.Wait()

			t.Run("case=request succeeds and uses the cache", func(t *testing.T) {
				config := setup(t, `{ "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
				sess := new(AuthenticationSession)

				err = a.Authenticate(r, sess, config, nil)
				didNotUseCache.Wait()
				require.NoError(t, err)
				assertx.EqualAsJSON(t, expected, sess)
			})

			t.Run("case=request the initial request which also passes", func(t *testing.T) {
				config := setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
				sess := new(AuthenticationSession)

				err = a.Authenticate(r, sess, config, nil)
				didNotUseCache.Wait()
				require.NoError(t, err)
				assertx.EqualAsJSON(t, expected, sess)
			})

			t.Run("case=requests a scope the token does not have", func(t *testing.T) {
				require.Error(t, a.Authenticate(r, new(AuthenticationSession),
					setup(t, `{ "required_scope": ["scope-b"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`),
					nil))
				didNotUseCache.Wait()
			})

			t.Run("case=requests an audience which the token does not have", func(t *testing.T) {
				require.Error(t, a.Authenticate(r, new(AuthenticationSession),
					setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["not-audience"] }`),
					nil))
				didNotUseCache.Wait()
			})

			t.Run("case=does not trust the issuer", func(t *testing.T) {
				require.Error(t, a.Authenticate(r, new(AuthenticationSession),
					setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["not-foo", "bar"], "target_audience": ["audience"] }`),
					nil))
				didNotUseCache.Wait()
			})

			t.Run("case=respects the expiry time", func(t *testing.T) {
				setup(t, `{ "required_scope": ["scope-a"], "trusted_issuers": ["foo", "bar"], "target_audience": ["audience"] }`)
				require.NoError(t, a.Authenticate(r, new(AuthenticationSession), config, nil))
				time.Sleep(time.Second)
				require.Error(t, a.Authenticate(r, new(AuthenticationSession), config, nil))
				didNotUseCache.Wait()
			})
		})
	})