The order confirmation page on our 3D cards
shop is
shown to a customer after they complete checkout, and is also linked to in the
confirmation and dispatch emails that they receive.
Importantly, this page is not authenticated; we don’t want a heavy sign up /
sign in process for customers as that tends to be annoying on other ecommerce
systems. I just want to buy some pop-up cards – why do I need to create an
account and remember a password etc?
Because the page is not authenticated, it would be possible for someone to guess
order IDs and try to iterate all of the order pages to scrape information. To
mitigate that, the order page contains no sensitive or personal information. It
just shows the order items and order time, to let the customer at least confirm
those details. The customer’s email address and delivery address are not shown,
so that they can not be leaked to scrapers.
This design is OK but has some drawbacks. It would be nice if the customer could
confirm all of their order details on the order page. In particular, we’d like
to encourage customers to double-check that their delivery address is correct
while there’s still time to change it. A surprising number of orders are lost
and must be refunded due to customers entering their delivery address
incorrectly.
The first idea we had was to show full details within a time limit after the
order is created, e.g. you get the full view for up to one hour after order
placement, and after that it just shows the basic non-sensitive version. The
vast majority of customers only look at the order confirmation view once anyway.
This would be easy to implement, but is risky – a scraper could come in during
that hour window and steal personal information (and the attacker does know the
system).
The second idea was to note the order id in the session, and then only show full
details if the current user’s session has that order id. This would prevent
anyone else getting the full details on the order view, and is better than the
time limit as there is no window in which a scraper can snatch the details; they
will never get the access set in their session. It would mean that once the
user’s session expires, they will no longer see full details on their order
confirmation page, but that’s acceptable.
The third idea was to use a
signature in the URL. The
application would generate a signature using the order id and and the
application’s secret key, and include that in the order confirmation URL. When
displaying the order confirmation view, the application would only display full
details if there is a valid signature.
It turns out that Laravel has a signed
URLs feature built-in for this
kind of situation. You can generate a signed URL like this:
<?php
URL::signedRoute('route.name', ['fooBar' => 42])
You can check if any request has a valid signature like this:
<?php
if ($request->hasValidSignature()) {
// act accordingly
}
It wouldn’t be difficult to implement this yourself, but it’s nice having it
built-in so it requires little effort to apply where necessary.
The signed URL approach is quite nice as it means you can have a permanent order
confirmation page with an un-guessable URL. Only the user has the signed URL as
you only give it to that user. However, it has a drawback for the same reason –
the user might share that URL (intentionally or inadvertently), and reveal their
email address and delivery address to whoever has the URL.
Sharing the URL is a valid use-case, though: the customer might want to let
someone else know that they have placed the order and show them the
confirmation.
In the end, we went with a combination of the session-access and signed URL
approaches. We note the order id in the session, and that is used in conjunction
with a valid signature to decide how much information to show on the order
confirmation page:
- Session access + signed URL: full details.
- No session access + signed URL: minimal order information (no personal
details).
- Unsigned URL: acknowledge that the order exists, but reveal no other
information.
By visiting the signed URL that they are given (i.e. redirected to) on order
completion, the customer can see the full order details including their email
address and their delivery address. This gives them a chance to confirm
everything is correct, and they can keep accessing this in the same browser for
as long as their session is alive (which is up to 24 hours), for example from
links in emails that they receive.
After the session has expired, or if they access the signed URL separately, the
user only sees minimal information about their order with no personal
information. This means that if the user does share their signed URL, the
personal information is not revealed but the order content is still shown.
Visiting the order confirmation view with an unsigned URL or an invalid
signature does acknowledge that such an order exists and shows the date it was
placed, but does not give any other information. This means that scrapers can
figure out our overall order numbers if they want to, but that’s not a concern
for us. Scrapers cannot gather any information about order value or content, and
most importantly cannot gain any customer information at all.
This combination solution reaches a good balance of utility whilst never
revealing personal user information to other people, which is the non-negotiable
requirement here.
View post:
Using Laravel signed routes to improve order confirmation security
|