gnikyt / laravel-shopify

A full-featured Laravel package for aiding in Shopify App development
MIT License
1.24k stars 374 forks source link

Auth Flow Refactoring #744

Closed gnikyt closed 3 years ago

gnikyt commented 3 years ago

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.

gnikyt commented 3 years ago

It seems its unknown who the shop is when returning from billing. I have time booked off Wednesday to look at this remaining bug.

andthink commented 3 years ago

Regarding the infinite loop in https://github.com/osiset/laravel-shopify/issues/744#issuecomment-859550283

I've found that it happens only on my development server, and not locally

and specifically in

     * Checks the token to ensure its not expired.
     *
     * @throws AssertionFailedException If token is expired.
     *
     * @return void
     */
    protected function verifyExpiration(): void
    {
        $now = Carbon::now();
        Assert::thatAll([
            $now->greaterThan($this->exp),
            $now->lessThan($this->nbf),
            $now->lessThan($this->iat),
        ])->false(self::EXCEPTION_EXPIRED);
    }

here commenting the check the infinite loops stops. I'm trying to understand what happens. Any suggestion? I was thinking maybe some sort of cache (opcache?) or something like that

gnikyt commented 3 years ago

Only thing I can suggest at the moment is to log all the array values to see which one is false.

On Mon., Jun. 21, 2021, 15:15 andthink, @.***> wrote:

Regarding the infinite loop in #744 (comment) https://github.com/osiset/laravel-shopify/issues/744#issuecomment-859550283

I've found that it happens only on my development server, and not locally

and specifically in /* Checks the token to ensure its not expired. @throws AssertionFailedException If token is expired. @return void */ protected function verifyExpiration(): void { $now = Carbon::now(); Assert::thatAll([ $now->greaterThan($this->exp), $now->lessThan($this->nbf), $now->lessThan($this->iat), ])->false(self::EXCEPTION_EXPIRED); }

here commenting the check the infinite loops stops. I'm trying to understand what happens. Any suggestion? I was thinking maybe some sort of cache (opcache?) or something like that

— 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-865225719, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASO4OWC4Z24GXPOX4XYLALTT53EJANCNFSM4ZLA3ICA .

andthink commented 3 years ago

@osiset thanks, i found out that the time of the server was two minutes in the past, and that caused the infinite loop

andthink commented 3 years ago

@squatto Hi!

I've a question about

async function retrieveToken(app) {
  window.sessionToken = await utils.getSessionToken(app);
}

to me window.sessionToken never changes even if it's called every x seconds....

any suggestion on what Can I possibily do wrong?

gnikyt commented 3 years ago

I have booked off some time today to look into the billing issue. I will also review the PRs. Thanks everyone... hopefully we can nail out this last issue and get a release up.

gnikyt commented 3 years ago

I'll be reviewing this comment from @restyler, and see what I can come up with in terms of this security issue, maybe we can encode/decode the shop param with the secret key or something... I'll see what I can come up with. We're almost there!

gnikyt commented 3 years ago

Hey everyone... I was able to resolve the billing issue based on previous comments and some modifications. I have merged in the latest PRs sent to master as well, and updated everything to be in-sync.

In my testing today:

If you're available to do so, you're welcome to test the branch and confirm things are fine as well.

If nothing else urgent pops up, I will be formatting some upgrade docs and looking to push this out sometime this or next week.

LonnyX commented 3 years ago

@osiset do you plan to merge cookieless to master?

gnikyt commented 3 years ago

Once the branch is good to do so.

LonnyX commented 3 years ago

Ok good, if have some note of what is still not working right now I will try to help :)

gnikyt commented 3 years ago

I had a couple hours last night to go through it again.

I have not found any red flags to prevent this from going into master for adoption and a beta release ATM. There is quite a bit of pressure to get this out for people's failing apps due to the new requirements.

If no objections, I'll probably merge this into master around 1500 NST, and begin some draft updates to the wiki, upgrade notes, and changelog notes.

andthink commented 3 years ago

HI @osiset

I'm trying to understand how to make to work standard forms

~I've added a token input with session-token class into my form (standard not ajax)~

~when I post my form i get a redirect 302 to the same url, with token parameter attached, but now it's a GET and I've lost all my post parameters so my form cannot be saved.~

I was omitting value="" in my <input name="_token" class="session-token">

BUT I've issues also with ajax forms, but excepting the routes from CSRF made them work. Here I see that the very long token generated by await utils.getSessionToken(app); fails compare with laravel session token that is a lot shorter, so it seems I'm comparing "apples and pears" I really don't understand the relation between the token returned (by shopify?) in utils.getSessionToken(app); and the laravel session one...

