Invalid Scope error on RefreshAccessToken

I'm integrating an Apigee oauth proxy with an off-the-shelf client library, but when it sends a refresh_token request to apigee, the RefreshAccessToken operation fails with an invalid_request error with cause Invalid Scope.

My policy looks like this:

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OAuthV2 enabled="true" name="OAuth-AuthorizationCode-RefreshToken">
  <Operation>RefreshAccessToken</Operation>
  <GenerateResponse enabled="false"/>
  <ExpiresIn ref="propertyset.access-management.token_expiry_authorization_code">3600000</ExpiresIn>
  <ReuseRefreshToken>false</ReuseRefreshToken>
</OAuthV2>

 

and an example request payload would be along the lines of

 

client_id=<The appropriate client id>
grant_type=refresh_token
scope=openid profile email
refresh_token=GZ7dhjXwG9MKOMPS43Gv4KXt6uOCraAk

 

although the problem persists whatever the scope parameter is unless it is empty.

The scopes sent by the client library are the same as were used to generate the original authentication code / token, and I assume given that I have removed the actual <Scope> tag that it's being defaulted from request.formparam.scope

I've not been able to find anything in the documentation about how the RefreshAccessToken operation handles scopes, and the fault itself doesn't seem to have any more specific information as to why the request is invalid.

I know I can bypass the error by unsetting the scope form parameter, or by pointing the <Scope> tag to an empty flow variable, but I would prefer to understand what the underlying issue is before I do that, as there must surely be some non-empty value that wouldn't raise a fault!

Solved Solved
0 1 5,735
1 ACCEPTED SOLUTION

I assume given that I have removed the actual <Scope>tag that it's being defaulted from request.formparam.scope

That is correct.

One possible explanation for the behavior you're observing is that Apigee does not support modifying the Scope of a token, with the refresh_token grant type. And even if you pass the same scope, the Apigee runtime is rejecting the request, saying "you can't do that" without even checking. That's a possibility. But that seems unlikely as an explanation. The relevant section of RFC6749 says that scope is an optional parameter with refresh_token grant type. It would be surprising if Apigee didn't support that at all. The Apigee implementation does not have logic that rejects a request if there is any scope, as far as I can tell.

A more likely explanation is ... the set of scopes you are passing there are defined by OpenID Connect, and refer to scopes that affect the user-identifying claims that will be placed into the ID token. In OpenID Connect, passing scope=openid profile email means the requesting app is asking for the ID token to contain these claims: name, family_name, given_name, middle_name, nickname, picture, updated_at, and email. The access_token does not "get" those scopes. They affect only the claims that get inserted into the ID token.

The refresh_token grant you are showing.... is refreshing the access_token. Not the ID token. The scopes you are passing are not attached to the access_token , so that is why you see a rejection, with the "Invalid Scope" error message.

If you have a client or client library that always sends along the OpenID scopes even in a refresh_token grant, I think that client is in error. That is not correct behavior. If you cannot change the behavior of the client, working around the issue by pointing the Scope tag in the Apigee policy to an empty flow variable seems like the right thing to do.

Addendum

I think the way to get scopes on the access_token, within the context of OpenID Connect, is to include in the initial /authorize request, a set of scopes that includes one or more scope names that are NOT from the reserved list defined by OpenID Connect for use with ID tokens. IE, something other than openid profile or email. These additional scopes would be something specific to your API Product. Therefore the scope param that you pass in the initial /authorize request should be something like scope=openid profile email scope1 scope2. The first three scopes are related to the ID token, and the others, not being defined by OpenID Connect, would be relevant for the access token. To make this work, you need to insure your API Product in Apigee is configured to support scope1 and scope2 (but not the OpenID Connect-related scopes). And you need to insure that the original GenerateAccessToken in Apigee refers to that scope variable.  THEN, you could refresh the access_token by specifying scope=scope1 scope2 , or a subset of scope1 scope2. I haven't tested this, but this should work for this purpose. (EDIT: I just tested this and can confirm, this is how it works)

But probably all of that is irrelevant to you, because you are not using custom scopes on the access token. You are specifying only scopes that affect the identity token.

View solution in original post

1 REPLY 1

I assume given that I have removed the actual <Scope>tag that it's being defaulted from request.formparam.scope

That is correct.

One possible explanation for the behavior you're observing is that Apigee does not support modifying the Scope of a token, with the refresh_token grant type. And even if you pass the same scope, the Apigee runtime is rejecting the request, saying "you can't do that" without even checking. That's a possibility. But that seems unlikely as an explanation. The relevant section of RFC6749 says that scope is an optional parameter with refresh_token grant type. It would be surprising if Apigee didn't support that at all. The Apigee implementation does not have logic that rejects a request if there is any scope, as far as I can tell.

A more likely explanation is ... the set of scopes you are passing there are defined by OpenID Connect, and refer to scopes that affect the user-identifying claims that will be placed into the ID token. In OpenID Connect, passing scope=openid profile email means the requesting app is asking for the ID token to contain these claims: name, family_name, given_name, middle_name, nickname, picture, updated_at, and email. The access_token does not "get" those scopes. They affect only the claims that get inserted into the ID token.

The refresh_token grant you are showing.... is refreshing the access_token. Not the ID token. The scopes you are passing are not attached to the access_token , so that is why you see a rejection, with the "Invalid Scope" error message.

If you have a client or client library that always sends along the OpenID scopes even in a refresh_token grant, I think that client is in error. That is not correct behavior. If you cannot change the behavior of the client, working around the issue by pointing the Scope tag in the Apigee policy to an empty flow variable seems like the right thing to do.

Addendum

I think the way to get scopes on the access_token, within the context of OpenID Connect, is to include in the initial /authorize request, a set of scopes that includes one or more scope names that are NOT from the reserved list defined by OpenID Connect for use with ID tokens. IE, something other than openid profile or email. These additional scopes would be something specific to your API Product. Therefore the scope param that you pass in the initial /authorize request should be something like scope=openid profile email scope1 scope2. The first three scopes are related to the ID token, and the others, not being defined by OpenID Connect, would be relevant for the access token. To make this work, you need to insure your API Product in Apigee is configured to support scope1 and scope2 (but not the OpenID Connect-related scopes). And you need to insure that the original GenerateAccessToken in Apigee refers to that scope variable.  THEN, you could refresh the access_token by specifying scope=scope1 scope2 , or a subset of scope1 scope2. I haven't tested this, but this should work for this purpose. (EDIT: I just tested this and can confirm, this is how it works)

But probably all of that is irrelevant to you, because you are not using custom scopes on the access token. You are specifying only scopes that affect the identity token.