diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fb8cf4fe..327abeba7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api ## Unreleased - [#1312](https://github.com/Shopify/shopify-api-ruby/pull/1312) Use same leeway for `exp` and `nbf` when parsing JWT -- [#1314](https://github.com/Shopify/shopify-api-ruby/pull/1314) Add new session util method `SessionUtils::session_id_from_shopify_id_token` +- [#1314](https://github.com/Shopify/shopify-api-ruby/pull/1314) + - Add new session util method `SessionUtils::session_id_from_shopify_id_token` + - `SessionUtils::current_session_id` now accepts shopify Id token in the format of `Bearer this_token` or just `this_token` ## 14.2.0 - [#1309](https://github.com/Shopify/shopify-api-ruby/pull/1309) Add `Session#copy_attributes_from` method diff --git a/docs/getting_started.md b/docs/getting_started.md index 5bdafc470..58fc4833f 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -56,14 +56,17 @@ If your app uses client side rendering instead of server side rendering, you wil For *embedded* apps: -If you have an `HTTP_AUTHORIZATION` header, you can pass the auth header into: -- `ShopifyAPI::Utils::SessionUtils.current_session_id(auth_header, nil, true)` for online (user) sessions or -- `ShopifyAPI::Utils::SessionUtils.current_session_id(auth_header, nil, false)` for offline (store) sessions. +If you have an `HTTP_AUTHORIZATION` header or `id_token` from the request URL params , you can pass that as `shopify_id_token` into: +- `ShopifyAPI::Utils::SessionUtils.current_session_id(shopify_id_token, nil, true)` for online (user) sessions or +- `ShopifyAPI::Utils::SessionUtils.current_session_id(shopify_id_token, nil, false)` for offline (store) sessions. -You can also use `id_token` from the request URL params to get the session ID: -- `ShopifyAPI::Utils::SessionUtils::session_id_from_shopify_id_token(id_token: id_token_from_param, online: true)` for online (user) sessions or -- `ShopifyAPI::Utils::SessionUtils::session_id_from_shopify_id_token(id_token: id_token_from_param, online: false)` for offline (store) sessions. +`current_session_id` accepts shopify_id_token in the format of `Bearer this_token` or just `this_token`. +You can also use this method to get session ID: +- `ShopifyAPI::Utils::SessionUtils::session_id_from_shopify_id_token(id_token: id_token, online: true)` for online (user) sessions or +- `ShopifyAPI::Utils::SessionUtils::session_id_from_shopify_id_token(id_token: id_token, online: false)` for offline (store) sessions. + +`session_id_from_shopify_id_token` does **NOT** accept shopify_id_token in the format of `Bearer this_token`, you must pass in `this_token`. #### Start Making Authenticated Shopify Requests diff --git a/lib/shopify_api/utils/session_utils.rb b/lib/shopify_api/utils/session_utils.rb index 271719f16..21eef510f 100644 --- a/lib/shopify_api/utils/session_utils.rb +++ b/lib/shopify_api/utils/session_utils.rb @@ -11,21 +11,16 @@ class << self sig do params( - auth_header: T.nilable(String), + shopify_id_token: T.nilable(String), cookies: T.nilable(T::Hash[String, String]), online: T::Boolean, ).returns(T.nilable(String)) end - def current_session_id(auth_header, cookies, online) + def current_session_id(shopify_id_token, cookies, online) if Context.embedded? - if auth_header - matches = auth_header.match(/^Bearer (.+)$/) - unless matches - ShopifyAPI::Logger.warn("Missing Bearer token in authorization header") - raise Errors::MissingJwtTokenError, "Missing Bearer token in authorization header" - end - - session_id_from_shopify_id_token(id_token: T.must(matches[1]), online: online) + if shopify_id_token + id_token = shopify_id_token.gsub("Bearer ", "") + session_id_from_shopify_id_token(id_token: id_token, online: online) else # falling back to session cookie raise Errors::CookieNotFoundError, "JWT token or Session cookie not found for app" unless @@ -48,7 +43,7 @@ def current_session_id(auth_header, cookies, online) ).returns(String) end def session_id_from_shopify_id_token(id_token:, online:) - raise Errors::MissingJwtTokenError, "Missing Shopify ID Token" unless id_token + raise Errors::MissingJwtTokenError, "Missing Shopify ID Token" if id_token.nil? || id_token.empty? payload = Auth::JwtPayload.new(id_token) shop = payload.shop diff --git a/test/utils/session_utils_test.rb b/test/utils/session_utils_test.rb index 0a3fd3fa9..5cd783061 100644 --- a/test/utils/session_utils_test.rb +++ b/test/utils/session_utils_test.rb @@ -25,20 +25,20 @@ def setup @jwt_token = JWT.encode(@jwt_payload, ShopifyAPI::Context.api_secret_key, "HS256") @auth_header = "Bearer #{@jwt_token}" + @expected_online_session_id = "#{@shop}_#{@user_id}" + @expected_offline_session_id = "offline_#{@shop}" end def test_gets_online_session_id_from_shopify_id_token - expected_session_id = "#{@shop}_#{@user_id}" assert_equal( - expected_session_id, + @expected_online_session_id, ShopifyAPI::Utils::SessionUtils.session_id_from_shopify_id_token(id_token: @jwt_token, online: true), ) end def test_gets_offline_session_id_from_shopify_id_token - expected_session_id = "offline_#{@shop}" assert_equal( - expected_session_id, + @expected_offline_session_id, ShopifyAPI::Utils::SessionUtils.session_id_from_shopify_id_token(id_token: @jwt_token, online: false), ) end @@ -50,11 +50,16 @@ def test_session_id_from_shopify_id_token_raises_invalid_jwt_errors end def test_session_id_from_shopify_id_token_raises_missing_jwt_token_error - error = assert_raises(ShopifyAPI::Errors::MissingJwtTokenError) do - ShopifyAPI::Utils::SessionUtils.session_id_from_shopify_id_token(id_token: nil, online: true) - end + [ + nil, + "", + ].each do |missing_jwt| + error = assert_raises(ShopifyAPI::Errors::MissingJwtTokenError) do + ShopifyAPI::Utils::SessionUtils.session_id_from_shopify_id_token(id_token: missing_jwt, online: true) + end - assert_equal("Missing Shopify ID Token", error.message) + assert_equal("Missing Shopify ID Token", error.message) + end end def test_non_embedded_app_current_session_id_raises_cookie_not_found_error @@ -98,6 +103,19 @@ def test_embedded_app_current_session_id_raises_cookie_not_found_error end end + def test_embedded_app_current_session_id_raises_invalid_jwt_token_error + ShopifyAPI::Context.stubs(:embedded?).returns(true) + [ + "Bearer invalid_token", + "Bearer", + "invalid_token", + ].each do |invalid_token| + error = assert_raises(ShopifyAPI::Errors::InvalidJwtTokenError, " - #{invalid_token}") do + ShopifyAPI::Utils::SessionUtils.current_session_id(invalid_token, nil, true) + end + end + end + def test_embedded_app_current_session_id_raises_missing_jwt_token_error ShopifyAPI::Context.stubs(:embedded?).returns(true) @@ -105,36 +123,51 @@ def test_embedded_app_current_session_id_raises_missing_jwt_token_error ShopifyAPI::Utils::SessionUtils.current_session_id("", nil, true) end - assert_equal("Missing Bearer token in authorization header", error.message) + assert_equal("Missing Shopify ID Token", error.message) end def test_embedded_app_current_session_id_returns_online_id_from_auth_header ShopifyAPI::Context.stubs(:embedded?).returns(true) - expected_session_id = "#{@shop}_#{@user_id}" assert_equal( - expected_session_id, + @expected_online_session_id, ShopifyAPI::Utils::SessionUtils.current_session_id(@auth_header, nil, true), ) end def test_embedded_app_current_session_id_returns_offline_id_from_auth_header ShopifyAPI::Context.stubs(:embedded?).returns(true) - expected_session_id = "offline_#{@shop}" assert_equal( - expected_session_id, + @expected_offline_session_id, ShopifyAPI::Utils::SessionUtils.current_session_id(@auth_header, nil, false), ) end + def test_embedded_app_current_session_id_returns_online_id_from_shopify_id_token + ShopifyAPI::Context.stubs(:embedded?).returns(true) + + assert_equal( + @expected_online_session_id, + ShopifyAPI::Utils::SessionUtils.current_session_id(@jwt_token, nil, true), + ) + end + + def test_embedded_app_current_session_id_returns_offline_id_from_shopify_id_token + ShopifyAPI::Context.stubs(:embedded?).returns(true) + + assert_equal( + @expected_offline_session_id, + ShopifyAPI::Utils::SessionUtils.current_session_id(@jwt_token, nil, false), + ) + end + def test_embedded_app_current_session_id_returns_id_from_auth_header_even_with_cookies ShopifyAPI::Context.stubs(:embedded?).returns(true) cookies = { ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME => "cookie_value" } - expected_session_id = "#{@shop}_#{@user_id}" assert_equal( - expected_session_id, + @expected_online_session_id, ShopifyAPI::Utils::SessionUtils.current_session_id(@auth_header, cookies, true), ) end