@osiset can you confirm that for now we have to except all routes from CSRF to make form work and trust only VerifyShopify middleware?

gnikyt commented 3 years ago

Sadly yes, at the moment, unless someone can come up with a neat work around, CSRF has to be disabled for routes as the shop technically has a new session token from Laravel each route visit. @andthink

Shopify's token and Laravel's are not the same. Laravel has its own tokens which is generated using the secret key of your Laravel instance... normally this would be fine with cookies since its saved in a cookie and passed on to each request. Now, without cookies due to the requirements, this token is useless when hitting a new request because a new login is created thus a new CSRF token is created. This creates a mis-match with the Laravel tokens, which makes CSRF not work since it can't compare them.

andthink commented 3 years ago

Thanks @osiset! I'had the suspect! It's a pity we cannot disable CSRF directly altogether then, but it's ok. Maybe let's add a note in the wiki :-P

Sadly yes, at the moment, unless someone can come up with a neat work around, CSRF has to be disabled for routes as the shop technically has a new session token from Laravel each route visit. @andthink

Shopify's token and Laravel's are not the same. Laravel has its own tokens which is generated using the secret key of your Laravel instance... normally this would be fine with cookies since its saved in a cookie and passed on to each request. Now, without cookies due to the requirements, this token is useless when hitting a new request because a new login is created thus a new CSRF token is created. This creates a mis-match with the Laravel tokens, which makes CSRF not work since it can't compare them.

gnikyt commented 3 years ago

PR has been opened

gnikyt commented 3 years ago

We have merged the work into master.

Whats next?

  1. Draft updates to upgrading wiki
  2. Draft updates to installation wiki (if needed)
  3. Expanded information on tokens in wiki
  4. Removal of any mentions of cookies/ITP in wiki
  5. New wiki page for CSRF issues
  6. Release as major release, as it has breaking changes

Thanks everyone for your support and getting us to this point of having a working product without the reliance of cookies.

If there is any issues once released, we will of course attempt to resolve them and issue out patches.

Once released, any issues previously pertaining to cookies will be closed.

squatto commented 3 years ago

@squatto Hi!

I've a question about

async function retrieveToken(app) {
  window.sessionToken = await utils.getSessionToken(app);
}

to me window.sessionToken never changes even if it's called every x seconds....

any suggestion on what Can I possibily do wrong?

@andthink sorry to not get back to you sooner! Were you able to figure this out? I've run into situations (typically after leaving the browser open for a while) where the async doesn't resolve once, and it gets stuck forever. Are you never able to get a response?

andthink commented 3 years ago

@squatto Hi! I've a question about

async function retrieveToken(app) {
  window.sessionToken = await utils.getSessionToken(app);
}

to me window.sessionToken never changes even if it's called every x seconds.... any suggestion on what Can I possibily do wrong?

@andthink sorry to not get back to you sooner! Were you able to figure this out? I've run into situations (typically after leaving the browser open for a while) where the async doesn't resolve once, and it gets stuck forever. Are you never able to get a response?

@squatto i think it was the same situation, cause I tried again with a fresh browser and it worked! Thanks for the answer!

ingalesachin7 commented 3 years ago

I have upgraded to 17.0.0 for Removal of dependency on cookies, I try to install the app getting an authentication error Exception Variable $topic of type WebhookSubscriptionTopic! was provided invalid value

after refresh Osiset\ShopifyApp\Exceptions\MissingAuthUrlException Missing auth url

Please help anyone to resolve this...Is something is missing while upgrading.?

squatto commented 3 years ago

@ingalesachin7 I'm having the same problem. Can you start a new issue for it and we'll discuss it there?

squatto commented 3 years ago

For those that land here looking for the solution to the WebhookSubscriptionTopic issue, please refer to #866 and the "Creating Webhooks" wiki page for the solution.

TzoLy commented 3 years ago

Just uppercase the topic and replace / with _ like orders/create => ORDERS_CREATE

gnikyt commented 3 years ago

Yes, sorry, forgot to update the docs here, I believe squatto has updated the wiki for this yesterday so the information there should be good now.

On Wed., Jul. 7, 2021, 03:52 TzoLy, @.***> wrote:

Just uppercase the topic and replace / with _ like orders/create => ORDERS_CREATE

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/osiset/laravel-shopify/issues/744#issuecomment-875375410, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASO4OTZKPASVIDJNPHTPUTTWQBSXANCNFSM4ZLA3ICA .