Closed gnikyt closed 3 years ago
Are you being redirected through the /authenticate/token
route? Click "Hello" and watch your browser's network requests. I bet that you are, so you're doing this:
@osiset Maybe we should dispatch a job at the end of the current request that destroys the current session? Clean up after itself, basically.
We had to fork Inertia in order to implement the default header config where the session token was placed globally, as it is not supported natively as of now.
Before the custom Inertia fork, we tried to send the token through an axios interceptor. However, this didn't work out. I guess Inertia just ignores any custom axios headers.
I'll share what we have done to set Inertia global header config as well as the JS bootstrap file by tomorrow.
Hi @diemah77 - just wondering if you got the inertia setup working well with the Polaris/App Bridge AppProvider ( proper handling of links/redirects etc ) ?
@squatto @davodavodavo3 This is "normal".
Session
Default provider for sessions is file. The default lifetime is 120 minutes.
Changing SESSION_LIFETIME
to 1
(1 minute). You can visually see Laravel clear out the sessions (given the session lottery runs). So they do and will eventually clear out depending on your session settings.
Using "database" as a provider, we can see more details, this is an initial request:
We can see the first request to the app makes a session without a user, second session is the result of hitting the token page, and lastly the actual session once logged in.
Clearing
Clearing the "file" session is not really possible to look up by user from what I see. Clearing with database or Redis is possible by looking up based on the user_id.
However, I don't think its worth it. It will eventually clear. We can recommend to change the lifetime to a minute and things will fix itself.
@squatto Added your directive to the package.
Updated the JS to look for anything with ".session-token", and if its an input, it will update the value
, if not, it adds data-value
.
Updated the JS to also support Axios/jQuery/Turbolinks depending on if its exists or not.
Does anyone still get 500 error on the billing routes?
The auth()->user() function always returns null in the BillingController and Billable middleware.
Does anyone still get 500 error on the billing routes?
The auth()->user() function always returns null in the BillingController and Billable middleware.
do you use safari?
No, I just use Chrome.
Hello, I'm trying to set up a feature/cookieless package.
I've used this middleware for the auth. Route::get('/', function () { return view('welcome'); })->middleware(['auth.token'])->name('home');
But I'm getting this error. https://prnt.sc/132t793
Anybody, please give me a guideline
Thank you!
No one should be using the branch for production, there will be no support for it. It is still in development and testing. If you see an issue, you're welcome to debug it and submit PRs if you see the problem :)
Hello, I'm trying to set up a feature/cookieless package.
I've used this middleware for the auth. Route::get('/', function () { return view('welcome'); })->middleware(['auth.token'])->name('home');
But I'm getting this error. https://prnt.sc/132t793
Anybody, please give me a guideline
Thank you!
@mohinht
middleware auth.token is removed in feature/cookieless branch. You should use the verify.shopify one instead.
@osiset
No one should be using the branch for production, there will be no support for it. It is still in development and testing. If you see an issue, you're welcome to debug it and submit PRs if you see the problem :)
Thank you very much for the clearing. :)
Hi, @osiset is it done or still in testing mode? because my app has been rejected due to uninstall issue as installing after uninstalling not working and @mohinht told me almost 1 month ago that you're working on JWT thing which will solve this issue also.
Not ready no.
On Tue., May 25, 2021, 16:04 Waqas Hafeez, @.***> wrote:
Hi, @osiset https://github.com/osiset is it done or still in testing mode? because my app has been rejected due to uninstall issue as installing after uninstalling not working and @mohinht https://github.com/mohinht told me almost 1 month ago that you're working on JWT thing which will solve this issue also.
— 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-848120205, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASO4OTMGPAT6KBFXVU35QTTPPUVBANCNFSM4ZLA3ICA .
Thanks for updating.
On Tue, 25 May 2021, 11:36 pm Tyler King, @.***> wrote:
Not ready no.
On Tue., May 25, 2021, 16:04 Waqas Hafeez, @.***> wrote:
Hi, @osiset https://github.com/osiset is it done or still in testing mode? because my app has been rejected due to uninstall issue as installing after uninstalling not working and @mohinht https://github.com/mohinht told me almost 1 month ago that you're working on JWT thing which will solve this issue also.
— 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-848120205 , or unsubscribe < https://github.com/notifications/unsubscribe-auth/AASO4OTMGPAT6KBFXVU35QTTPPUVBANCNFSM4ZLA3ICA
.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/osiset/laravel-shopify/issues/744#issuecomment-848122996, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD7E3YAOCS7WRMYGSRW64ETTPPUZRANCNFSM4ZLA3ICA .
@vicky92727 The uninstall/reinstall issue is probably not related to the cookie-less/auth flow refactor. I am using the feature/cookieless
branch and I don't have any problems with an uninstall/reinstall. Have you implemented the uninstall job? The instructions to do so are here.
I've followed each and every steps and I can see uninstall webhook created on the Shopify side. But whenever I'm uninstalling the app the webhook is not triggering because user/shop is not being soft deleted from users table.
On Wed, 26 May 2021, 9:30 pm Scott Carpenter, @.***> wrote:
@vicky92727 https://github.com/vicky92727 The uninstall/reinstall issue is probably not related to the cookie-less/auth flow refactor. I am using the feature/cookieless branch and I don't have any problems with an uninstall/reinstall. Have you implemented the uninstall job? The instructions to do so are here https://github.com/osiset/laravel-shopify/wiki/Installation#uninstalled-job-recommended .
— 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-848919924, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD7E3YBU3IZZQHOVKY6UKRLTPUO3ZANCNFSM4ZLA3ICA .
I wonder what's causing it to not trigger the webhook. Is the webhook endpoint not being hit by Shopify at all? Do you see it in your logs?
You can test that your webhook endpoint is set up correctly and is accessible by replacing your-hostname-here.com
with your app's hostname (make sure to replace both of them) and running this on your terminal:
curl --request POST 'https://your-hostname-here.com/webhook/app-uninstalled' \
--header 'X-Shopify-Webhook-Id:4aeb3c80-b496-413b-875c-442dfeb4e086' \
--header 'Content-Length:2' \
--header 'Accept-Encoding:gzip;q=1.0,deflate;q=0.6,identity;q=0.3' \
--header 'Connection:close' \
--header 'Accept:*/*' \
--header 'User-Agent:Ruby' \
--header 'X-Shopify-Topic:app/uninstalled' \
--header 'Host:your-hostname-here.com' \
--header 'X-Shopify-Api-Version:2021-01' \
--header 'X-Shopify-Hmac-Sha256:NCiusmb2pfQ71BMIp2L5QDL7Eq+Sv/YgWXWmRc5ZaJo=' \
--header 'X-Shopify-Shop-Domain:appuninstalledwebhooktest.myshopify.com' \
--header 'Content-Type:application/json' \
--data '{}'
You should get an error message back: "Invalid webhook signature."
That will at least confirm that the endpoint is accessible and is attempting to verify the HMAC.
EDIT: if you are ok sharing your app's hostname, I can put together a curl POST request for you that will actually validate and should trigger your webhook handler. If you'd prefer to not share your app's hostname with everyone, you can DM it to me on twitter @squatto
I'd say move this to a new issue since its not really related. If you feel its still related to the new code, still make a new issue, would be easier to track :)
Hey I've just switched to cookieless branch due to the 419 page expired errors on Inertia form POST requests. Here is what made the debugging and fixing complicated:
my regular Chrome without incognito worked just fine with master branch of this package, with cookies powered approach, and I managed to go through basic approval process (when you get "your app approved, minor changes required" email), all POSTs were working fine, etc. Sometimes the Shopify reviewer was loosing session and was redirected to login screen, but I couldn't reproduce this on my browser. It turned out Chrome in Incognito mode treats 3rd party cookies differently. My chrome version: Version 90.0.4430.212 (Official Build) (x86_64) (macOS). I thought I am ready for public release and then this 419 error on forms submits and later investigation of chrome network tab leads me to the JWT tokens topic.
I've switched to cookieless branch and re-iterated everything, made sure that my Inertia axios gets proper token headers (btw no Inertia fork was needed for me, for now, I think it works out of the box).. Then on App install I've started to get "Missing auth url" Exception for some reason. After spending several hours scratching my head I found the reason of this: I forgot to launch migrations after switching to cookieless and the InstallShop middleware was swallowing "Unknown column in table users: password_updated_at" in https://github.com/osiset/laravel-shopify/blob/feature/cookieless/src/ShopifyApp/Actions/InstallShop.php#L97 and just returning empty array: ['shop' => null, 'completed' => false, 'url' => null]
When I launched the migration, this error is gone..
I will spend another day testing everything since I have a feeling this is not the last thing to fix :) Just my 5 cents, may be it will help someone. Thank you for your hard work!
@thang12l
Does anyone still get 500 error on the billing routes?
The auth()->user() function always returns null in the BillingController and Billable middleware.
I'm also getting this error. What should i do?
Thank you
In master branch, I used MissingShopDomainException exception to catch it (in my exception Handler.php) and redirect customer to my own login form. For some reason there is no such exception class in cookieless exception. Is this intended? What is the proper way to route visitors which have not yet entered "shop" or for some reason lost session?
For now I hacked Traits/AuthController.php::authenticate
method and and added
if (!$request->get('shop')) {
throw new MissingShopDomainException('No shop specified');
}
to the else
condition.
Since my login form (which has single 'shop' input) has /login
path I've also had to hack /Http/Middleware/VerifyShopify.php::handle
to append this url to avoid endless redirect loop:
if (Str::contains($request->getRequestUri(), ['/authenticate', '/billing', '/login'])) {
return $next($request);
}
this definitely feels hacky and there should be a better way.
@thang12l
Does anyone still get 500 error on the billing routes? The auth()->user() function always returns null in the BillingController and Billable middleware.
I'm also getting this error. What should i do?
Thank you
@mohinht ,
I am not sure that you got the 500 error at the same stage with me or not but here is where I am now.
[Using Chrome on Windows 10, with Incognito mode OFF]
[Using Chrome on Windows 10, with Incognito mode ON]
TL
Just update for Chrome, Incognito ON, Actually, the billing routes get 500 error when Block 3rd-party cookie is ENABLE because the shop domain is not accessible.
public function index(Request $request, ?int $plan = null, GetPlanUrl $getPlanUrl): ViewView
{
/** @var $shop IShopModel */
//$shop = auth()->user();
$shop = getShopForBilling($request);
If I try to get the shop not from auth() facade but from getShopForBilling helper, can get through to the confirmation URL. After the user approves for the charge, get another fail when Shopify redirects back to /authenticate route because the shop domain will be lost again. Since sessionStorage is not available, the app won't know who to authenticate for!
@thang12l
Yes, you are right. if block third-party cookies. Then billing is not working for the cookieless branch.
And I was checking after block the third-party cookie.
I think it should work from auth() facade.
@mohinht ,
Current authentication is lost after confirmation of billing charge because the return URL that Shopify will do redirect has only plan Id. If we can pass the shopDomain or just shop Id to the return URL then BillingController knows which shop to be processed with. In this way, we don't need to rely on any cookie and/or sessionStorage of Safari or Chrome.
TL
@mohinht ,
Current authentication is lost after confirmation of billing charge because the return URL that Shopify will do redirect has only plan Id. If we can pass the shopDomain or just shop Id to the return URL then BillingController knows which shop to be processed with. In this way, we don't need to rely on any cookie and/or sessionStorage of Safari or Chrome.
TL
I can confirm issues with billing routes.
I've tried to encode shop into the return url query argument (to do this, I modified ChargeHelper.php
URL::secure()
call) but returning back from charging started to throw Unable to verify signature
exception. So I had to encode shop into billing/process route in shopify.php like so:
if (registerPackageRoute('billing.process', $manualRoutes)) {
Route::get(
'/billing/process/{plan}/{shop}',
'Osiset\ShopifyApp\Http\Controllers\BillingController@process'
)
->middleware(['verify.shopify'])
->where('plan', '^([0-9]+|)$')
->where('shop', '^([\w\.\-]+|)$')
->name(getShopifyConfig('route_names.billing.process'));
}
I also had to modify BillingController.php::process() signature to accept shop domain:
public function process(
int $plan,
string $shopName,
Request $request,
ActivatePlan $activatePlan
): RedirectResponse {
// Activate the plan and save
$shopQuery = new Shop();
$shop = $shopQuery->getByDomain(ShopDomain::fromNative($shopName));
$result = $activatePlan(
$shop->getId(),
PlanId::fromNative($plan),
ChargeReference::fromNative((int) $request->query('charge_id'))
);
// Go to homepage of app
return Redirect::route(getShopifyConfig('route_names.home'), [
'shop' => $shop->getDomain()->toNative(),
])->with(
$result ? 'success' : 'failure',
'billing'
);
}
only after this I was able to go through charge activation without issues.
But the question is: does it introduce a security hole (since verify middleware does not run on /billing/process
, and shop is encoded in request uri so I don't think it is verified against malicious tampering, only query parameters are verified by hmac) and should I do additional check the authenticity of shop (or encrypt it with standard laravel functionality)?
BTW: my Inertia/Vue cookieless Laravel app got approved on Shopify App Store! it was a bumpy road due to this cookie situation, and it required custom session middleware which uses headers instead of cookies, and axios interceptors, but it could be worse :)
I have set up this cookieless branch and currently testing it after login into this app, I did not see any Authorization Bearer in the browser request header
Secondly, I cannot send any ajax call due to CSRF token mismatch issue.
how can I identify that the Session Token is added to the browser request header as in the above image?
@restyler
I also had to modify BillingController.php::process() signature to accept shop domain:
public function process( int $plan, string $shopName, Request $request, ActivatePlan $activatePlan ): RedirectResponse { // Activate the plan and save $shopQuery = new Shop(); $shop = $shopQuery->getByDomain(ShopDomain::fromNative($shopName)); $result = $activatePlan( $shop->getId(), PlanId::fromNative($plan), ChargeReference::fromNative((int) $request->query('charge_id')) ); // Go to homepage of app return Redirect::route(getShopifyConfig('route_names.home'), [ 'shop' => $shop->getDomain()->toNative(), ])->with( $result ? 'success' : 'failure', 'billing' ); }
Thanks for the reply.
Should i import 2 classes to the Traits/Billing Controller.php? use Osiset\ShopifyApp\Objects\Values\ShopDomain; use Osiset\ShopifyApp\Storage\Queries\Shop;
Thank you!
Should i import 2 classes to the Traits/Billing Controller.php? use Osiset\ShopifyApp\Objects\Values\ShopDomain; use Osiset\ShopifyApp\Storage\Queries\Shop;
Thank you!
correct. I should emphasise that I have no relation to this package maintainers and this is just my approach, which might be suboptimal and even dangerous (as noted above).
@osiset any news about this issue? It looks like it's becoming really problematic
@restyler
correct. I should emphasise that I have no relation to this package maintainers and this is just my approach, which might be suboptimal and even dangerous (as noted above).
Yes, Thanks for the clarification. Still, I'm getting error after modifying shopify.php and Billing Controller.php
I'm trying to get plan in this way. <a href="{{ route('billing', ['plan' => 1]) }}">Test plan</a>
Thank you!
@restyler
correct. I should emphasise that I have no relation to this package maintainers and this is just my approach, which might be suboptimal and even dangerous (as noted above).
Yes, Thanks for the clarification. Still, I'm getting error after modifying shopify.php and Billing Controller.php
I'm trying to get plan in this way.
<a href="{{ route('billing', ['plan' => 1]) }}">Test plan</a>
Thank you!
This happens due to the not-perfect approach of excluding verify middleware ( which sets up the user for you) on '/billing' routes. (discussed above). I've edited VerifyShopify middleware in the line where it checks the current uri and in my case it looks like this:
// Continue if current route is an auth or billing route
if (Str::contains($request->getRequestUri(), ['/authenticate', '/login', '/billing/process'])) {
return $next($request);
}
You should also make sure your link is properly prepared to pass session id to the billing route.
I have set up this cookieless branch and currently testing it after login into this app, I did not see any Authorization Bearer in the browser request header
Secondly, I cannot send any ajax call due to CSRF token mismatch issue.
how can I identify that the Session Token is added to the browser request header as in the above image?
You need to this code on your controller to get token in your request. return redirect()->tokenRedirect('route-name');
@squatto has done this awesome job. Thanks a lot to make it easier for us.
I think Excellent guidelines will be coming soon. Maybe @osiset will do a release soon.
@osiset Can you please look billing issue? I've tried to find the issue. But no luck.
Thank very much
Hi, @restyler , Thanks for the detailed answer.
You should also make sure your link is properly prepared to pass session id to the billing route.
How can I pass the session id to the billing route? In this package, Macro is already added to pass tokens from the controller.
I don't know, how I do that for the billing route? Do you have any suggestions for that?
Thank you!
Don't know if this will help anyone, but I used this to inject the appbridge session token onto all axios requests, in my laravel-shopify backed react SPA. Doing it this way meant that laravel bootstrap.js handling of the csrf token appears too still work. CSRF doesn't work this way, as the way it currently works is with a cookie. I'm thinking that having a session auth on it might preclude the requirement for csrf though, have excepted my endpoints for now. Will have to consider this more.
Great work @osiset, thanks very much for your dedication to this.
I put all this in the first subcomponent of <Provider>
.
const bridge = useAppBridge()
// and to set up the interceptor, zero dependency, run-once useEffect.
// i suppose you could use componentWillMount() or componentDidMount(), too
useEffect(() => {
axios.interceptors.request.use(async (config) => {
const setToken = async (config) => {
const newToken = await getSessionToken(bridge)
config.headers.common['Authorization'] = `Bearer ${newToken}`
return config
}
const newConfig = await setToken(config)
return newConfig
}, (error) => {
return Promise.reject(error)
})
// any subsequent axios requests anywhere in the app will grab a new session token first.
}, [])
Hi all - Raff here from Shopify. I wanted to say thank you to everybody who submitted ideas, suggestions and concerns on this thread. There is a lot of extremely relevant context and I will make sure to share your feedback with our team. I don't want to hijack this conversation, so please join us on the Shopify Partners Slack organisation if you have any questions/feedback relevant to JWT or the embedded app auth flow that is not relevant to this library. And of course, I will continue to :eyes: this convo to make sure we can bring any additional context.
I currently don't have any time to look into the billing issue this week, I am slammed at worked. I will have to look at this next week, but if anyone finds a solution, feel free to submit a PR.
For the current open PRs, I will be looking at these tomorrow. Thanks everyone.
Hi, @restyler , Thanks for the detailed answer.
You should also make sure your link is properly prepared to pass session id to the billing route.
How can I pass the session id to the billing route? In this package, Macro is already added to pass tokens from the controller. I don't know, how I do that for the billing route? Do you have any suggestions for that?
Thank you!
@stevesweets approach is the same as I am using in my Vue+Inertia setup. Axios interceptors are the way to go.
@squatto I hope you can help me Hi I'm having difficulties with jquery on a page embedded in admin
I get this constant error repeated forever in console
Uncaught (in promise) TypeError: window.jQuery.ajaxSettings.headers is undefined
originated in
async function retrieveToken(app) {
any suggestion?
Edit.
I've tried changing it with
// jQuery
window.jQuery.ajaxSetup({
headers: { 'Authorization': `Bearer ${window.sessionToken}` }
});
and I get rid of the error
But Now i get a 403 error on ajax post
Session token has expired.
This happens if I wait a little bit to press the save button, if i press it right away i get 200 OK
and these warnings
(sorry not exact text cause my browser it's in italian)
cookie “XSRF-TOKEN” has been rejected because cross site and it has the “SameSite” attribute set to “Lax” o “Strict”
cookie “laravel_session” has been rejected because cross site and it has the “SameSite” ha valore “Lax” o “Strict”.
(this warning were solved setting same-site to none in config/session.php is it correct?)
@andthink Please change your code in the token_handler.blade.php as in the below screenshot. it will resolve your constant repeated error in the console.
file path: vendor/osiset/laravel-shopify/src/ShopifyApp/resources/views/partials/token_handler.blade.php
Hi Everybody
I've a strange loop issue that happens when i first enter in my app , it's a classic one not SPA.
First click on app I get a network loop, that actually end itself after a lot of iterations (for example in this case 187 loops), and then the app seems to work
the loop happens between /?token=....
and /authenticate/?token=....
any suggestion on what may cause this strange behaviour?
Thanks
I've been on vacation so I'm a bit behind - I'll respond to the messages above as soon as possible. I just thought that everyone here would like to see this:
My app is 100% non-SPA and works like a charm. I went against @osiset's advice and I'm using the cookieless branch in production 😉
Congratulations @squatto Could you please look at this package about the remaining issue? It will be really admirable.
Thank you!
@squatto Ha! Awesome!
Hi everybody
any suggestion for this issue? I'm the only one with this problem?
https://github.com/osiset/laravel-shopify/issues/744#issuecomment-859550283
Hello @squatto I'm trying to get all products using Axios. But I'm getting 403. Maybe there is a need to pass the token. But I cannot do that.
When I try to post a request, I was doing that to my controller. return redirect()->tokenRedirect('route-name');
But when I try to get the product, I need to return the expected result to the get request. How can I pass the token, When I will try to request to get data? Or What should I do? Please help me
Thank you!
Hey everyone,
Shopify would like to thank the contributors on this issue and their work in helping apps migrate to session tokens. Those listed below, please email me at jason.tigas@shopify.com with your shipping address, phone # and shirt size so I can ship you a hat and t-shirt.
@osiset @squatto @thang12l @Enmaboya @stevesweets
Thanks again!
Hi all!
Does anyone have a summary of what is not working with the Billing feature? Does certain parts work like subscriptions or one-time payments? Or is the whole thing broken?
I'd like take the opportunity to thank everyone for their effort as well!
Hi all!
Does anyone have a summary of what is not working with the Billing feature? Does certain parts work like subscriptions or one-time payments? Or is the whole thing broken?
I'd like take the opportunity to thank everyone for their effort as well!
this only works in the "feature/cookieless" branch.
but, as written above, it is not recommended to use it in production yet.
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.