Shopify authorization configures the Spring Security filter chain more than had been expected. All the security logic can be separated into a JAR that automatically configures the importing application to function as a Shopify app. For example, to configure landing pages you shouldn't have to go in to the Thymeleaf files the current project provides.
Check out the new project.
How can we use Shopify's default OAuth offline access token in a Spring Boot app, leveraging the power of Spring Security? This working implementation only requires a few lines in the application.properties file for it to work. It's a server that authenticates with Shopify and, upon successful authentication, keeps the OAuth token, store name, and api key in a session object, which can be used in a variety of ways, such as a single page web application (or React/Polaris).
We assume you know your way around the Shopify developer site to create apps and development stores. Once you have a development store, create a private app.
- Fill out "App name" with the name of your choice.
- Add your "App URL":
- https://{your-hostname}/install/shopify
- For "Whitelisted redirection URL(s)" add:
- https://{your-hostname}/login/app/oauth2/code/shopify
Now that you've created your app, you're given an API key and an API key secret.
- Copy the API key and API key secret from the Shopify site.
- Store them, along with the desired scope, in
application.properties
:
shopify.client.client_id=your-key
shopify.client.client_secret=your-key-secret
shopify.client.scope=scope1,scope2,...
- Choose the salt and password that the Spring encryptors will use to encrypt the token and add them to your
application.properties
:
lm.security.cipher.password=your-passwords
lm.security.cipher.salt=your-salt
-
Whether you're using ngrok, or your own server, make sure you use HTTPS to comply with Shopify's security requirements.
-
Make sure your app is running and live at the hostname you specified.
-
That's it!
Try out the following endpoints from your browser:
- /install/shopify?shop={your-store-name.myshopify.com}: to log in (and install the app on the given store)
- /init: to log in by entering your store in a form
- /products: a secure endpoint
- /logout: to log out
For example, say you have a store with the name "mysamplestore".
- Go to https://{your-hostname}/install/shopify?shop=mysamplestore.myshopify.com
- Follow the instruction on the browser to authenticate.
- If this is the first time, install the store.
- If you go to the Shopify Admin for "mysamplestore", under Apps, you should see the new app you installed.
- Click on the app from the Shopify Admin.
- This should load the embedded app; by default, you should see "WELCOME".
You can change the defaults in the SecurityConfig
class in the com.lm.security.configuration
package.
Note: Once the app is installed, it expects to find a token in the database. If it is lost (for example if the database is in-memory and the server restarts), you will not be able to log in via the embedded app. Access the app directly from a browser, which will trigger the OAuth redirects and save the token in the database. You should then be able to log in as an embedded app.
Note: This Spring Security application requires the Java Cryptography Encryption policy files for encryption.
See https://www.oracle.com/technetwork/java/javase/downloads/jce-all-download-5170447.html
A request to /install/shopify will either:
- redirect to /init if the request is missing a shop parameter
- pass through with a
OAuth2AuthenticationToken
if the store exists and this is an embedded app - initiate the OAuth flow. If the request is not coming from an embedded app (regardless of whether or not this app has been installed), or if the request came from an embedded app (and this app has not been installed), the
ShopifyOAuth2AuthorizationRequestResolver
and its helper classes prepare for the first step of the OAuth flow by saving anOAuth2AuthorizationRequest
in the session, and creating and saving the redirect uris as request attributes for retrieval from the javascript fragment that will redirect.
Assuming the redirect takes place, the OAuth flow begins.
The OAuth2LoginAuthenticationFilter
/AbstractAuthenticationProcessingFilter
matches the default {baseUrl}/login/app/oauth2/code/shopify and...
- Retrieves and removes the
OAuth2AuthorizationRequest
saved byShopifyHttpSessionOAuth2AuthorizationRequestRepository
- Builds an
OAuth2AuthorizationResponse
from the Shopify response parameters - Builds an
OAuth2AuthorizationExchange
that contains theOAuth2AuthorizationRequest
andOAuth2AuthorizationResponse
- Uses the
OAuth2AuthorizationExchange
along with the corresponding ShopifyClientRegistration
to build anOAuth2LoginAuthenticationToken
- Delegates to
OAuth2LoginAuthenticationProvider
, which returns aOAuth2LoginAuthenticationToken
- Uses the
OAuth2LoginAuthenticationToken
to create anOAuth2AuthenticationToken
and anOAuth2AuthorizedClient
- Uses the default
AuthenticatedPrincipalOAuth2AuthorizedClientRepository
(which uses the customShopifyOAuth2AuthorizedClientService
) to save theOAuth2AuthorizedClient
- Calls
sessionStrategy.onAuthentication(...)
on the defaultNullAuthenticatedSessionStrategy
(does nothing) - Calls
successfulAuthentication(...)
which sets the authentication in theSecurityContextHolder
, takes care of other services, and finally delegates to the customNoRedirectSuccessHandler
successHandler, which forwards to login/app/oauth2/code.
The default OAuth2LoginAuthenticationProvider
...
- Uses a custom
OAuth2AccessTokenResponseClient
,ShopifyAuthorizationCodeTokenResponseClient
, to get anOAuth2AccessTokenResponse
- Asks the custom implementation of
OAuth2UserService<OAuth2UserRequest, OAuth2User>
,DefaultShopifyUserService
, to load theOAuth2User
. - Returns a
OAuth2LoginAuthenticationToken
using theClientRegistration
,AuthorizationExchange
,OAuth2User
, ...