Skip to content

Commit 20b8c8e

Browse files
committed
Add support to app authentication
1 parent d3c18f8 commit 20b8c8e

22 files changed

+780
-588
lines changed

README.md

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
- [Shop Pay](#shop-pay)
3838
- [Customer Account API](#customer-account-api)
3939
- [Offsite Payments](#offsite-payments)
40+
- [App Authentication](#app-authentication)
41+
- [How to generate the JWT](#how-to-generate-the-jwt)
4042
- [Explore the sample apps](#explore-the-sample-apps)
4143
- [Contributing](#contributing)
4244
- [License](#license)
@@ -181,17 +183,14 @@ The SDK provides a way to customize the presented checkout experience via the `S
181183
By default, the SDK will match the user's device color appearance. This behavior can be customized via the `colorScheme` property:
182184

183185
```swift
184-
// [Default] Automatically toggle idiomatic light and dark themes based on device preference (`UITraitCollection`)
186+
// Automatic (matches device appearance)
185187
ShopifyCheckoutSheetKit.configuration.colorScheme = .automatic
186188

187-
// Force idiomatic light color scheme
189+
// Force light mode
188190
ShopifyCheckoutSheetKit.configuration.colorScheme = .light
189191

190-
// Force idiomatic dark color scheme
192+
// Force dark mode
191193
ShopifyCheckoutSheetKit.configuration.colorScheme = .dark
192-
193-
// Force web theme, as rendered by a mobile browser
194-
ShopifyCheckoutSheetKit.configuration.colorScheme = .web
195194
```
196195

197196
### `tintColor`
@@ -560,7 +559,78 @@ public func checkoutDidClickLink(url: URL) {
560559
}
561560
```
562561

563-
---
562+
## App Authentication
563+
564+
App Authentication allows your app to securely identify itself to Shopify Checkout Kit and access additional permissions or features granted by Shopify. If your integration requires App Authentication, you must generate a JWT (JSON Web Token) on your secure server and pass it to the SDK.
565+
566+
### How to generate the JWT
567+
568+
**Prerequisites:**
569+
1. Create a Shopify app in the [Shopify Partners Dashboard](https://partners.shopify.com/organizations) or CLI to obtain your `api_key` and `shared_secret`.
570+
2. Install the app on a merchant's shop to obtain an `access_token` via the OAuth flow.
571+
572+
**Encrypt your access token:**
573+
- Use AES-128-CBC with your `shared_secret` to encrypt the `access_token`.
574+
- Derive encryption and signing keys from the SHA-256 hash of your `shared_secret`.
575+
- Base64 encode the result.
576+
577+
<details>
578+
<summary>Pseudo-code (Ruby) for encrypting your access_token</summary>
579+
580+
```ruby
581+
shared_secret = <your_shared_secret>
582+
access_token = <your_access_token>
583+
584+
key_material = OpenSSL::Digest.new("sha256").digest(shared_secret)
585+
encryption_key = key_material[0,16]
586+
signature_key = key_material[16,16]
587+
588+
cipher = OpenSSL::Cipher.new("aes-128-cbc")
589+
cipher.encrypt
590+
cipher.key = encryption_key
591+
cipher.iv = iv = cipher.random_iv
592+
raw_encrypted_token = iv + cipher.update(access_token) + cipher.final
593+
594+
signature = OpenSSL::HMAC.digest("sha256", signature_key, raw_encrypted_token)
595+
encrypted_access_token = Base64.urlsafe_encode64(raw_encrypted_token + signature)
596+
```
597+
</details>
598+
599+
**Create the JWT payload:**
600+
601+
```json
602+
{
603+
"api_key": "<your_api_key>",
604+
"access_token": "<your_encrypted_access_token>",
605+
"iat": <epoch_seconds>,
606+
"jti": "<unique_id>"
607+
}
608+
```
609+
610+
**Sign the JWT:**
611+
612+
```ruby
613+
require 'jwt'
614+
require 'securerandom'
615+
616+
payload = {
617+
api_key: '<your_api_key>',
618+
access_token: '<your_encrypted_access_token>',
619+
iat: Time.now.utc.to_i,
620+
jti: SecureRandom.uuid
621+
}
622+
623+
token = JWT.encode(payload, '<your_shared_secret>', 'HS256')
624+
```
625+
626+
**Use the JWT in your iOS app:**
627+
628+
```swift
629+
let checkoutOptions = CheckoutOptions(appAuthentication: .token("<JWT_TOKEN>"))
630+
ShopifyCheckoutSheetKit.present(checkout: checkoutURL, from: self, delegate: self, options: checkoutOptions)
631+
```
632+
633+
> **Note:** The JWT should always be generated on a secure server, never on the device.
564634
565635
## Explore the sample apps
566636

Samples/MobileBuyIntegration/MobileBuyIntegration/AppConfiguration.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ public final class AppConfiguration: ObservableObject {
3737

3838
// Displays the Checkout with ApplePay button
3939
@Published var applePayEnabled: Bool = true
40+
41+
/// Optional app authentication token for partner app identification
42+
/// This should be generated on a secure server, not on the device
43+
@Published var appAuthenticationToken: String?
4044
}
4145

4246
public var appConfiguration = AppConfiguration() {

Samples/MobileBuyIntegration/MobileBuyIntegration/AppDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
2929
func application(_: UIApplication, willFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
3030
ShopifyCheckoutSheetKit.configure {
3131
/// Checkout color scheme setting
32-
$0.colorScheme = .web
32+
$0.colorScheme = .automatic
3333

3434
/// Customize progress bar color
3535
$0.tintColor = ColorPalette.primaryColor

Samples/MobileBuyIntegration/MobileBuyIntegration/Localizable.xcstrings

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
},
1919
"Adding..." : {
2020

21+
},
22+
"App Authentication" : {
23+
2124
},
2225
"Apple Pay not available" : {
2326

@@ -36,6 +39,9 @@
3639
},
3740
"Clear logs" : {
3841

42+
},
43+
"Enter a JWT token for app authentication. This token should be generated on a secure server, not on the device. Leave empty to disable app authentication." : {
44+
3945
},
4046
"Events" : {
4147

@@ -54,6 +60,9 @@
5460
},
5561
"Handle Product URLs" : {
5662

63+
},
64+
"JWT Token" : {
65+
5766
},
5867
"Loading products..." : {
5968

Samples/MobileBuyIntegration/MobileBuyIntegration/ViewControllers/CheckoutController.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,14 @@ class CheckoutController: UIViewController {
4444

4545
public func present(checkout url: URL) {
4646
if let rootViewController = window?.topMostViewController() {
47-
ShopifyCheckoutSheetKit.present(checkout: url, from: rootViewController, delegate: self)
47+
var options: CheckoutOptions?
48+
49+
// Configure app authentication if token is provided
50+
if let token = appConfiguration.appAuthenticationToken, !token.isEmpty {
51+
options = CheckoutOptions(appAuthentication: .token(token))
52+
}
53+
54+
ShopifyCheckoutSheetKit.present(checkout: url, from: rootViewController, delegate: self, options: options)
4855
root = rootViewController
4956
}
5057
}

Samples/MobileBuyIntegration/MobileBuyIntegration/Views/SettingsView.swift

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ struct SettingsView: View {
5555
Toggle("Show Checkout with ApplePay button", isOn: $config.applePayEnabled)
5656
}
5757

58+
Section(header: Text("App Authentication")) {
59+
TextField("JWT Token", text: Binding(
60+
get: { config.appAuthenticationToken ?? "" },
61+
set: { config.appAuthenticationToken = $0.isEmpty ? nil : $0 }
62+
))
63+
.textFieldStyle(RoundedBorderTextFieldStyle())
64+
65+
Text("Enter a JWT token for app authentication. This token should be generated on a secure server, not on the device. Leave empty to disable app authentication.")
66+
.font(.caption)
67+
.foregroundColor(.secondary)
68+
}
69+
5870
Section(header: Text("Theme")) {
5971
ForEach(Configuration.ColorScheme.allCases, id: \.self) { scheme in
6072
ColorSchemeView(scheme: scheme, isSelected: scheme == selectedColorScheme)
@@ -145,25 +157,27 @@ extension Configuration.ColorScheme {
145157
return "Dark"
146158
case .automatic:
147159
return "Automatic"
148-
case .web:
149-
return "Web"
150160
}
151161
}
152162

153163
var tintColor: UIColor {
154164
switch self {
155-
case .web:
156-
return UIColor(red: 0.18, green: 0.16, blue: 0.22, alpha: 1.00)
157-
default:
165+
case .light:
166+
return UIColor(red: 0.09, green: 0.45, blue: 0.69, alpha: 1.00)
167+
case .dark:
168+
return UIColor(red: 0.09, green: 0.45, blue: 0.69, alpha: 1.00)
169+
case .automatic:
158170
return UIColor(red: 0.09, green: 0.45, blue: 0.69, alpha: 1.00)
159171
}
160172
}
161173

162174
var backgroundColor: UIColor {
163175
switch self {
164-
case .web:
165-
return ColorPalette.backgroundColor
166-
default:
176+
case .light:
177+
return .systemBackground
178+
case .dark:
179+
return .systemBackground
180+
case .automatic:
167181
return .systemBackground
168182
}
169183
}

0 commit comments

Comments
 (0)