# Getting a refresh token from CSS

**URL:** <https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310>\
**Category:** General Discussion\
**Created:** [March 7, 2024, 11:22pm UTC](https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310 "2024-03-07T23:22:02Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Anushka](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.solidproject.org/anushka/32/3134_2.png) [@Anushka](https://forum.solidproject.org/u/Anushka)\
**Post date:** [March 7, 2024, 11:22pm UTC](https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310/1 "2024-03-07T23:22:02Z")

</div>

Hi all,

I am trying to get a refresh token from the server when authenticating a client. Following is my authentication request.

```auto
http.post(client.issuer.tokenEndpoint,
    headers: {
      'Accept': '*/*',
      'Accept-Encoding': 'gzip, deflate, br',
      'DPoP': dPoPToken,
      'content-type': 'application/x-www-form-urlencoded',
      'Authorization': 'Basic $h',
      'Connection': 'keep-alive',
    },
    body: {
      'grant_type': 'authorization_code',
      'code': code,
      'redirect_uri': redirectUri.toString(),
      'Scope': 'openid profile offline_access',
      'client_id': client.clientId,
      'code_verifier': _proofKeyForCodeExchange['code_verifier']
    }

```

With `solidcommunity.net` I am able to get a refresh token out. But with my own CSS installation, I cannot seem to get the refresh token. The server does not respond with a refresh token. Do I need to set up a specific configuration when installing the server in order to get a refresh token upon authentication?

Any help is much appreciated.

Thanks, Anushka

---

<div class="post-metadata">

**Author:** ![zwifi](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.solidproject.org/zwifi/32/1525_2.png) [@zwifi](https://forum.solidproject.org/u/zwifi)\
**Post date:** [March 12, 2024, 9:58am UTC](https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310/2 "2024-03-12T09:58:31Z")

</div>

Hi @Anushka !

I’m not very familiar with CSS configuration, but I can help with the OpenID part of this. The issue you are seeing happens at the token request step, after the authorization request, as part of the authorization code flow (correct me if any of this is inaccurate).

Is the client you are using statically registered, or dynamically ? In other words, how is `client.clientId` obtained? As part of the client registration (be it static or dynamic), metadata about the client is provided, including the authorized scopes for the client. Make sure that `offline_access` is present at this point, because this is the scope that causes the OpenID Provider to issue a Refresh Token.

Also, in your example, the POST body has a `Scope` key (it should probably be `scope`), however that isn’t a parameter provided during the token request, it should be provided during the authorization request according to [the specification](https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest). Can you share more about the complete OpenID flow you are following?

---

<div class="post-metadata">

**Author:** ![Anushka](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.solidproject.org/anushka/32/3134_2.png) [@Anushka](https://forum.solidproject.org/u/Anushka)\
**Post date:** [March 14, 2024, 1:14am UTC](https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310/3 "2024-03-14T01:14:10Z")

</div>

Hi @zwifi thanks a lot for your reply. I do dynamic client registration. So based on your answers, here’s what I do now.

**Dynamic client registration**

```auto
final response = await http.post(Uri.parse(regEndPoint),
    headers: <String, String>{
      'Accept': '*/*',
      'Content-Type': 'application/json',
      'Connection': 'keep-alive',
      'Accept-Encoding': 'gzip, deflate, br',
    },
    body: json.encode({
      "application_type": "web",
      "scope": "openid, profile, offline_access",
      "grant_types": ["authorization_code", "refresh_token"],
      "redirect_uris": reidirUrlList,
      "token_endpoint_auth_method": "client_secret_basic",
    }));

```

The response I get for this registration request is as follows:

```auto
response = {"application_type":"web",
"grant_types":["authorization_code","refresh_token"],
"id_token_signed_response_alg":"ES256",
"require_auth_time":false,
"response_types":["code"],
"subject_type":"public",
"token_endpoint_auth_method":"client_secret_basic",
"post_logout_redirect_uris":[],
"require_pushed_authorization_requests":false,
"dpop_bound_access_tokens":false,
"client_id_issued_at":1710377438,
"client_id":"eGKzO3ArtyRq5Fl2",
"client_secret_expires_at":0,"client_secret":"G2uxR3VA7D...",
"redirect_uris":["http://localhost:4400/"],
"scope":"openid profile offline_access",
"registration_client_uri":"https://solidserver.url/.oidc/reg/eGKzO3ArtyRq5Fl2",
"registration_access_token":"d3rlVyYJFlfAZ7qMO..."}

```

I then create a `client` object using the above response details and after that I do the **Authorisation request** as follows:

```auto
var h = base64.encode('${client.clientId}:${client.clientSecret}'.codeUnits);
json = await http.post(client.issuer.tokenEndpoint,
    headers: {
      'Accept': '*/*',
      'Accept-Encoding': 'gzip, deflate, br',
      'content-type': 'application/x-www-form-urlencoded',
      'Authorization': 'Basic $h',
      'Connection': 'keep-alive',
    },
    body: {
      'grant_type': 'refresh_token',
      'code': code,
      'redirect_uri': redirectUri.toString(),
      'client_id': client.clientId,
      'code_verifier': _proofKeyForCodeExchange['code_verifier']
    },
    client: client.httpClient);

```

But when I do this now I get the following error.

`Unhandled Exception: OpenIdException(invalid_request): missing required parameter 'refresh_token'`

When I change the `grant_type` in the **Authorisation request** to `authorization_code`, the request goes through, but I still do not get a refresh\_token.

I am sure I am doing something wrong with these requests, but could not figure out exactly what. Any help would be much appreciated.

---

<div class="post-metadata">

**Author:** ![zwifi](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.solidproject.org/zwifi/32/1525_2.png) [@zwifi](https://forum.solidproject.org/u/zwifi)\
**Post date:** [March 14, 2024, 9:21am UTC](https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310/4 "2024-03-14T09:21:16Z")

</div>

The authorization request shouldn’t be a POST to the Token Endpoint, but rather a GET to the authorization endpoint. This will redirect your user to the OpenID Provider (OP), where they will log in using whatever credentials the OP supports (typically, username/password, but it could be anything, that’s totally transparent to the Relying Party (RP), i.e. your app). Then, the OP will redirect the user back to the `redirect_url` that has been provided as part of the Authorization Request (if it matches the one registered during Dynamic Client Registration), providing the authorisation code. It’s only at this stage that you can do the Token Request, i.e. the POST request to the Token Endpoint, using the authorization code you’ve just been sent by the OP. If everything goes well, the response from the Token Endpoint will include an ID Token, an Access Token, and a Refresh Token.

If you aren’t too familiar with OAuth in general and/or OpenID Connect in particular, I would advise to look for a library implementing this protocol in your target language, which would save you some headaches 🙂

---

<div class="post-metadata">

**Author:** ![Anushka](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.solidproject.org/anushka/32/3134_2.png) [@Anushka](https://forum.solidproject.org/u/Anushka)\
**Post date:** [March 15, 2024, 4:13am UTC](https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310/5 "2024-03-15T04:13:37Z")

</div>

Hi @zwifi, thanks again. I think I have made a mistake in my previous reply stating the Token request as the Authorisation request. Sorry for that. So here’s the complete flow that I use.

**1. Dynamic client registration**

```auto
final regResponse = await http.post(Uri.parse(regEndpoint),
    headers: <String, String>{
      'Accept': '*/*',
      'Content-Type': 'application/json',
      'Connection': 'keep-alive',
      'Accept-Encoding': 'gzip, deflate, br',
    },
    body: json.encode({
      'application_type': 'web',
      'scope': 'openid profile offline_access',
      'grant_types': ['authorization_code', 'refresh_token'],
      'redirect_uris': reidirUrlList,
      'token_endpoint_auth_method': 'client_secret_basic',
    }));

```

**2. Authorisation request**

```auto
final authResponse = await http.get(Uri.parse(authEndpoint),
    headers: <String, String>{
      'Accept': '*/*',
      'response_type': 'code',
      'scope': 'openid profile offline_access'
      'client_id': client.clientId,
      'redirect_uri'= 'uri',
      'code_challenge'_method': 'S256',
      'code_challenge': 'iyWwZ6lXXICIeBzwNYAap7...'
    });

```

**3. Token request**

```auto
var h = base64.encode('${client.clientId}:${client.clientSecret}'.codeUnits);
final tokenResponse = await http.post(client.issuer.tokenEndpoint,
    headers: <String, String> {
      'Accept': '*/*',
      'Accept-Encoding': 'gzip, deflate, br',
      'content-type': 'application/x-www-form-urlencoded',
      'Authorization': 'Basic $h',
    },
    body: json.encode({
      'grant_type': 'authorization_code',
      'code': code,
      'redirect_uri': redirectUri.toString(),
      'client_id': client.clientId,
      'code_verifier': _proofKeyForCodeExchange['code_verifier']
    }));

```

But I am still not getting the refresh\_token in the token response. I only get the following for the token response.

```auto
{expires_at: 1710475763, 
access_token: eyJhbGciOiJ..., 
expires_in: 3600, 
id_token: eyJhbGciOiJFUzI1NiIsInR5c..., 
scope: *this field is empty for some reason*, 
token_type: Bearer}

```

---

<div class="post-metadata">

**Author:** ![zwifi](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.solidproject.org/zwifi/32/1525_2.png) [@zwifi](https://forum.solidproject.org/u/zwifi)\
**Post date:** [March 15, 2024, 8:29am UTC](https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310/6 "2024-03-15T08:29:59Z")

</div>

Thanks for the detailed flow, that’s very thorough. Unfortunately, nothing jumps at me from an OpenID point of view, all looks correct to me from a protocol perspective. When you log in, you should get some kind of consent prompt asking you to authorize our client, does it mention any scopes?

In any case, I’m afraid I won’t be able to help. If there aren’t other responses here, maybe you could open an issue on the CSS GitHub repository, you may have some configuration issue, as you mentioned initially.

---

<div class="post-metadata">

**Author:** ![josephguillaume](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.solidproject.org/josephguillaume/32/1072_2.png) [@josephguillaume](https://forum.solidproject.org/u/josephguillaume)\
**Post date:** [March 18, 2024, 7:06am UTC](https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310/7 "2024-03-18T07:06:31Z")

</div>

node-oidc-provider, which CSS uses, is pretty strict.  
I don’t think you’ve shown here what prompt is being specified - maybe you’re running into this issue?

> <https://github.com/CommunitySolidServer/CommunitySolidServer/issues/1058>
>
> The intended behaviour with restorePreviousSession is to receive an access\_token… (default ttl 60min) and a refresh\_token (ttl 24h) with prompt=none rather than prompt=consent.
> 
> As discussed at https://github.com/solid/community-server/issues/909, deviating from prompt=consent is not entirely consistent with the OIDC spec. However, requiring prompt=consent means that consent is required at every page refresh, and not providing a refresh\_token means that authorised operations are not possible after 60min without repeating authentication and consent again.
> 
> At the moment it seems the intention of CSS is to assume consent if the user has selected "remember", and the UI implies that the user will not need to provide consent again for some unspecified time/a number of future authentications.
> 
> https://github.com/solid/community-server/blob/4da1b1cc7df15f32abeacf6f7a8b0d677225e9ef/src/identity/interaction/util/InteractionCompleter.ts#L40
> 
> However, rejectedScopes does not accept offline\_access when shouldRemember is true, it merely avoids rejecting it.
> Removal of the offline\_access scope occurs at 
> https://github.com/panva/node-oidc-provider/blob/d7a5ba5ba191d5af8e4bed9449cbb43a3d5a1619/lib/actions/authorization/check\_scope.js#L34
> 
> This can be tested with a token request with scope="openid offline\_access" and prompt=none, as is issued by solid-client-authn-browser when refreshing a logged in page with restorePreviousSession=true. Refresh\_token is not provided in the response.
> 
> As discussed at https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#refresh-tokens and https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#issuerefreshtoken this behaviour can be changed by overriding the issueRefreshToken function in the config:
> https://github.com/solid/community-server/blob/a1579f625a68ac97096ed476a55075a748258453/src/identity/configuration/IdentityProviderFactory.ts#L136
> However, it's not clear to me how the "remember" status would be accessed from this function.
> The following function obviously returns a refresh token for every token request:
> \`\`\`
> async function issueRefreshToken(ctx, client, code) {
> if (!client.grantTypeAllowed('refresh\_token')) {                     
> return false;                                                    
> }
> return true;                                                     
> }                                                                  
> config.issueRefreshToken=issueRefreshToken;
> \`\`\`
> 
> 
> A workaround is to keep the default restorePreviousSession=false, so consent is requested every time and the refresh\_token is consistently issued.

Unfortunately the problem you’re running into seems like it would require debugging of node-oidc-provider within CSS, which is a bit tricky.

---

<div class="post-metadata">

**Author:** ![Anushka](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.solidproject.org/anushka/32/3134_2.png) [@Anushka](https://forum.solidproject.org/u/Anushka)\
**Post date:** [March 21, 2024, 11:28pm UTC](https://forum.solidproject.org/t/getting-a-refresh-token-from-css/7310/8 "2024-03-21T23:28:26Z")

</div>

Hi @zwifi and @josephguillaume,

Thanks a lot for your responses with clear explanations.

The change suggested by @josephguillaume worked finally. I wasn’t specifying the `prompt` specifically in my requests earlier. Thanks for pointing that out.

So for anyone who is following the thread here’s the final flow of requests that I am using now to get the response with a refresh token.

**1. Dynamic client registration**

```auto
final regResponse = await http.post(Uri.parse(regEndpoint),
    headers: <String, String>{
      'Accept': '*/*',
      'Content-Type': 'application/json',
      'Connection': 'keep-alive',
      'Accept-Encoding': 'gzip, deflate, br',
    },
    body: json.encode({
      'application_type': 'web',
      'scope': 'openid profile offline_access',
      'grant_types': ['authorization_code', 'refresh_token'],
      'redirect_uris': reidirUrlList,
      'token_endpoint_auth_method': 'client_secret_basic',
    }));

```

**2. Authorisation request**

```auto
final authResponse = await http.get(Uri.parse(authEndpoint),
    headers: <String, String>{
      'Accept': '*/*',
      'response_type': 'code',
      'scope': 'openid profile offline_access'
      'client_id': client.clientId,
      'redirect_uri': 'uri',
      'prompt': 'consent',
      'state': '8LZkwOr...'
      'code_challenge_method': 'S256',
      'code_challenge': 'iyWwZ6lXXICIeBzwNYAap7...'
    });

```

**3. Token request**

```auto
var h = base64.encode('${client.clientId}:${client.clientSecret}'.codeUnits);
final tokenResponse = await http.post(client.issuer.tokenEndpoint,
    headers: <String, String> {
      'Accept': '*/*',
      'Accept-Encoding': 'gzip, deflate, br',
      'content-type': 'application/x-www-form-urlencoded',
      'Authorization': 'Basic $h',
    },
    body: json.encode({
      'grant_type': 'authorization_code',
      'code': code,
      'redirect_uri': redirectUri.toString(),
      'client_id': client.clientId,
      'code_verifier': _proofKeyForCodeExchange['code_verifier']
    }));

```

With this the server responds with all three tokens, `access_token`, `id_token`, and `refresh_token`.
