I'd consider giving the app a new token and expiring all old tokens instead. That way if an attacker gets hold of the refresh token the genuine  client and the attacker will raise for a valid access token and we later can think about measures to detect that and invalidate the refresh token altogether.

Another issue in this method is that the requester is not validated, one approach could be to generate a new keypair on the client, have the client send the public key alongside the authorization request and require a signed message upon renewal. That by itself doesn't make it safe against replay attacks, so we would need other properties than the refresh token to be included into the signed message. That's just a first rough idea I didn't think through fully, other approaches could involve things like HMACs.