Skip to content

Conversation

@zendesk-mradmacher
Copy link

@zendesk-mradmacher zendesk-mradmacher commented Oct 8, 2025

Add support for refreshing OAuth access token.

When you need some inspiration, consult how the token refresh flow is done by Restforce, the public gem for Salesforce REST API.

I add a new ZendeskAPI::TokenRefresher service, to obtain new access and refresh tokens. It takes client configuration as a parameter. When tokens get refreshed, the client configuration is updated, so the next requests are issued using the newly obtained access token. Also a block is yielded with new access and refresh tokens, so they could be stored for further use.

The alternative solution is to use ZendeskAPI::Middleware::Response::TokenRefresher, to start the process of token refreshing each time when the API responds with 401. The middleware also updates the client configuration. It requires a callback to be configured. The called is called whenever new tokens are obtained to allow storing them for further use.
There is a configuration option to enable middleware usage.

How to test?

In Admin Center, Apps and Integrations / APIs / OAuth Clients create a new client. Note client id and secret and redirect URL. The redirect URL can be any address.

  • Login to Zendesk as an user.
  • Assumming your Zendesk subdomain is "testing", open the following URL in browser:
https://testing.zendesk.com/oauth/authorizations/new?client_id=<your client id>&response_type=code&redirect_uri=<encoded redirect URL>&scope=read 

The encoded URL looks like https%3A%2F%2Fexample.com.

  • Accept the authorization and intercept the code issued to the redirect URL.
  • Request for access and refresh tokens using the intercepted code:
curl https://testing.zendesk.com/oauth/tokens \ -H "Content-Type: application/json" \ -d '{"grant_type": "authorization_code", "code": "<here goes the code>", "client_id": "<your client id>", "client_secret": "<your client secret>", "redirect_uri": "<your redirect URL", "scope": "read", "expires_in": 300 }' \ -X POST 

expires_in is chosen to be 5 minutes (the lowest possible value), to make the access token expire quickly, for easier testing.

  • Save tokens in a file named tokens in the following format:
<access token>,<refresh token> 
  • Use the following Ruby script to make requests to your Zendesk. Observe how tokens are refreshed upon expiring.
require 'zendesk_api' # `tokens` is a one line file that stores comma separated tokens: # <access token>,<refresh token> access_token, refresh_token = File.open("tokens") do |file| file.read.strip.split(",") end client = ZendeskAPI::Client.new do |config| # Mandatory: config.url = "https://testing.zendesk.com/api/v2" config.client_id = "<your client id>" config.client_secret = "<your client secret>" config.access_token_expiration = 300 # expire quickly to observe token refreshing config.access_token = access_token config.refresh_token = refresh_token # In case of using the middleware for refreshing tokens, update the storage with new tokens # config.auto_refresh_tokens = true # config.refresh_tokens_callback = lambda do |access_token, refresh_token| # File.open("tokens", "w") do |file| # file.write("#{access_token},#{refresh_token}") # end # end require 'logger' config.logger = Logger.new(STDOUT) end users = client.users.per_page(1) begin # A request with an expired access token is made. users.fetch! # The request is rejected with 401 status code. rescue ZendeskAPI::Error::Unauthorized # Refresh tokens and store them securely ZendeskAPI::TokenRefresher.new(client.config).refresh_token do |access_token, refresh_token| File.open("tokens", "w") do |file| file.write("#{access_token},#{refresh_token}") end end # Issue the request again. users.fetch! end 
@zendesk-mradmacher zendesk-mradmacher requested a review from a team October 13, 2025 11:26
@zendesk-mradmacher zendesk-mradmacher force-pushed the mradmacher/refresh-token-support branch 2 times, most recently from 1b028ca to e1d2604 Compare November 3, 2025 14:23
@zendesk-mradmacher zendesk-mradmacher marked this pull request as ready for review November 3, 2025 14:23
@zendesk-mradmacher zendesk-mradmacher requested a review from a team as a code owner November 3, 2025 14:23
@zendesk-mradmacher zendesk-mradmacher requested a review from a team November 6, 2025 09:35
@zendesk-mradmacher zendesk-mradmacher force-pushed the mradmacher/refresh-token-support branch 2 times, most recently from b60624d to 9ad3fc8 Compare November 6, 2025 09:56
README.md Outdated
Comment on lines 70 to 76
# You can configure token refreshening by adding the OAuth client ID, secret and refresh token:
config.client_id = "your OAuth client id"
config.client_secret = "your OAuth client secret"
config.refresh_token = "your OAuth refresh token"
# Optionally you can set access and refresh token expiration:
config.access_token_expiration = 300 # time in seconds between 5 minutes and 2 days (300 and 172800)
config.refresh_token_expiration = 605800 # time in seconds between 7 and 90 days (605800 and 7776000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be below the "Optional" header on line 78?

return unless ERROR_CODES.include?(env[:status])

ZendeskAPI::TokenRefresher.new(@config).refresh_token do |access_token, refresh_token|
if @config.refresh_token_callback.is_a?(Proc)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we instead check the value when it's being set, not every time it is used? And set the default value to a "null lambda", i.e. -> {} instead of nil.

@zendesk-mradmacher zendesk-mradmacher force-pushed the mradmacher/refresh-token-support branch 3 times, most recently from b8da5d2 to 482a435 Compare November 6, 2025 14:26
add dedicated unauthorized error allow token refreshing middleware to be enabled in configuration
@zendesk-mradmacher zendesk-mradmacher force-pushed the mradmacher/refresh-token-support branch from 482a435 to 544eee2 Compare November 6, 2025 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants