Skip to content

Commit

Permalink
Merge pull request #1861 from Shopify/remove-jwt-middleware
Browse files Browse the repository at this point in the history
Remove JWT middleware
  • Loading branch information
danielpgross authored Jun 17, 2024
2 parents dd13b33 + a53b692 commit 59c5f1b
Show file tree
Hide file tree
Showing 15 changed files with 93 additions and 498 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Unreleased
----------
- ⚠️ [Breaking] Removes `ShopifyApp::JWTMiddleware`. Any existing app code relying on decoded JWT contents set from `request.env` should instead include the `WithShopifyIdToken` concern and call its respective methods. [#1861](https://github.com/Shopify/shopify_app/pull/1861)
- Handle scenario when invalid URI is passed to `sanitize_shop_domain` [#1852](https://github.com/Shopify/shopify_app/pull/1852)

22.2.1 (May 6,2024)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ PATH
shopify_app (22.2.1)
activeresource
addressable (~> 2.7)
jwt (>= 2.2.3)
rails (> 5.2.1)
redirect_safely (~> 1.0)
shopify_api (>= 14.3.0, < 15.0)
Expand Down Expand Up @@ -266,6 +265,7 @@ PLATFORMS

DEPENDENCIES
byebug
jwt (>= 2.2.3)
minitest
mocha
pry
Expand Down
18 changes: 16 additions & 2 deletions docs/Upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ We also recommend the use of a staging site which matches your production enviro
If you do run into issues, we recommend looking at our [debugging tips.](https://github.com/Shopify/shopify_app/blob/main/docs/Troubleshooting.md#debugging-tips)

## Unreleased

#### (v23.0.0) - Deprecated methods in CallbackController
The following methods from `ShopifyApp::CallbackController` have been deprecated in `v23.0.0`
- `perform_after_authenticate_job`
Expand All @@ -53,15 +54,28 @@ If you have overwritten these methods in your callback controller to modify the
update your app to use configurable option `config.custom_post_authenticate_tasks` instead. See [post authenticate tasks](/docs/shopify_app/authentication.md#post-authenticate-tasks)
for more information.

#### (v23.0.0) - Removed `ShopifyApp::JWTMiddleware`
The `ShopifyApp::JWTMiddleware` middleware has been removed in `v23.0.0`. This middleware was used to populate the following environment variables from the JWT session token:
- `request.env["jwt.token"]`
- `request.env["jwt.shopify_domain"]`
- `request.env["jwt.shopify_user_id"]`
- `request.env["jwt.expire_at"]`

If you are using any of these variables in your app, you'll need to replace them. You can instead include the `ShopifyApp::WithShopifyIdToken` concern, which does the same JWT parsing as the middleware, and exposes the same values in the following helper methods:
- `shopify_id_token`
- `jwt_shopify_domain`
- `jwt_shopify_user_id`
- `jwt_expire_at`

#### (v23.0.0) - Deprecated "ShopifyApp::JWT" class
The `ShopifyApp::JWT` class has been deprecated in `v23.0.0`. Use [ShopifyAPI::Auth::JwtPayload](https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/auth/jwt_payload.rb)
class from the `shopify_api` gem instead. A search and replace should be enough for this migration.
- `ShopifyAPI::Auth::JwtPayload` is a superset of the `ShopifyApp::JWT` class, and contains methods that were available in `ShopifyApp::JWT`.
- `ShopifyAPI::Auth::JwtPayload` is a superset of the `ShopifyApp::JWT` class, and contains methods that were available in `ShopifyApp::JWT`.
- `ShopifyAPI::Auth::JwtPayload` raises `ShopifyAPI::Errors::InvalidJwtTokenError` if the token is invalid.

## Upgrading to `v22.2.0`
#### Added new feature for zero redirect embedded app authorization flow - Token Exchange
A new embedded app authorization strategy has been introduced in `v22.2.0` that eliminates the redirects that were previously necessary for OAuth.
A new embedded app authorization strategy has been introduced in `v22.2.0` that eliminates the redirects that were previously necessary for OAuth.
It can replace the existing installation and authorization code grant flow.
See [new embedded app authorization strategy](/README.md#new-embedded-app-authorization-strategy-token-exchange) for more information.

Expand Down
4 changes: 0 additions & 4 deletions lib/shopify_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,10 @@ def self.use_webpacker?
# managers
require "shopify_app/managers/webhooks_manager"

# middleware
require "shopify_app/middleware/jwt_middleware"

# session
require "shopify_app/session/in_memory_session_store"
require "shopify_app/session/in_memory_shop_session_store"
require "shopify_app/session/in_memory_user_session_store"
require "shopify_app/session/jwt"
require "shopify_app/session/null_user_session_store"
require "shopify_app/session/session_repository"
require "shopify_app/session/session_storage"
Expand Down
3 changes: 2 additions & 1 deletion lib/shopify_app/controller_concerns/csrf_protection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ module ShopifyApp
module CsrfProtection
extend ActiveSupport::Concern
included do
include ShopifyApp::WithShopifyIdToken
protect_from_forgery with: :exception, unless: :valid_session_token?
end

private

def valid_session_token?
request.env["jwt.shopify_domain"]
jwt_payload.present?
end
end
end
25 changes: 16 additions & 9 deletions lib/shopify_app/controller_concerns/with_shopify_id_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,38 @@ module WithShopifyIdToken
extend ActiveSupport::Concern

def shopify_id_token
@shopify_id_token ||= id_token_from_request_env || id_token_from_authorization_header || id_token_from_url_param
return @shopify_id_token if defined?(@shopify_id_token)

@shopify_id_token = id_token_from_authorization_header || id_token_from_url_param
end

def jwt_payload
return @jwt_payload if defined?(@jwt_payload)

@jwt_payload = shopify_id_token.present? ? ShopifyAPI::Auth::JwtPayload.new(shopify_id_token) : nil
end

def jwt_shopify_domain
request.env["jwt.shopify_domain"]
return @jwt_shopify_domain if defined?(@jwt_shopify_domain)

@jwt_shopify_domain = if jwt_payload.present?
ShopifyApp::Utils.sanitize_shop_domain(jwt_payload.shopify_domain)
end
end

def jwt_shopify_user_id
request.env["jwt.shopify_user_id"]
jwt_payload&.shopify_user_id
end

def jwt_expire_at
expire_at = request.env["jwt.expire_at"]
expire_at = jwt_payload&.expire_at
return unless expire_at

expire_at - 5.seconds # 5s gap to start fetching new token in advance
end

private

def id_token_from_request_env
# This is set from ShopifyApp::JWTMiddleware
request.env["jwt.token"]
end

def id_token_from_authorization_header
request.headers["HTTP_AUTHORIZATION"]&.match(/^Bearer (.+)$/)&.[](1)
end
Expand Down
4 changes: 0 additions & 4 deletions lib/shopify_app/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ class Engine < Rails::Engine
]
end

initializer "shopify_app.middleware" do |app|
app.config.middleware.insert_after(::Rack::Runtime, ShopifyApp::JWTMiddleware)
end

initializer "shopify_app.redact_job_params" do
ActiveSupport.on_load(:active_job) do
if ActiveJob::Base.respond_to?(:log_arguments?)
Expand Down
48 changes: 0 additions & 48 deletions lib/shopify_app/middleware/jwt_middleware.rb

This file was deleted.

73 changes: 0 additions & 73 deletions lib/shopify_app/session/jwt.rb

This file was deleted.

2 changes: 1 addition & 1 deletion shopify_app.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ Gem::Specification.new do |s|

s.add_runtime_dependency("activeresource") # TODO: Remove this once all active resource dependencies are removed
s.add_runtime_dependency("addressable", "~> 2.7")
s.add_runtime_dependency("jwt", ">= 2.2.3")
s.add_runtime_dependency("rails", "> 5.2.1")
s.add_runtime_dependency("redirect_safely", "~> 1.0")
s.add_runtime_dependency("shopify_api", ">= 14.3.0", "< 15.0")
s.add_runtime_dependency("sprockets-rails", ">= 2.0.0")

s.add_development_dependency("byebug")
s.add_development_dependency("jwt", ">= 2.2.3")
s.add_development_dependency("minitest")
s.add_development_dependency("mocha")
s.add_development_dependency("pry")
Expand Down
14 changes: 13 additions & 1 deletion test/shopify_app/controller_concerns/csrf_protection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,19 @@ class CsrfProtectionTest < ActionDispatch::IntegrationTest
end

test "it does not raise if a valid session token was provided" do
post "/csrf_protection_test", env: { "jwt.shopify_domain": "exampleshop.myshopify.com" }
jwt_payload = {
iss: "iss",
dest: "https://test-shop.myshopify.com",
aud: ShopifyAPI::Context.api_key,
sub: "1",
exp: (Time.now + 10).to_i,
nbf: 1234,
iat: 1234,
jti: "4321",
sid: "abc123",
}
jwt_token = JWT.encode(jwt_payload, ShopifyAPI::Context.api_secret_key, "HS256")
post "/csrf_protection_test", headers: { HTTP_AUTHORIZATION: "Bearer #{jwt_token}" }

assert_response :ok
end
Expand Down
18 changes: 15 additions & 3 deletions test/shopify_app/controller_concerns/login_protection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -567,9 +567,21 @@ class LoginProtectionControllerTest < ActionController::TestCase

test "#jwt_expire_at returns jwt expire at with 5s gap" do
expire_at = 2.hours.from_now.to_i

with_application_test_routes do
request.env["jwt.expire_at"] = expire_at
jwt_payload = {
iss: "iss",
dest: "https://test-shop.myshopify.com",
aud: ShopifyAPI::Context.api_key,
sub: "1",
exp: expire_at,
nbf: 1234,
iat: 1234,
jti: "4321",
sid: "abc123",
}
jwt_token = JWT.encode(jwt_payload, ShopifyAPI::Context.api_secret_key, "HS256")

with_application_test_routes do
request.headers["HTTP_AUTHORIZATION"] = "Bearer #{jwt_token}"
get :index

assert_equal expire_at - 5.seconds, @controller.jwt_expire_at
Expand Down
Loading

0 comments on commit 59c5f1b

Please sign in to comment.