Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add token exchange concern #1817

Merged
merged 8 commits into from
Mar 27, 2024
Merged

Add token exchange concern #1817

merged 8 commits into from
Mar 27, 2024

Conversation

zzooeeyy
Copy link
Contributor

@zzooeeyy zzooeeyy commented Mar 25, 2024

What this PR does

Tophat:
馃摴 https://videobin.shopify.io/v/J4j8l2

Happy path for token exchange

  • Add token exchange concern to retrieve current session. This replaces "LoginProtection" logic when "use_new_embedded_auth_strategy" is turned on.
  • "EnsureHasSession" concern can be used with token exchange.

Note on what else needs to be implemented in other tasks:

Impact on existing apps:

  • None - All the code is gated behind the configuration flag: config.wip_new_embedded_auth_strategy

Checklist

Before submitting the PR, please consider if any of the following are needed:

  • [ will update when the feature is complete ] Update CHANGELOG.md if the changes would impact users
  • [ will update when the feature is complete ] Update README.md, if appropriate.
  • [ will update when the feature is complete ] Update any relevant pages in /docs, if necessary

@zzooeeyy zzooeeyy requested a review from a team as a code owner March 25, 2024 13:36
Copy link
Contributor

@paulomarg paulomarg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had some general questions, not necessarily things we need / want to apply.


if ShopifyApp.configuration.use_new_embedded_auth_strategy?
include ShopifyApp::RetrieveSessionFromTokenExchange
around_action :activate_shopify_session
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering - is this repeated in both paths because we need the after_action to kick in after this in the else branch?

Not that I mind the repetition, just curious.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I repeated it so that it's explicit in which action is called, if we leave this as a shared/common action outside of this condition, then we're implicitly forcing the method name to be the same between the 2 concerns. Which would also work, but I feel like less implicit dependency the better? 馃

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree - less magic, more good.

@@ -6,14 +6,20 @@ module EnsureHasSession

included do
include ShopifyApp::Localization
include ShopifyApp::LoginProtection

if ShopifyApp.configuration.use_new_embedded_auth_strategy?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe these conditions should only apply if the app is embedded, right? We'd still want to use LoginProtection for non-embedded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's right, use_new_embedded_auth_strategy is only true if app is embedded AND has wip_new_embedded_auth_strategy enabled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh perfect, I missed that part :)

# frozen_string_literal: true

module ShopifyApp
module RetrieveSessionFromTokenExchange
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we call this just TokenExchange? Would that be confusing?

module RetrieveSessionFromTokenExchange
extend ActiveSupport::Concern

def activate_shopify_session
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of these methods seem to be the same for both cases, right? Should we create one or more helper concerns for them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes and no --?

Some of the functions might change as we add more error handling (e.g. current_shopify_session might raise and rescue error so we can redirect to bounce page to get session token)

Some methods are only extracted to call 1 single method from the ShopifyAPI, which I think we should keep as is to reduce even more magic from more concerns..

I think the only method that makes sense is online_token_configured? this doesn't seem like it belongs in the concerns but in the configuration itself.. I've extracted this one so we don't check the business logic from the concerns - bcc2e1e

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair - I think if we expect the code to change then it won't make sense to extract them. We can live with some minor duplications here, as long as we're aware we'll need to consider both cases when making changes.

Comment on lines 48 to 50
# TODO: Right now JWT Middleware only updates env['jwt.shopify_domain'] from request headers tokens,
# which won't work for new installs.
# we need to update the middleware to also update the env['jwt.shopify_domain'] from the query params
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should happen as part of this PR, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm not necessarily. We won't always have session token in the query params (beta flag can still be turned off), so our feature shouldn't rely on having session tokens from the params. Thus I think that could be handled as a part of another ticket to add support for using id_token from param.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But won't that mean apps won't be able to do the first install flow with the setting turned on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yess, so the EnsureInstalled concern doesn't work yet. We'll have to handle reloading from AppBridge with session token in request headers (part of this ticket: https://github.com/Shopify/develop-app-access/issues/145) to make the first install flow work. That's why EnsureInstalled doesn't work with token exchange yet

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense

# respond_to_invalid_session_token
return
rescue ShopifyAPI::Errors::HttpResponseError => error
ShopifyApp::Logger.info(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log these error messages as info?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I'll change them to :error!

Comment on lines 48 to 50
# TODO: Right now JWT Middleware only updates env['jwt.shopify_domain'] from request headers tokens,
# which won't work for new installs.
# we need to update the middleware to also update the env['jwt.shopify_domain'] from the query params
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense

module RetrieveSessionFromTokenExchange
extend ActiveSupport::Concern

def activate_shopify_session
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair - I think if we expect the code to change then it won't make sense to extract them. We can live with some minor duplications here, as long as we're aware we'll need to consider both cases when making changes.

@paulomarg
Copy link
Contributor

I think this makes sense, assuming the TODOs will be resolved before we release :)

@ragalie
Copy link
Contributor

ragalie commented Mar 26, 2024

This looks good to me! One question: should we have unit tests around the TokenExchange class?

@paulomarg
Copy link
Contributor

Great callout, we should definitely add some tests for the new paths.

@zzooeeyy
Copy link
Contributor Author

This looks good to me! One question: should we have unit tests around the TokenExchange class?

Yes we totally should, I was going to tackle that after we handle all the special invalid token cases. That way we can ensure we have all the test cases and ways to recover in 1 go... I've added that another task so we don't forget about it:

@ragalie
Copy link
Contributor

ragalie commented Mar 26, 2024

I was going to tackle that after we handle all the special invalid token cases

I'd encourage you to test the happy path in the happy path PR, and the sad path in the sad path PR! Doing all the tests at once can make it harder to discover what caused errors, whereas doing them more iteratively makes it more obvious when a change is breaking an assumption.

@zzooeeyy
Copy link
Contributor Author

Thanks @ragalie & @paulomarg -- I've updated this PR with tests!


test "Exchanges offline token when session doesn't exist" do
with_application_test_routes do
ShopifyAPI::Utils::SessionUtils.expects(:current_session_id).twice.with(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd maybe go with a stubs here instead of an expectation. It doesn't seem essential to the test outcome that this gets called twice; that's an implementation detail. The thing we care about is that the exchange_token happens once, which you're expecting below. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I don't disagree with that!

Copy link
Contributor

@ragalie ragalie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a comment on test tactics but otherwise LGTM!

@zzooeeyy zzooeeyy merged commit 932f5f3 into main Mar 27, 2024
6 of 7 checks passed
@zzooeeyy zzooeeyy deleted the token-exchange-happy-path branch March 27, 2024 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants