Closed gnikyt closed 3 years ago
Open to input here as I'm stuck. The token stuff is fine for SPAs but if I am to remove dependence on cookies, then for non-SPAs, this would be an issue as the token won't be in the next request.
Currently the only thought I had was to have the token appended on URLs and added to forms with helpers. So all links and forms used will have the token. Something like:
{{ token_url('/orders/search') }}
{{ token_url(route('home')) }}
Open to suggestions if others think there's a better way.
Why not store the token in a database like laravel sanctum does?
Database is useless. We store the offline token there now, but that doesn't tell Laravel who it is. We can't access the correct information without knowing who.. and if going cookie-less route, this information is in the token.
For SPAs, this is no issue since you just issue the token in the headers with the AJAX requests.
For non-SPAs, this is an issue. Currently my only solution, which seems to be used by other Laravel projects utlizing tokens, is to check for it in the query param.
When I get some time this weekend, I'll be diving in to try all this out.
Why not just store the token in some form of client-side storage, and then have a client-side helper that injects it as an Authorization: Bearer
header with each request? This would basically be a roll-your-own-cookie without the restrictions imposed on actual third-party cookies.
Related to this - is there a good step-by-step guide or docs around how to use this package with JWT for a Laravel SPA?
What about use turbolink to make non-SPAs acting like SPAs? so it can use ajax requests which can issue token in the request header, is this a possible solution? @alexweissman I think for non-SPAs, the requests will always be non-ajax, non-ajax requests can't change the request header, right?
What about use turbolink to make non-SPAs acting like SPAs? so it can use ajax requests which can issue token in the request header, is this a possible solution? @alexweissman I think for non-SPAs, the requests will always be non-ajax, non-ajax requests can't change the request header, right?
In our Shopify apps we use InertiaJS to handle frontend with backend routing. I guess it might be a better solution to turbolinks.
I would rather avoid turbolinks. I don't see an issue with it as part of a post or query param.. it appears many other projects do this. With a blade helper function to automatically append it to the url or form, then it should be fine.
On Fri., Mar. 26, 2021, 08:15 diemah77, @.***> wrote:
What about use turbolink to make non-SPAs acting like SPAs? so it can use ajax requests which can issue token in the request header, is this a possible solution? @alexweissman https://github.com/alexweissman I think for non-SPAs, the requests will always be non-ajax, non-ajax requests can't change the request header, right?
In our Shopify apps we use InertiaJS to handle frontend with backend routing. I guess it might be a better solution to turbolinks.
— You are receiving this because you were assigned. Reply to this email directly, view it on GitHub https://github.com/osiset/laravel-shopify/issues/744#issuecomment-808113043, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASO4OTFEIP2X4YNNLQLOTTTFRQTHANCNFSM4ZLA3ICA .
Isn't it a rather significant security issue to expose access tokens in query string parameters? See https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url
Seems like authentication in non-SPA contexts has been a major blind spot in the rush to banish third-party cookies from browsers.
Alex is correct, JWTs should not be passed in query params, they should only ever be in the Authorization
header.
I'm open to suggestions on how to handle for non-SPAs then. I've been given a good hint that the end goal is to eventually force apps to completely remove dependence on cookies, good to plan for this now than later.
For non-SPAs, this is an issue. Currently my only solution, which seems to be used by other Laravel projects utlizing tokens, is to check for it in the query param.
You should fetch a fresh token from the frontend app instance before posting, and Shopify will always give you a fresh, valid token so the backend never needs to know who the token is for, you just decode a valid token from Shopify and the URL inside will be valid.
If you mean wanting to know the token from a get request, there isn't really much more of an option than adding it to the query params.
Ideally non-SPA would add something like @hotwired/turbo
(not turbolinks) and add a few lines of code to make it work with fetch
, but if Turbo is not an option then query params are pretty much your only solution.
@osiset Are you planning to consume tokens issued by Shopify App Bridge or use a custom implementation like Laravel Sanctum?
@diemah77 the ones issued by Shopify, which are already supported on the auth.token
middleware.
Related to this - is there a good step-by-step guide or docs around how to use this package with JWT for a Laravel SPA?
Just add the JWT you get to an Authorization header and send it to a auth.token
route and it will work.
I've decided to go ahead and kill off ITP and cookie dependencies. Will be pushing tokens as the solution. For non-SPAs, since theres no much choice on plain GET/POST, then it'll have to do as a trade off. I'll leave it up to the developer to modify if they are not happy with it as a param.
I'll begin this work tomorrow on my day off.
I've started the work this morning on prototyping this.
So initially if no token, then the user will be taken to a route (still behind the auth.shopify middleware) which loads app bridge and gets a token.
Then, redirected to home route with the token and verified (auth.shopify + auth.token) to the home route where you can then use the token with your requests for SPAs or use a Blade helper for appending to URLs.
So far that's what I have planned..
So auth.shopify
will then basically varify hmac?
It will handle everything it currently does yes.
Hi @osiset thanks for this awesome package.
Any estimate do you have in mind for this as I was planning to go live but Shopify now doesn't accept apps without session based auth? Also, any part of this I can help you with let me know and I will issue a PR.
No I don't. I am picking at it slowly.
@osiset
So initially if no token, then the user will be taken to a route (still behind the auth.shopify middleware) which loads app bridge and gets a token
The user should get sent to a route that has no middleware at all, just a basic page that loads app bridge and grabs a token from Shopify
@darrynten That's what I have done :) so far so good.
I've been busy, but managed to grab a few hours today to work on this.
I've started and pushed some code to create a more simplified and combined middleware to verify hmac, handle tokens, and other loose ends.
I have a route for unauthenticated to init app bridge and get a token.
I have a base there just trying to test some physical scenarios out.
Will update when I have more.
Things are going well, I managed to grab a few more hours.
I've removed all ITP code/routes, old session helpers, and cookie code.
I've created a more combined and simplified middleware as mentioned before to handle the bulk of the checks.
Results
Next Steps:
Thanks everyone for patience.
@osiset Let us know when this feature will be available to use.
New update
I was able to complete the work.
In the attached video I am doing a non-SPA.
https://user-images.githubusercontent.com/2420282/115582271-5be47580-a2a3-11eb-849f-6c79d03340a5.mp4
Next Steps
Rewrite a lot of the unit tests and begin work there.
First of all, thank you for your great work Tyler. (I'm using Basic Shopify API)
Can you push this new change so we can check and maybe try to help?
You can pull from the feature/cookieless branch, but I don't have upgrade instructions yet completed.
On Sun, Apr 25, 2021 at 17:28, Scorpion @.***> wrote:
First of all, thank you for your great work Tyler. (I'm using Basic Shopify API)
Can you push this new change so we can check and maybe try to help?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/osiset/laravel-shopify/issues/744#issuecomment-826425548, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASO4OVYUXO2GXJOQ5AMIGTTKSXSRANCNFSM4ZLA3ICA.
It's okay, no need for instruction. Developer is required to read the code. Unless this app will be bought/managed by Shopify and they can protect us of any vulnerabilities.
By the way I suggest adding some tipping link to your project. I believe many people are using your app and earn good money.
So I believe they can tip you a small amount of that earnings.
Thank you.
@osiset Do you think calling invocable classes will be more beautiful using other technic instead of call_user_func()?
Instead of
$result = call_user_func( $this->installShopAction, ShopDomain::fromNative($request->get('shop')), $request->query('code') );
Maybe use
$result = (new installShop())->__invoke(ShopDomain::fromNative($request->get('shop')),, $request->query('code'));
OR
$installShopAction = new installShop();
$result = $installShopAction(ShopDomain::fromNative($request->get('shop')),, $request->query('code'));
If this package is for laravel 7 and UP we can use even this method
($this->installShopAction)(ShopDomain::fromNative($request->get('shop')),, $request->query('code'));
I think in this case we can save some little speed(ms) by declaring/calling the function native instead of calling user func to search for it.
Hi! Am I correct in assuming that the billing is not working at the moment? It uses the ShopSession class, which no longer exists
Yes
On Thu., Apr. 29, 2021, 15:05 Vitaly, @.***> wrote:
Hi! Am I correct in assuming that the billing is not working at the moment? It uses the ShopSession class, which no longer exists
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/osiset/laravel-shopify/issues/744#issuecomment-829455822, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASO4OVCSXSJIYCOTNRLN2LTLGKGXANCNFSM4ZLA3ICA .
sent a pull-request with billing
My code probably leaves a lot to be desired, Immediately I apologize. But I think it will be useful for those who already need an application with tokens, but lack of billing stopped them Or for those who want to or already use Turbolinks
Excellent!! Thats super helpful. I am taking the afternoon off work to continue working on the unit tests.
Turbolinks support is great.
I'll merge this before I continue.
Probably a bit pre-mature to use this yet in production however until I get all the tests in working order... there may be something overlooked (hopefully not though).
On Wed, May 5, 2021 at 06:32, Vitaly @.***> wrote:
sent a pull-request with billing
My code probably leaves a lot to be desired, Immediately I apologize. But I think it will be useful for those who already need an application with tokens, but lack of billing stopped them Or for those who want to or already use Turbolinks
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/osiset/laravel-shopify/issues/744#issuecomment-832689678, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASO4OS2DU2DGC7KPEH56CDTMFCFNANCNFSM4ZLA3ICA.
I'm working on integrating this into my app today. I'll contribute anything I can, if needed.
I was going to submit my app for review on Monday and then I found out about the token auth requirement, so I may end up submitting my app with the unreleased code. Trial by fire 🔥 😃
I just submitted three PRs:
turbo_enabled
config settingcontains()
to support route prefixesauthenticate.token
instead of authenticate.oauth
VerifyShopify
middleware won't skip authentication or billing routes.The other 2 are just convenience and cleanup changes.
Or for those who want to or already use Turbolinks
@Enmaboya in the turbolinks:request-start
event listener, why do you have it making a request and not just setting the authorization header? Will this cause the original Turbolinks request to also run, leading to 2 requests? Will it cause POST
requests to run as GET
, since you are calling xhr.open('GET', ...)
?
From what I can gather from the Shopify docs here and here and the Turbolinks docs here, you should get the token (async) on page load, store it, and only set the request header in the turbolinks:request-start
event listener (and allow the Turbolinks request to run). The Shopify docs also start an interval/timer to retrieve the token every 2 seconds, which I'm assuming is to ensure that the token doesn't expire.
Sorry for the barrage of questions 😬
Thanks!
@osiset Does the authenticate.token router is failing in loop because of verify.shopify middleware?
@davodavodavo3 are you using the default package routes with no prefix or have you customized your routes? If you have set a route prefix, you are going to get stuck in a loop. I've submitted a fix (PR #775) but it hasn't been merged yet.
Yeah, I used a prefix to separate the shopify app from laravel jetstream.
Great I see. By the way, what do you think about separating stores tables from users?
That approach is limiting the existing laravel projects
@davodavodavo3 it's hacky, but if you change this line:
To this:
if (Str::contains($request->getRequestUri(), ['/authenticate', '/billing'])) {
You will no longer get stuck in a redirect loop. You'll be changing it in your vendor
directory, which of course will be overwritten when you update, but it will at least get you moving forward again.
@davodavodavo3 I'm only running a single app with my codebase so I haven't run into any issues with users being the stores. I can certainly see the downsides to doing it that way, but as of right now I don't have any reason to move in that direction.
This really makes this a pain to deal with 🤦🏼♂️
Status Update
Thanks everyone for the help with the various PRs.
I had a few hours today to tackle the unit tests.
As of now, the tests are passing locally and externally on Actions with no failures, took a lot to get to this point finally.
Next Steps
Thanks!
Thanks @osiset! I'm glad that I can contribute to your already-amazing package. I'll keep doing all that I can 👍🏻
This really makes this a pain to deal with 🤦🏼♂️
Yes, not an issue for SPAs as you can get a new token easily, but with non-SPAs its a killer. I am looking at adding a JS function for non-SPAs to fetch and update the token URL upon click/submit. I will be testing this tomorrow more than likely.
I am looking at adding a JS function for non-SPAs to fetch and update the token URL upon click/submit
I am implementing that into my app literally as we speak, in fact. It's the only way to make this remotely possible 🤦🏼♂️
Once I get the initial version of my app submitted and approved, I'm going to take a few days and convert everything to use Inertia.js. I hate having to do hacky things just to make something work like normal.
@osiset Are you able to submit POST forms? I get a 419 error no matter what I do for some reason. Regular forms that have always been there and that worked before switching to this branch. My first instinct was a collision with the CSRF token but the field names are different (_token
vs token
). Removing the Shopify session token field still results in a 419.
I'm digging into it right now, but wanted to check with you in case there's something obvious I missed 🤔
Dev Branch: [feature/cookieless]
Introduction
Shopify is now requiring apps to work with session tokens (JWT).
This package has changed several times due to requirement changes with either Shopify or browsers.
At this point, a restructure will be worked on as a priority for authentication flow and validation.
Off the top of my head as some planning, the following will be done and worked on:
Removal of cookies & JWT first-class
Still toying with this in my head, however, while the recent releases helps with cookie issues by handling ITP, its a messy solution and also breaks a good userflow when they see the warnings pop up to enable cookies.
Currently, I see abolishing all cookie and ITP related code and switch to JWT only.
This would involve the removal of ITP views, ITP middleware, cookie class, a good chunk of AuthShopify middleware, and more.
In place, we can promote AuthToken middleware to the forefront, and modify it to utilize Laravel's request context so place in the decoded JWT data into the request lifecycle.
This will allow for the JWT data to be available within the code and views, allowing us to then lookup the user based on the JWT data by request, instead of cookies.
A lot of work will be required to complete this, please be patient. If someone sees an issue with promoting JWT to the forefront, please let me know.