shopify_app 21.0.0 → 22.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/ISSUE_TEMPLATE/ENHANCEMENT.md +9 -0
- data/.github/ISSUE_TEMPLATE/bug-report.md +30 -47
- data/.github/ISSUE_TEMPLATE/feature-request.md +5 -29
- data/.github/workflows/build.yml +11 -12
- data/.github/workflows/release.yml +2 -2
- data/.github/workflows/remove-labels-on-activity.yml +1 -1
- data/.github/workflows/rubocop.yml +2 -3
- data/.nvmrc +1 -1
- data/.rubocop.yml +2 -1
- data/.ruby-version +1 -1
- data/.spin/rails/prepare-application +8 -0
- data/CHANGELOG.md +173 -7
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +16 -6
- data/Gemfile +1 -0
- data/Gemfile.lock +160 -121
- data/README.md +67 -19
- data/SECURITY.md +1 -1
- data/app/assets/javascripts/shopify_app/redirect.js +3 -10
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +9 -4
- data/app/controllers/concerns/shopify_app/ensure_has_session.rb +25 -0
- data/app/controllers/concerns/shopify_app/ensure_installed.rb +84 -0
- data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +5 -1
- data/app/controllers/shopify_app/authenticated_controller.rb +1 -1
- data/app/controllers/shopify_app/callback_controller.rb +101 -39
- data/app/controllers/shopify_app/extension_verification_controller.rb +4 -1
- data/app/controllers/shopify_app/sessions_controller.rb +37 -7
- data/app/controllers/shopify_app/webhooks_controller.rb +1 -1
- data/app/views/shopify_app/layouts/app_bridge.html.erb +17 -0
- data/app/views/shopify_app/sessions/patch_shopify_id_token.html.erb +0 -0
- data/app/views/shopify_app/shared/redirect.html.erb +10 -1
- data/config/locales/cs.yml +0 -18
- data/config/locales/da.yml +0 -15
- data/config/locales/de.yml +0 -17
- data/config/locales/en.yml +0 -11
- data/config/locales/es.yml +0 -17
- data/config/locales/fi.yml +0 -15
- data/config/locales/fr.yml +0 -18
- data/config/locales/it.yml +0 -16
- data/config/locales/ja.yml +0 -12
- data/config/locales/ko.yml +0 -14
- data/config/locales/nb.yml +0 -16
- data/config/locales/nl.yml +0 -16
- data/config/locales/pl.yml +0 -16
- data/config/locales/pt-BR.yml +0 -16
- data/config/locales/pt-PT.yml +0 -17
- data/config/locales/sv.yml +0 -16
- data/config/locales/th.yml +0 -15
- data/config/locales/tr.yml +0 -17
- data/config/locales/vi.yml +0 -17
- data/config/locales/zh-CN.yml +0 -11
- data/config/locales/zh-TW.yml +0 -11
- data/config/routes.rb +2 -1
- data/docs/Quickstart.md +14 -5
- data/docs/Troubleshooting.md +38 -25
- data/docs/Upgrading.md +103 -32
- data/docs/shopify_app/authentication.md +179 -58
- data/docs/shopify_app/controller-concerns.md +89 -0
- data/docs/shopify_app/engine.md +2 -11
- data/docs/shopify_app/generators.md +2 -2
- data/docs/shopify_app/logging.md +21 -0
- data/docs/shopify_app/sessions.md +358 -0
- data/docs/shopify_app/testing.md +32 -10
- data/docs/shopify_app/webhooks.md +97 -7
- data/karma.conf.js +6 -4
- data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +6 -3
- data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -1
- data/lib/generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator.rb +15 -0
- data/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_declarative_webhook/add_declarative_webhook_generator.rb +53 -0
- data/lib/generators/shopify_app/add_declarative_webhook/templates/webhook_controller.rb.tt +13 -0
- data/lib/generators/shopify_app/add_declarative_webhook/templates/webhook_job.rb.tt +15 -0
- data/lib/generators/shopify_app/add_privacy_jobs/add_privacy_jobs_generator.rb +23 -0
- data/lib/generators/shopify_app/add_privacy_jobs/templates/customers_data_request_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_privacy_jobs/templates/customers_redact_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_privacy_jobs/templates/shop_redact_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +8 -3
- data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +4 -2
- data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +1 -1
- data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +1 -1
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
- data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +1 -1
- data/lib/generators/shopify_app/install/install_generator.rb +4 -4
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +13 -3
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +1 -1
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
- data/lib/generators/shopify_app/routes/routes_generator.rb +1 -1
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +1 -1
- data/lib/generators/shopify_app/shop_model/templates/db/migrate/add_shop_access_scopes_column.erb +1 -1
- data/lib/generators/shopify_app/shopify_app_generator.rb +2 -0
- data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_access_scopes_column.erb +1 -1
- data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_expires_at_column.erb +5 -0
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +21 -1
- data/lib/shopify_app/access_scopes/noop_strategy.rb +4 -0
- data/lib/shopify_app/access_scopes/user_strategy.rb +9 -2
- data/lib/shopify_app/admin_api/with_token_refetch.rb +27 -0
- data/lib/shopify_app/auth/post_authenticate_tasks.rb +48 -0
- data/lib/shopify_app/auth/token_exchange.rb +73 -0
- data/lib/shopify_app/configuration.rb +82 -1
- data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +3 -3
- data/lib/shopify_app/controller_concerns/csrf_protection.rb +2 -1
- data/lib/shopify_app/controller_concerns/embedded_app.rb +42 -3
- data/lib/shopify_app/controller_concerns/ensure_billing.rb +28 -12
- data/lib/shopify_app/controller_concerns/frame_ancestors.rb +1 -1
- data/lib/shopify_app/controller_concerns/localization.rb +11 -8
- data/lib/shopify_app/controller_concerns/login_protection.rb +83 -38
- data/lib/shopify_app/controller_concerns/payload_verification.rb +1 -1
- data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +15 -3
- data/lib/shopify_app/controller_concerns/sanitized_params.rb +5 -0
- data/lib/shopify_app/controller_concerns/token_exchange.rb +111 -0
- data/lib/shopify_app/controller_concerns/webhook_verification.rb +4 -1
- data/lib/shopify_app/controller_concerns/with_shopify_id_token.rb +48 -0
- data/lib/shopify_app/engine.rb +7 -8
- data/lib/shopify_app/logger.rb +28 -0
- data/lib/shopify_app/managers/webhooks_manager.rb +20 -10
- data/lib/shopify_app/middleware/jwt_middleware.rb +13 -9
- data/lib/shopify_app/session/in_memory_user_session_store.rb +1 -1
- data/lib/shopify_app/session/jwt.rb +11 -2
- data/lib/shopify_app/session/session_repository.rb +66 -14
- data/lib/shopify_app/session/session_storage.rb +2 -2
- data/lib/shopify_app/session/shop_session_storage.rb +5 -1
- data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +5 -1
- data/lib/shopify_app/session/user_session_storage.rb +6 -2
- data/lib/shopify_app/session/user_session_storage_with_scopes.rb +27 -2
- data/lib/shopify_app/test_helpers/all.rb +1 -0
- data/lib/shopify_app/test_helpers/shopify_session_helper.rb +16 -0
- data/lib/shopify_app/utils.rb +82 -20
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app.rb +12 -3
- data/package.json +5 -6
- data/service.yml +0 -2
- data/shopify_app.gemspec +6 -5
- data/translation.yml +1 -0
- data/yarn.lock +2139 -3910
- metadata +78 -58
- data/.github/workflows/stale.yml +0 -31
- data/app/assets/images/storage_access.svg +0 -1
- data/app/assets/javascripts/shopify_app/app_bridge_3.1.1.js +0 -10
- data/app/assets/javascripts/shopify_app/app_bridge_redirect.js +0 -22
- data/app/assets/javascripts/shopify_app/app_bridge_utils_3.1.1.js +0 -1
- data/app/assets/javascripts/shopify_app/enable_cookies.js +0 -3
- data/app/assets/javascripts/shopify_app/itp_helper.js +0 -40
- data/app/assets/javascripts/shopify_app/partition_cookies.js +0 -8
- data/app/assets/javascripts/shopify_app/post_redirect.js +0 -9
- data/app/assets/javascripts/shopify_app/request_storage_access.js +0 -3
- data/app/assets/javascripts/shopify_app/storage_access.js +0 -148
- data/app/assets/javascripts/shopify_app/storage_access_redirect.js +0 -17
- data/app/assets/javascripts/shopify_app/top_level.js +0 -2
- data/app/assets/javascripts/shopify_app/top_level_interaction.js +0 -11
- data/app/controllers/concerns/shopify_app/authenticated.rb +0 -19
- data/app/controllers/concerns/shopify_app/require_known_shop.rb +0 -48
- data/app/views/shopify_app/sessions/enable_cookies.html.erb +0 -70
- data/app/views/shopify_app/sessions/request_storage_access.html.erb +0 -68
- data/app/views/shopify_app/sessions/top_level_interaction.html.erb +0 -63
- data/app/views/shopify_app/shared/post_redirect_to_auth_shopify.html.erb +0 -13
- data/docs/shopify_app/script-tags.md +0 -28
- data/docs/shopify_app/session-repository.md +0 -88
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +0 -41
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +0 -62
- data/lib/shopify_app/controller_concerns/itp.rb +0 -45
- data/lib/shopify_app/jobs/scripttags_manager_job.rb +0 -16
- data/lib/shopify_app/managers/scripttags_manager.rb +0 -84
@@ -1,41 +1,184 @@
|
|
1
1
|
# Authentication
|
2
2
|
|
3
|
-
The Shopify App gem implements [OAuth 2.0](https://shopify.dev/tutorials/authenticate-with-oauth) to get [
|
3
|
+
The Shopify App gem implements [OAuth 2.0](https://shopify.dev/tutorials/authenticate-with-oauth) to get [session tokens](https://shopify.dev/concepts/about-apis/authentication#api-access-modes). These are used to authenticate requests made by the app to the Shopify API.
|
4
4
|
|
5
5
|
By default, the gem generates an embedded app frontend that uses [Shopify App Bridge](https://shopify.dev/tools/app-bridge) to fetch [session tokens](https://shopify.dev/concepts/apps/building-embedded-apps-using-session-tokens). Session tokens are used by the embedded app to make authenticated requests to the app backend.
|
6
6
|
|
7
|
-
See [*
|
7
|
+
See [*Getting started with session token authentication*](https://shopify.dev/docs/apps/auth/oauth/session-tokens/getting-started) to learn more.
|
8
8
|
|
9
9
|
> ⚠️ Be sure you understand the differences between the types of authentication schemes before reading this guide.
|
10
10
|
|
11
11
|
#### Table of contents
|
12
12
|
|
13
|
-
[OAuth
|
13
|
+
* [Supported types of OAuth Flow](#supported-types-of-oauth)
|
14
|
+
* [Token Exchange](#token-exchange)
|
15
|
+
* [Authorization Code Grant Flow](#authorization-code-grant-flow)
|
16
|
+
* [OAuth callback](#oauth-callback)
|
17
|
+
* [Customizing callback controller](#customizing-callback-controller)
|
18
|
+
* [Detecting scope changes](#detecting-scope-changes-1)
|
19
|
+
* [Run jobs after the OAuth flow](#post-authenticate-tasks)
|
20
|
+
* [Rotate API credentials](#rotate-api-credentials)
|
21
|
+
* [Making authenticated API requests after authorization](#making-authenticated-api-requests-after-authorization)
|
14
22
|
|
15
|
-
|
23
|
+
## Supported types of OAuth
|
24
|
+
> [!TIP]
|
25
|
+
> If you are building an embedded app, we **strongly** recommend using [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation)
|
26
|
+
with [token exchange](#token-exchange) instead of the authorization code grant flow.
|
16
27
|
|
17
|
-
[
|
28
|
+
1. [Token Exchange](#token-exchange)
|
29
|
+
- Recommended and is only available for embedded apps
|
30
|
+
- Doesn't require redirects, which makes authorization faster and prevents flickering when loading the app
|
31
|
+
- Access scope changes are handled by Shopify when you use [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation)
|
32
|
+
2. [Authorization Code Grant Flow](#authorization-code-grant-flow)
|
33
|
+
- Suitable for non-embedded apps
|
34
|
+
- Installations, and access scope changes are managed by the app
|
18
35
|
|
19
|
-
|
20
|
-
* [`ShopifyApp::Authenticated`](#shopifyappauthenticated)
|
21
|
-
* [`ShopifyApp::EnsureAuthenticatedLinks`](#shopifyappensureauthenticatedlinks)
|
36
|
+
## Token Exchange
|
22
37
|
|
23
|
-
|
38
|
+
OAuth process by exchanging the current user's [session token (shopify id token)](https://shopify.dev/docs/apps/auth/session-tokens) for an
|
39
|
+
[access token](https://shopify.dev/docs/apps/auth/access-token-types/online.md) to make
|
40
|
+
authenticated Shopify API queries. This will replace authorization code grant flow completely when your app is configured with [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).
|
24
41
|
|
25
|
-
|
42
|
+
To enable token exchange authorization strategy, you can follow the steps in ["New embedded app authorization strategy"](/README.md#new-embedded-app-authorization-strategy).
|
43
|
+
Upon completion of the token exchange to get the access token, [post authenticated tasks](#post-authenticate-tasks) will be run.
|
26
44
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
* [Run jobs after the OAuth flow](#run-jobs-after-the-oauth-flow)
|
32
|
-
* Redirecting to the return address
|
45
|
+
Learn more about:
|
46
|
+
- [How token exchange works](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange)
|
47
|
+
- [Using Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation)
|
48
|
+
- [Configuring access scopes through the Shopify CLI](https://shopify.dev/docs/apps/tools/cli/configuration)
|
33
49
|
|
34
|
-
|
50
|
+
#### Handling invalid access tokens
|
51
|
+
If the access token used to make an API call is invalid, the token exchange strategy will handle the error and try to retrieve a new access token before retrying
|
52
|
+
the same operation.
|
53
|
+
See ["Re-fetching an access token when API returns Unauthorized"](/docs/shopify_app/sessions.md#re-fetching-an-access-token-when-api-returns-unauthorized) section for more information.
|
54
|
+
|
55
|
+
#### Detecting scope changes
|
56
|
+
|
57
|
+
##### Shopify managed installation
|
58
|
+
If your access scopes are [configured through the Shopify CLI](https://shopify.dev/docs/apps/tools/cli/configuration), scope changes will be handled by Shopify automatically.
|
59
|
+
Learn more about [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).
|
60
|
+
Using token exchange will ensure that the access token retrieved will always have the latest access scopes granted by the user.
|
61
|
+
|
62
|
+
## Authorization Code Grant Flow
|
63
|
+
Authorization code grant flow is the OAuth flow that requires the app to redirect the user
|
64
|
+
to Shopify for installation/authorization of the app to access the shop's data. It is still required for apps that are not embedded.
|
65
|
+
|
66
|
+
If your app is not using [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation) with declared scopes in your `.toml` file, you can change the requested access scopes during OAuth flow
|
67
|
+
by adding the `scope` to your configurations - `ShopifyApp.configuration` & `ShopifyAPI::Context.setup`.
|
68
|
+
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# config/initializers/shopify_app.rb
|
72
|
+
|
73
|
+
ShopifyApp.configure do |config|
|
74
|
+
...
|
75
|
+
config.scope = ["read_discounts", "write_products"]
|
76
|
+
...
|
77
|
+
end
|
78
|
+
|
79
|
+
ShopifyAPI::Context.setup(
|
80
|
+
...
|
81
|
+
scope: ShopifyApp.configuration.scope,
|
82
|
+
...
|
83
|
+
)
|
84
|
+
```
|
85
|
+
|
86
|
+
To perform [authorization code grant flow](https://shopify.dev/docs/apps/auth/get-access-tokens/authorization-code-grant), you app will need to handle
|
87
|
+
[begin OAuth](#begin-oauth) and [OAuth callback](#oauth-callback) routes.
|
88
|
+
|
89
|
+
### Begin OAuth
|
90
|
+
ShopifyApp automatically redirects the user to Shopify to complete OAuth to install the app when the `ShopifyApp.configuration.login_url` is reached.
|
91
|
+
Behind the scenes the ShopifyApp gem starts the process by calling `ShopifyAPI::Auth::Oauth.begin_auth` to build the
|
92
|
+
redirect URL with necessary parameters like the OAuth callback URL, scopes requested, type of access token (offline or online) requested, etc.
|
93
|
+
The ShopifyApp gem then redirect the merchant to Shopify, to ask for permission to install the app. (See [ShopifyApp::SessionsController.redirect_to_begin_oauth](https://github.com/Shopify/shopify_app/blob/main/app/controllers/shopify_app/sessions_controller.rb#L76-L96)
|
94
|
+
for detailed implementation)
|
95
|
+
|
96
|
+
### OAuth callback
|
97
|
+
|
98
|
+
Shopify will redirect the merchant back to your app's callback URL once they approve the app installation.
|
99
|
+
Upon completing the OAuth flow, Shopify calls the app at `ShopifyApp.configuration.login_callback_url`. (This was provided to Shopify in the OAuth begin URL parameters)
|
100
|
+
|
101
|
+
The default callback controller [`ShopifyApp::CallbackController`](../../app/controllers/shopify_app/callback_controller.rb) provides the following behaviour:
|
102
|
+
|
103
|
+
1. Logging into the shop and resetting the session
|
104
|
+
2. Storing the session to the `SessionRepository`
|
105
|
+
3. [Post authenticate tasks](#post-authenticate-tasks)
|
106
|
+
4. Redirecting to the return address
|
107
|
+
|
108
|
+
#### Customizing callback controller
|
109
|
+
If you need to define a custom callback controller to handle your app's use case, you can configure the callback route to your controller.
|
110
|
+
|
111
|
+
Example:
|
112
|
+
|
113
|
+
1. Create the new custom callback controller
|
114
|
+
```ruby
|
115
|
+
# web/app/controllers/my_custom_callback_controller.rb
|
116
|
+
|
117
|
+
class MyCustomCallbackController
|
118
|
+
def callback
|
119
|
+
# My custom callback logic
|
120
|
+
end
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
2. Override callback routing to this controller
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
# web/config/routes.rb
|
128
|
+
|
129
|
+
Rails.application.routes.draw do
|
130
|
+
root to: "home#index"
|
131
|
+
|
132
|
+
# Overriding the callback controller to the new custom one.
|
133
|
+
# This must be added before mounting the ShopifyApp::Engine
|
134
|
+
get ShopifyApp.configuration.login_callback_url, to: 'my_custom_callback#callback'
|
135
|
+
|
136
|
+
mount ShopifyApp::Engine, at: "/api"
|
137
|
+
|
138
|
+
# other routes
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
### Detecting scope changes
|
143
|
+
When the OAuth process is completed, the created session has a `scope` field which holds all of the access scopes that were requested from the merchant at the time.
|
144
|
+
|
145
|
+
When an app's access scopes change, it needs to request merchants to go through OAuth again to renew its permissions.
|
146
|
+
|
147
|
+
See [Handling changes in access scopes](/docs/shopify_app/handling-access-scopes-changes.md).
|
148
|
+
|
149
|
+
## Post Authenticate tasks
|
150
|
+
After authentication is complete, a few tasks are run by default by PostAuthenticateTasks:
|
151
|
+
1. [Installing Webhooks](/docs/shopify_app/webhooks.md)
|
152
|
+
2. [Run configured after_authenticate_job](#after_authenticate_job)
|
153
|
+
|
154
|
+
The [PostAuthenticateTasks](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/auth/post_authenticate_tasks.rb)
|
155
|
+
class is responsible for triggering the webhooks manager for webhooks registration, and enqueue jobs from [after_authenticate_job](#after_authenticate_job).
|
156
|
+
|
157
|
+
If you simply need to enqueue more jobs to run after authenticate, use [after_authenticate_job](#after_authenticate_job) to define these jobs.
|
158
|
+
|
159
|
+
If your post authentication tasks is more complex and is different than just installing webhooks and enqueuing jobs,
|
160
|
+
you can customize the post authenticate tasks by creating a new class that has a `self.perform(session)` method,
|
161
|
+
and configuring `custom_post_authenticate_tasks` in the initializer.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
# my_custom_post_authenticate_task.rb
|
165
|
+
class MyCustomPostAuthenticateTask
|
166
|
+
def self.perform(session)
|
167
|
+
# This will be triggered after OAuth callback and token exchange completion
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# config/initializers/shopify_app.rb
|
172
|
+
ShopifyApp.configure do |config|
|
173
|
+
config.custom_post_authenticate_tasks = "MyCustomPostAuthenticateTask"
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
#### after_authenticate_job
|
35
178
|
|
36
179
|
See [`ShopifyApp::AfterAuthenticateJob`](/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb).
|
37
180
|
|
38
|
-
If your app needs to perform specific actions after the user is authenticated successfully (i.e. every time a new session is created), ShopifyApp can queue or run a job of your choosing
|
181
|
+
If your app needs to perform specific actions after the user is authenticated successfully (i.e. every time a new session is created), ShopifyApp can queue or run a job of your choosing. To configure the after authenticate job, update your initializer as follows:
|
39
182
|
|
40
183
|
```ruby
|
41
184
|
ShopifyApp.configure do |config|
|
@@ -49,7 +192,7 @@ If you need the job to run synchronously add the `inline` flag:
|
|
49
192
|
|
50
193
|
```ruby
|
51
194
|
ShopifyApp.configure do |config|
|
52
|
-
config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob, inline: true }
|
195
|
+
config.after_authenticate_job = { job: "Shopify::AfterAuthenticateJob", inline: true }
|
53
196
|
end
|
54
197
|
```
|
55
198
|
|
@@ -63,7 +206,7 @@ If you want to perform that action only once, e.g. send a welcome email to the u
|
|
63
206
|
|
64
207
|
## Rotate API credentials
|
65
208
|
|
66
|
-
If your Shopify secret key is leaked, you can use the RotateShopifyTokenJob to perform [API Credential Rotation](https://help.shopify.com/en/api/getting-started/authentication/oauth/api-credential-rotation).
|
209
|
+
If your Shopify secret key is leaked, you can use the `RotateShopifyTokenJob` to perform [API Credential Rotation](https://help.shopify.com/en/api/getting-started/authentication/oauth/api-credential-rotation).
|
67
210
|
|
68
211
|
Before running the job, you'll need to generate a new secret key from your Shopify Partner dashboard, and update the `/config/initializers/shopify_app.rb` to hold your new and old secret keys:
|
69
212
|
|
@@ -72,6 +215,17 @@ config.secret = Rails.application.secrets.shopify_secret
|
|
72
215
|
config.old_secret = Rails.application.secrets.old_shopify_secret
|
73
216
|
```
|
74
217
|
|
218
|
+
Also make sure the old secret is specified when setting up `ShopifyAPI::Context` as well:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
ShopifyAPI::Context.setup(
|
222
|
+
api_key: ShopifyApp.configuration.api_key,
|
223
|
+
api_secret_key: ShopifyApp.configuration.secret,
|
224
|
+
# ...
|
225
|
+
old_api_secret_key: ShopifyApp.configuration.old_secret,
|
226
|
+
)
|
227
|
+
```
|
228
|
+
|
75
229
|
We've provided a generator which creates the job and an example rake task:
|
76
230
|
|
77
231
|
```sh
|
@@ -86,43 +240,10 @@ strategy.options[:old_client_secret] = ShopifyApp.configuration.old_secret
|
|
86
240
|
|
87
241
|
> **Note:** If you are updating `shopify_app` from a version prior to 8.4.2 (and do not wish to run the default/install generator again), you will need to add [the following line](https://github.com/Shopify/shopify_app/blob/4f7e6cca2a472d8f7af44b938bd0fcafe4d8e88a/lib/generators/shopify_app/install/templates/shopify_provider.rb#L18) to `config/initializers/omniauth.rb`:
|
88
242
|
|
89
|
-
##
|
243
|
+
## Making authenticated API requests after authorization
|
244
|
+
After the app is installed onto a shop and has been granted all necessary permission, a new session record will be added to `SessionRepository#shop_storage`, or `SessionRepository#user_storage` if online sessions are enabled.
|
90
245
|
|
91
|
-
|
92
|
-
|
93
|
-
The engine provides a [`ShopifyApp::Authenticated`](/app/controllers/concerns/shopify_app/authenticated.rb) concern which should be included in any controller that is intended to be behind Shopify OAuth. It adds `before_action`s to ensure that the user is authenticated and will redirect to the Shopify login page if not. It is best practice to include this concern in a base controller inheriting from your `ApplicationController`, from which all controllers that require Shopify authentication inherit.
|
94
|
-
|
95
|
-
*Example:*
|
96
|
-
|
97
|
-
```rb
|
98
|
-
class AuthenticatedController < ApplicationController
|
99
|
-
include ShopifyApp::Authenticated
|
100
|
-
end
|
101
|
-
|
102
|
-
class ApiController < AuthenticatedController
|
103
|
-
# Actions in this controller are protected
|
104
|
-
end
|
105
|
-
```
|
106
|
-
|
107
|
-
For backwards compatibility, the engine still provides a controller called `ShopifyApp::AuthenticatedController` which includes the `ShopifyApp::Authenticated` concern. Note that it inherits directly from `ActionController::Base`, so you will not be able to share functionality between it and your application's `ApplicationController`.
|
108
|
-
|
109
|
-
#### Embedded apps and `ShopifyApp::Authenticated`
|
110
|
-
|
111
|
-
Embedded Shopify Admin apps can only use the `ShopifyApp::Authenticated` controller concern *if* the requests originate from App Bridge's `authenticatedFetch` method. Those who include this concern in the `HomeController` or some other embedded controller will see what looks like an OAuth redirect loop as the `ShopifyApp::Authenticated` concern will be fighting with the App Bridge. For more details on how to handle embedded sessions, refer to [the session token documentation](https://shopify.dev/apps/auth/oauth/session-tokens).
|
112
|
-
|
113
|
-
### `ShopifyApp::EnsureAuthenticatedLinks`
|
114
|
-
|
115
|
-
The [`ShopifyApp::EnsureAuthenticatedLinks`](/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb) concern helps authenticate users that access protected pages of your app directly.
|
116
|
-
|
117
|
-
Include this concern in your app's `AuthenticatedController` if your app uses session tokens with [Turbolinks](https://github.com/turbolinks/turbolinks). It adds a `before_action` filter that detects whether a session token is present or not. If a session token is not found, the user is redirected to your app's splash page path (`root_path`) along with `return_to` and `shop` parameters.
|
118
|
-
|
119
|
-
*Example:*
|
120
|
-
|
121
|
-
```rb
|
122
|
-
class AuthenticatedController < ApplicationController
|
123
|
-
include ShopifyApp::EnsureAuthenticatedLinks
|
124
|
-
include ShopifyApp::Authenticated
|
125
|
-
end
|
126
|
-
```
|
246
|
+
When your app needs to make API requests to Shopify, `ShopifyApp`'s `ActiveSupport` controller concerns can help you retrieve the active session token from the repository to make the authenticate API call.
|
127
247
|
|
128
|
-
|
248
|
+
- ⚠️ See [Sessions](./sessions.md) page to understand how sessions work.
|
249
|
+
- ⚠️ See [Controller Concerns](./controller-concerns.md) page to understand when to use which concern.
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Controller Concerns
|
2
|
+
|
3
|
+
The following controller concerns are designed to be public and can be included in your controllers.
|
4
|
+
|
5
|
+
If you are looking to make requests within the context of a logged in **User**, you will include `EnsureHasSession` into your controller to ensure users have a valid session to make requests.
|
6
|
+
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
class YourController < ApplicationController
|
10
|
+
include ShopifyApp::EnsureHasSession
|
11
|
+
end
|
12
|
+
```
|
13
|
+
|
14
|
+
If you don't require a user to be present but make requests scoped by a **Shop**, you can include `EnsureInstalled` in your controller to ensure your app has been installed by the Shop and they have a shop session.
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
class YourController < ApplicationController
|
18
|
+
include ShopifyApp::EnsureInstalled
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
## EnsureHasSession - Online User Sessions
|
23
|
+
Designed for controllers that are designed to handle authenticated actions by ensuring there is a valid session for the request.
|
24
|
+
|
25
|
+
In addition to session management, this concern will also handle localization, CSRF protection, embedded app settings, and billing enforcement.
|
26
|
+
|
27
|
+
## EnsureInstalled - Offline Shop Sessions
|
28
|
+
Designed to handle request scoped by a shop for *embedded apps*. If you are non-embedded app, we recommend using `EnsureHasSession` concern instead of this one.
|
29
|
+
|
30
|
+
Rather than using the JWT to determine the requested shop of the request, the `shop` name param is taken from the query string that Shopify Admin provides.
|
31
|
+
|
32
|
+
If the shop session cannot be found for the provided `shop` in the query string, the request will be redirected to login or the `embedded_redirect_url`.
|
33
|
+
|
34
|
+
## EnsureAuthenticatedLinks
|
35
|
+
Designed to be more of a lightweight session concern specifically for XHR requests. Where `EnsureHasSession` does far more than just session management, this concern will redirect to the splash page of the app if no active session was found.
|
36
|
+
|
37
|
+
## ShopAccessScopesVerification
|
38
|
+
If scopes for the session don't match configuration of scopes defined in `config/initializers/shopify_app.rb` the user will be redirected to login or the `embedded_redirect_url`.
|
39
|
+
|
40
|
+
# Private Concerns used with EnsureHasSession
|
41
|
+
These concerns shouldn't be included directly, but we provide some documentation as to their functionality that are included with `EnsureHasSession`. Concerns defined in `lib/shopify_app/controller_concerns` are designed to be private and are not meant to be included directly into your controllers.
|
42
|
+
|
43
|
+
#### LoginProtection - Session Management
|
44
|
+
This concern will setup and teardown the session around the action. If the session cannot be setup for the requested shop the request will be redirected to login.
|
45
|
+
|
46
|
+
The concern will load sessions depending on your app's configuration:
|
47
|
+
|
48
|
+
**Embedded apps**
|
49
|
+
|
50
|
+
Cookies are not available for embedded apps because it loads in an iframe, so this concern will load the session from the request's `Authorization` header containing a session token, which can be set using [App Bridge](https://shopify.dev/apps/tools/app-bridge).
|
51
|
+
|
52
|
+
Learn more about [using `authenticatedFetch`](https://shopify.dev/apps/auth/oauth/session-tokens/getting-started#step-2-authenticate-your-requests) to create session tokens and authenticate your requests.
|
53
|
+
|
54
|
+
**Non-embedded apps**
|
55
|
+
|
56
|
+
Since cookies are available, the concern will load the session directly from them, so you can make regular `fetch` requests on your front-end.
|
57
|
+
|
58
|
+
#### Localization
|
59
|
+
I18n localization is saved to the session for consistent translations for the session.
|
60
|
+
|
61
|
+
#### CSRFProtection
|
62
|
+
Implements Rails' [protect_from_forgery](https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html#method-i-protect_from_forgery) unless a valid session token is found for the request.
|
63
|
+
|
64
|
+
#### EmbeddedApp
|
65
|
+
If your ShopifyApp configuration has the `embedded_app` config set to true, [P3P header](https://www.w3.org/P3P/) and [content security policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) are handled for you.
|
66
|
+
|
67
|
+
By default, the `EmbeddedApp` concern also sets the layout file to be `app/views/layouts/embedded_app.html.erb`.
|
68
|
+
|
69
|
+
Sometimes one wants to run an embedded app in non-embedded mode. For example:
|
70
|
+
|
71
|
+
- When the remote environment is a CI;
|
72
|
+
- When the remote environment is a preview/PR app;
|
73
|
+
- When the developer wants to run the app in a non-embedded mode for testing.
|
74
|
+
|
75
|
+
To use the same application layout for every application controller, a developer can now overwrite the `#use_embedded_app_layout?` method.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class ApplicationController
|
79
|
+
# Ensures every controller is using the standard app/views/layouts/application.html.erb layout.
|
80
|
+
#
|
81
|
+
# @return [true, false]
|
82
|
+
def use_embedded_app_layout?
|
83
|
+
false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
#### EnsureBilling
|
89
|
+
If billing is enabled for the app, the active payment for the session is queried and enforced if needed. If billing is required the user will be redirected to a page requesting payment.
|
data/docs/shopify_app/engine.md
CHANGED
@@ -27,24 +27,15 @@ The engine may also be mounted at a nested route, for example:
|
|
27
27
|
mount ShopifyApp::Engine, at: '/nested'
|
28
28
|
```
|
29
29
|
|
30
|
-
This will create the Shopify engine routes under the specified subpath. You'll also need to make some updates to your `shopify_app.rb
|
30
|
+
This will create the Shopify engine routes under the specified subpath. You'll also need to make some updates to your `shopify_app.rb`. Update the shopify_app initializer to include a custom `root_url` and `login_callback_url` e.g.:
|
31
31
|
|
32
32
|
```ruby
|
33
33
|
ShopifyApp.configure do |config|
|
34
34
|
config.root_url = '/nested'
|
35
|
+
config.login_callback_url = '/nested/auth/shopify/callback'
|
35
36
|
end
|
36
37
|
```
|
37
38
|
|
38
|
-
then update the omniauth initializer to include a custom `callback_path` e.g.:
|
39
|
-
|
40
|
-
```ruby
|
41
|
-
provider :shopify,
|
42
|
-
ShopifyApp.configuration.api_key,
|
43
|
-
ShopifyApp.configuration.secret,
|
44
|
-
scope: ShopifyApp.configuration.scope,
|
45
|
-
callback_path: '/nested/auth/shopify/callback'
|
46
|
-
```
|
47
|
-
|
48
39
|
You may also need to change your `config/routes.rb` to render a view for `/nested`, since this is what will be rendered in the Shopify Admin of any shops that have installed your app. The engine itself doesn't have a view for this, so you'll need something like this:
|
49
40
|
|
50
41
|
```ruby
|
@@ -68,13 +68,13 @@ Specify whether the app is an embedded app. Apps are embedded by default.
|
|
68
68
|
|
69
69
|
#### `$ rails generate shopify_app:shop_model`
|
70
70
|
|
71
|
-
This generator creates a `Shop` model and a migration to store shop installation records. See [*Shop-based token strategy*](/docs/shopify_app/
|
71
|
+
This generator creates a `Shop` model and a migration to store shop installation records. See [*Shop-based token strategy*](/docs/shopify_app/sessions.md#shop-offline-token-storage) to learn more.
|
72
72
|
|
73
73
|
---
|
74
74
|
|
75
75
|
#### `$ rails generate shopify_app:user_model`
|
76
76
|
|
77
|
-
This generator creates a `User` model and a migration to store user records. See [*User-based token strategy*](/docs/shopify_app/
|
77
|
+
This generator creates a `User` model and a migration to store user records. See [*User-based token strategy*](/docs/shopify_app/sessions.md#user-online-token-storage) to learn more.
|
78
78
|
|
79
79
|
---
|
80
80
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Logging
|
2
|
+
|
3
|
+
## Log Levels
|
4
|
+
|
5
|
+
There are four log levels with `error` being the most severe.
|
6
|
+
|
7
|
+
1. Debug
|
8
|
+
2. Info
|
9
|
+
3. Warn
|
10
|
+
4. Error
|
11
|
+
|
12
|
+
## Configuration
|
13
|
+
|
14
|
+
The logging is controlled by the `log_level` configuration setting.
|
15
|
+
The default log level is `:info`.
|
16
|
+
You can disable all logs by setting this to `:off`.
|
17
|
+
|
18
|
+
## Upgrading
|
19
|
+
|
20
|
+
For a newly-generated app, the `shopify_app` initializer will contain the `log_level` setting.
|
21
|
+
If you are upgrading from a previous version of the `shopify_app` gem then you will need to add this manually, otherwise it will default to `:info`.
|