Skip to content

Commit b3871b4

Browse files
committed
Add docs and changelog
1 parent ddf8231 commit b3871b4

File tree

3 files changed

+138
-27
lines changed

3 files changed

+138
-27
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ Unreleased
33
- ⚠️ [Breaking] Removes `ShopifyApp::JWTMiddleware` and `ShopifyApp::JWT` See [Upgrading](/docs/Upgrading.md) for more migration. [1960](https://github.com/Shopify/shopify_app/pull/1960)
44
- ⚠️ [Breaking] Removed deprecated `CallbackController` methods. `perform_after_authenticate_job`, `install_webhooks`, and `perform_post_authenticate_jobs` have been removed. [#1961](https://github.com/Shopify/shopify_app/pull/1961)
55
- ⚠️ [Breaking] Bumps minimum supported Ruby version to 3.1 [#1959](https://github.com/Shopify/shopify_app/pull/1959)
6+
- Adds automatic offline access token refresh support for `ShopSessionStorage`. When using `with_shopify_session`, expired offline access tokens will automatically be refreshed using refresh tokens. [#XXXX](https://github.com/Shopify/shopify_app/pull/XXXX)
7+
- Adds `refresh_token_if_expired!` public method to `ShopSessionStorage` for manual token refresh
8+
- Adds `RefreshTokenExpiredError` exception raised when refresh token itself is expired
9+
- `ShopSessionStorage` now automatically stores `access_scopes`, `expires_at`, `refresh_token`, and `refresh_token_expires_at` from auth sessions
10+
- `UserSessionStorage` now automatically stores `access_scopes` and `expires_at` from auth sessions (refresh tokens not applicable for online/user tokens)
11+
- See [Sessions documentation](/docs/shopify_app/sessions.md#automatic-access-token-refresh) for migration guide
12+
- Deprecates `ShopSessionStorageWithScopes` and `UserSessionStorageWithScopes` in favor of `ShopSessionStorage` and `UserSessionStorage`, which now handle session attributes automatically. Will be removed in v23.0.0. [#XXXX](https://github.com/Shopify/shopify_app/pull/XXXX)
613
- Adds a `script_tag_manager` that will automatically create script tags when the app is installed. [1948](https://github.com/Shopify/shopify_app/pull/1948)
714
- Handle invalid token when adding redirection headers [#1945](https://github.com/Shopify/shopify_app/pull/1945)
815
- Handle invalid record error for concurrent token exchange calls [#1966](https://github.com/Shopify/shopify_app/pull/1966)

docs/Upgrading.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,53 @@ If you do run into issues, we recommend looking at our [debugging tips.](https:/
4747
#### (v23.0.0) Drops support for Ruby 3.0
4848
The minimum ruby version is now 3.1
4949

50+
#### (v23.0.0) - ShopSessionStorageWithScopes and UserSessionStorageWithScopes are deprecated
51+
52+
`ShopSessionStorageWithScopes` and `UserSessionStorageWithScopes` are now deprecated in favor of `ShopSessionStorage` and `UserSessionStorage`, which handle all session attributes automatically (including `access_scopes`, `expires_at`, `refresh_token`, and `refresh_token_expires_at` for shops).
53+
54+
**Migration:**
55+
56+
1. Update your Shop model to use `ShopSessionStorage`:
57+
```ruby
58+
# Before
59+
class Shop < ActiveRecord::Base
60+
include ShopifyApp::ShopSessionStorageWithScopes
61+
end
62+
63+
# After
64+
class Shop < ActiveRecord::Base
65+
include ShopifyApp::ShopSessionStorage
66+
end
67+
```
68+
69+
2. Update your User model to use `UserSessionStorage`:
70+
```ruby
71+
# Before
72+
class User < ActiveRecord::Base
73+
include ShopifyApp::UserSessionStorageWithScopes
74+
end
75+
76+
# After
77+
class User < ActiveRecord::Base
78+
include ShopifyApp::UserSessionStorage
79+
end
80+
```
81+
82+
3. **Optional but recommended:** Add columns to enable automatic token refresh for shops:
83+
```ruby
84+
class AddTokenRefreshFieldsToShops < ActiveRecord::Migration[7.0]
85+
def change
86+
add_column :shops, :expires_at, :datetime
87+
add_column :shops, :refresh_token, :string
88+
add_column :shops, :refresh_token_expires_at, :datetime
89+
end
90+
end
91+
```
92+
93+
With these columns, `ShopSessionStorage#with_shopify_session` will automatically refresh expired offline access tokens. See the [Sessions documentation](/docs/shopify_app/sessions.md#automatic-access-token-refresh) for more details.
94+
95+
**Note:** If you had custom `access_scopes=` or `access_scopes` methods in your models, these are no longer needed. The base concerns now handle these attributes automatically.
96+
5097
#### (v23.0.0) - Deprecated methods in CallbackController
5198
The following methods from `ShopifyApp::CallbackController` have been deprecated in `v23.0.0`
5299
- `perform_after_authenticate_job`

docs/shopify_app/sessions.md

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ These methods are already implemented as a part of the `User` and `Shop` models
129129
Simply include these concerns if you want to use the implementation, and overwrite methods for custom implementation
130130

131131
- `Shop` storage
132-
- [ShopSessionStorageWithScopes](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/session/shop_session_storage_with_scopes.rb)
132+
- [ShopSessionStorageWithScopes](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/session/shop_session_storage_with_scopes.rb) (Deprecated in 23.0.0)
133133
- [ShopSessionStorage](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/session/shop_session_storage.rb)
134134

135135
- `User` storage
136-
- [UserSessionStorageWithScopes](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/session/user_session_storage_with_scopes.rb)
136+
- [UserSessionStorageWithScopes](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/session/user_session_storage_with_scopes.rb) (Deprecated in 23.0.0)
137137
- [UserSessionStorage](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/session/user_session_storage.rb)
138138

139139
### Loading Sessions
@@ -206,6 +206,60 @@ user.with_shopify_session do
206206
end
207207
```
208208

209+
##### Automatic Access Token Refresh
210+
211+
`ShopSessionStorage` includes automatic token refresh for expired offline access tokens. When using `with_shopify_session` on a Shop model, the gem will automatically refresh the access token if it has expired, using the refresh token stored in the database.
212+
213+
**Requirements:**
214+
- Shop model must include `ShopSessionStorage` concern
215+
- Database must have the following columns:
216+
- `expires_at` (datetime) - when the access token expires
217+
- `refresh_token` (string) - the refresh token
218+
- `refresh_token_expires_at` (datetime) - when the refresh token expires
219+
220+
**Migration Example:**
221+
```ruby
222+
class AddTokenRefreshFieldsToShops < ActiveRecord::Migration[7.0]
223+
def change
224+
add_column :shops, :expires_at, :datetime
225+
add_column :shops, :refresh_token, :string
226+
add_column :shops, :refresh_token_expires_at, :datetime
227+
end
228+
end
229+
```
230+
231+
**Usage:**
232+
```ruby
233+
shop = Shop.find_by(shopify_domain: "example.myshopify.com")
234+
235+
# Automatic refresh (default behavior)
236+
shop.with_shopify_session do
237+
# If the token is expired, it will be automatically refreshed before making API calls
238+
ShopifyAPI::Product.all
239+
end
240+
241+
# Disable automatic refresh if needed
242+
shop.with_shopify_session(auto_refresh: false) do
243+
# Token will NOT be refreshed even if expired
244+
ShopifyAPI::Product.all
245+
end
246+
247+
# Manual refresh
248+
begin
249+
shop.refresh_token_if_expired!
250+
rescue ShopifyApp::RefreshTokenExpiredError
251+
# Handle case where refresh token itself has expired
252+
# App needs to go through OAuth flow again
253+
end
254+
```
255+
256+
**Error Handling:**
257+
- `ShopifyApp::RefreshTokenExpiredError` is raised when the refresh token itself is expired
258+
- When this happens, the shop must go through the OAuth flow again to get new tokens
259+
- The refresh process uses database row-level locking to prevent race conditions from concurrent requests
260+
261+
**Note:** Refresh tokens are only available for offline (shop) access tokens. Online (user) access tokens do not support refresh and must be re-authorized through OAuth when expired.
262+
209263
#### Re-fetching an access token when API returns Unauthorized
210264

211265
When using `ShopifyApp::EnsureHasSession` and the `new_embedded_auth_strategy` configuration, any **unhandled** Unauthorized `ShopifyAPI::Errors::HttpResponseError` will cause the app to perform token exchange to fetch a new access token from Shopify and the action to be executed again. This will update and store the new access token to the current session instance.
@@ -300,39 +354,42 @@ class MyController < ApplicationController
300354
end
301355
```
302356

303-
## Access scopes
304-
If you want to customize how access scopes are stored for shops and users, you can implement the `access_scopes` getters and setters in the models that include `ShopifyApp::ShopSessionStorageWithScopes` and `ShopifyApp::UserSessionStorageWithScopes` as shown:
357+
## Expiry date
358+
When the configuration flag `check_session_expiry_date` is set to true, the session expiry date will be checked to trigger a re-auth and get a fresh user token when it is expired.
359+
This requires the `ShopifyAPI::Auth::Session` `expires` attribute to be stored.
305360

306-
### `ShopifyApp::ShopSessionStorageWithScopes`
307-
```ruby
308-
class Shop < ActiveRecord::Base
309-
include ShopifyApp::ShopSessionStorageWithScopes
361+
### Online access tokens
362+
When the `User` model includes the `UserSessionStorage` concern, a DB migration can be generated with `rails generate shopify_app:user_model --skip` to add the `expires_at` attribute to the model.
310363

311-
def access_scopes=(scopes)
312-
# Store access scopes
313-
end
314-
def access_scopes
315-
# Find access scopes
316-
end
317-
end
318-
```
364+
Online access tokens can not be refreshed, so when the token is expired, the user must go through the OAuth flow again to get a new token.
365+
366+
### Offline access tokens
367+
368+
**Optional Configuration:** By default, offline access tokens do not expire. However, you can opt-in to expiring offline access tokens for enhanced security by configuring it through `ShopifyAPI::Context`:
319369

320-
### `ShopifyApp::UserSessionStorageWithScopes`
321370
```ruby
322-
class User < ActiveRecord::Base
323-
include ShopifyApp::UserSessionStorageWithScopes
371+
# config/initializers/shopify_app.rb
372+
ShopifyApp.configure do |config|
373+
# ... other configuration
324374

325-
def access_scopes=(scopes)
326-
# Store access scopes
327-
end
328-
def access_scopes
329-
# Find access scopes
330-
end
375+
# Enable checking session expiry dates
376+
config.check_session_expiry_date = true
331377
end
378+
379+
# For ShopifyAPI Context - enable expiring offline tokens
380+
ShopifyAPI::Context.setup(
381+
# ... other configuration
382+
offline_access_token_expires: true, # Opt-in to expiring offline tokens
383+
)
332384
```
333385

334-
## Expiry date
335-
When the configuration flag `check_session_expiry_date` is set to true, the user session expiry date will be checked to trigger a re-auth and get a fresh user token when it is expired. This requires the `ShopifyAPI::Auth::Session` `expires` attribute to be stored. When the `User` model includes the `UserSessionStorageWithScopes` concern, a DB migration can be generated with `rails generate shopify_app:user_model --skip` to add the `expires_at` attribute to the model.
386+
When expiring offline tokens are enabled, Shopify will issue offline access tokens with an expiration date and a refresh token. Your app can then automatically refresh these tokens when they expire.
387+
388+
**Database Setup:** When the `Shop` model includes the `ShopSessionStorage` concern, a DB migration can be generated with `rails generate shopify_app:shop_model --skip` to add the `expires_at`, `refresh_token`, and `refresh_token_expires_at` attributes to the model.
389+
390+
**Automatic Refresh:** Offline access tokens can be automatically refreshed using the stored refresh token when expired. See [Automatic Access Token Refresh](#automatic-access-token-refresh) for more details.
391+
392+
**Note:** If you choose not to enable expiring offline tokens, the `expires_at`, `refresh_token`, and `refresh_token_expires_at` columns will remain `NULL` and no automatic refresh will occur.
336393

337394
## Migrating from shop-based to user-based token strategy
338395

0 commit comments

Comments
 (0)