You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,6 +9,12 @@ Unreleased
9
9
- ⚠️ [Breaking] Removes `ShopifyApp::JWTMiddleware` and `ShopifyApp::JWT` See [Upgrading](/docs/Upgrading.md) for more migration. [1960](https://github.com/Shopify/shopify_app/pull/1960)
10
10
- ⚠️ [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)
11
11
- ⚠️ [Breaking] Bumps minimum supported Ruby version to 3.1 [#1959](https://github.com/Shopify/shopify_app/pull/1959)
12
+
- Adds support for expiring offline access tokens with automatic refresh for `ShopSessionStorage`. Apps can now opt-in to expiring offline tokens via `ShopifyAPI::Context.setup(offline_access_token_expires: true)`, and `ShopSessionStorage` will automatically refresh expired tokens when using `with_shopify_session`. **See [migration guide](/docs/shopify_app/sessions.md#migrating-to-expiring-offline-access-tokens) for setup instructions.**[#2027](https://github.com/Shopify/shopify_app/pull/2027)
13
+
-`ShopSessionStorage` now automatically stores `access_scopes`, `expires_at`, `refresh_token`, and `refresh_token_expires_at` from auth sessions
14
+
-`UserSessionStorage` now automatically stores `access_scopes` and `expires_at` from auth sessions (refresh tokens not applicable for online/user tokens)
15
+
- Adds `refresh_token_if_expired!` public method to `ShopSessionStorage` for manual token refresh
16
+
- Adds `RefreshTokenExpiredError` exception raised when refresh token itself is expired
17
+
- Mark deprecation for `ShopSessionStorageWithScopes` and `UserSessionStorageWithScopes` in favor of `ShopSessionStorage` and `UserSessionStorage`.
12
18
- 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)
13
19
- Handle invalid token when adding redirection headers [#1945](https://github.com/Shopify/shopify_app/pull/1945)
14
20
- Handle invalid record error for concurrent token exchange calls [#1966](https://github.com/Shopify/shopify_app/pull/1966)
-**sprockets-rails**: Now a required runtime dependency. Most Rails apps already include this, but if your app uses an alternative asset pipeline (e.g., Propshaft), you may need to add `sprockets-rails` to your Gemfile.
82
82
83
+
#### (v23.0.0) - ShopSessionStorageWithScopes and UserSessionStorageWithScopes are deprecated
84
+
85
+
`ShopSessionStorageWithScopes` and `UserSessionStorageWithScopes` are now marked as deprecated and will be removed in v24.0.0 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).
86
+
87
+
**Migration:**
88
+
89
+
1. Update your Shop model to use `ShopSessionStorage`:
90
+
```ruby
91
+
# Before
92
+
classShop < ActiveRecord::Base
93
+
includeShopifyApp::ShopSessionStorageWithScopes
94
+
end
95
+
96
+
# After
97
+
classShop < ActiveRecord::Base
98
+
includeShopifyApp::ShopSessionStorage
99
+
end
100
+
```
101
+
102
+
2. Update your User model to use `UserSessionStorage`:
103
+
```ruby
104
+
# Before
105
+
classUser < ActiveRecord::Base
106
+
includeShopifyApp::UserSessionStorageWithScopes
107
+
end
108
+
109
+
# After
110
+
classUser < ActiveRecord::Base
111
+
includeShopifyApp::UserSessionStorage
112
+
end
113
+
```
114
+
115
+
3.**Optional:** You can now opt-in to using expiring offline access tokens with automatic refresh. See the [Sessions documentation](/docs/shopify_app/sessions.md#offline-access-tokens) for setup instructions.
116
+
117
+
**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.
118
+
83
119
#### (v23.0.0) - Deprecated methods in CallbackController
84
120
The following methods from `ShopifyApp::CallbackController` have been deprecated in `v23.0.0`
-[Migrating to Expiring Offline Access Tokens](#migrating-to-expiring-offline-access-tokens)
26
27
-[Migrating from shop-based to user-based token strategy](#migrating-from-shop-based-to-user-based-token-strategy)
27
28
-[Migrating from `ShopifyApi::Auth::SessionStorage` to `ShopifyApp::SessionStorage`](#migrating-from-shopifyapiauthsessionstorage-to-shopifyappsessionstorage)
28
29
@@ -129,11 +130,11 @@ These methods are already implemented as a part of the `User` and `Shop` models
129
130
Simply include these concerns if you want to use the implementation, and overwrite methods for custom implementation
-[ShopSessionStorageWithScopes](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/session/shop_session_storage_with_scopes.rb) (Deprecated in 23.0.0)
-[UserSessionStorageWithScopes](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/session/user_session_storage_with_scopes.rb) (Deprecated in 23.0.0)
When using `Shop` models with [expiring offline access tokens](#migrating-to-expiring-offline-access-tokens) configured, `with_shopify_session` will automatically refresh expired tokens before executing the block. This ensures your API calls always use valid credentials without manual intervention.
213
+
214
+
To disable automatic refresh, pass `auto_refresh: false`:
215
+
216
+
```ruby
217
+
shop.with_shopify_session(auto_refresh:false) do
218
+
# Token will NOT be refreshed even if expired
219
+
end
220
+
```
221
+
209
222
#### Re-fetching an access token when API returns Unauthorized
210
223
211
224
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 +313,153 @@ class MyController < ApplicationController
300
313
end
301
314
```
302
315
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:
316
+
## Expiry date
317
+
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 token when it is expired.
318
+
This requires the `ShopifyAPI::Auth::Session``expires` attribute to be stored.
319
+
320
+
**Online access tokens (User sessions):**
321
+
- 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
322
+
- Online access tokens cannot be refreshed, so when the token is expired, the user must go through the OAuth flow again to get a new token
323
+
324
+
**Offline access tokens (Shop sessions):**
325
+
- Offline access tokens can optionally be configured to expire and support automatic refresh. See [Migrating to Expiring Offline Access Tokens](#migrating-to-expiring-offline-access-tokens) for detailed setup instructions
326
+
327
+
## Migrating to Expiring Offline Access Tokens
328
+
329
+
You can opt-in to expiring offline access tokens for enhanced security. When enabled, Shopify will issue offline access tokens with an expiration date and a refresh token. `ShopSessionStorage` will then automatically refresh expired tokens when using `with_shopify_session`.
330
+
331
+
**1. Database Migration:**
332
+
333
+
Run the shop model generator (use `--skip` to avoid regenerating the Shop model if it already exists):
334
+
335
+
```bash
336
+
rails generate shopify_app:shop_model --skip
337
+
```
338
+
339
+
The generator will prompt you to create a migration that adds the `expires_at`, `refresh_token`, and `refresh_token_expires_at` columns. Alternatively, you can create the migration manually:
If your Shop model is using the deprecated `ShopSessionStorageWithScopes` concern, update it to use `ShopSessionStorage`:
305
354
306
-
### `ShopifyApp::ShopSessionStorageWithScopes`
307
355
```ruby
356
+
# app/models/shop.rb
308
357
classShop < ActiveRecord::Base
309
-
includeShopifyApp::ShopSessionStorageWithScopes
358
+
includeShopifyApp::ShopSessionStorage# Change from ShopSessionStorageWithScopes
359
+
end
360
+
```
310
361
311
-
defaccess_scopes=(scopes)
312
-
# Store access scopes
313
-
end
314
-
defaccess_scopes
315
-
# Find access scopes
316
-
end
362
+
`ShopSessionStorage` now automatically handles `access_scopes`, `expires_at`, `refresh_token`, and `refresh_token_expires_at` - no additional concerns needed.
363
+
364
+
**3. Configuration:**
365
+
366
+
```ruby
367
+
# config/initializers/shopify_app.rb
368
+
ShopifyApp.configure do |config|
369
+
# ... other configuration
370
+
371
+
# Enable automatic reauthentication when session is expired
372
+
config.check_session_expiry_date =true
317
373
end
374
+
375
+
# For ShopifyAPI Context - enable requesting expiring offline tokens
376
+
ShopifyAPI::Context.setup(
377
+
# ... other configuration
378
+
expiring_offline_access_tokens:true, # Opt-in to start requesting expiring offline tokens
379
+
)
318
380
```
319
381
320
-
### `ShopifyApp::UserSessionStorageWithScopes`
382
+
**4. Refreshing Expired Tokens:**
383
+
384
+
With the configuration enabled, expired tokens are automatically handled differently based on the flow:
When `check_session_expiry_date` is enabled, expired sessions trigger automatic re-authentication through the OAuth flow. This happens transparently when using controller concerns like `EnsureHasSession`.
388
+
389
+
**For background jobs and non-user interactions:**
390
+
Tokens are automatically refreshed when using `with_shopify_session` from `ShopSessionStorage`:
# If the token is expired, it will be automatically refreshed before making API calls
398
+
end
399
+
400
+
# Disable automatic refresh if needed
401
+
shop.with_shopify_session(auto_refresh:false) do
402
+
# Token will NOT be refreshed even if expired
403
+
end
404
+
405
+
# Manual refresh
406
+
begin
407
+
shop.refresh_token_if_expired!
408
+
rescueShopifyApp::RefreshTokenExpiredError
409
+
# Handle case where refresh token itself has expired
410
+
# App needs to go through OAuth flow again
411
+
end
412
+
```
413
+
414
+
**Error Handling:**
415
+
-`ShopifyApp::RefreshTokenExpiredError` is raised when the refresh token itself is expired
416
+
- When this happens, the user must interact with the app to go through the OAuth flow again to get new tokens
417
+
- The refresh process uses database row-level locking to prevent race conditions from concurrent requests
418
+
419
+
**5. Migrating Existing Shop Installations:**
420
+
421
+
⚠️ **Important:** When you enable `offline_access_token_expires: true`, only **new shop installations** will automatically receive expiring tokens during the OAuth flow. Existing shop installations with non-expiring tokens will continue using their current tokens until manually migrated.
422
+
423
+
To migrate existing shops to expiring tokens, use the `ShopifyAPI::Auth::TokenExchange.migrate_to_expiring_token` method. Here's an example background job to migrate all existing shops:
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.
457
+
**Migration notes:**
458
+
- This is a **one-time, irreversible operation** per shop
459
+
- The shop must have the app installed and have a valid access token
460
+
- After migration, the shop's offline token will have an expiration date and a refresh token
461
+
462
+
**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. 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.
336
463
337
464
## Migrating from shop-based to user-based token strategy
0 commit comments