Skip to content

Commit 124dfdf

Browse files
Merge pull request #450 from kinde-oss/Feat/App-store-guide
Feat/app store guide
2 parents f5a2598 + 84a8269 commit 124dfdf

File tree

2 files changed

+240
-6
lines changed

2 files changed

+240
-6
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
---
2+
page_id: d1268687-4af5-4758-af52-8a04e7010364
3+
title: Guide to app store reviews
4+
sidebar:
5+
order: 1
6+
relatedArticles:
7+
- 839112f5-c8bc-49ac-b4eb-bda592f5c011
8+
---
9+
10+
This guide is designed to help prepare your app for an app store review if you’re using Kinde for auth. It provides recommendations and solutions for setting up reviewer access.
11+
12+
This isn’t a complete list of preparation tasks, and there’s no guarantee your app will pass using only this guide, but it’s a solid starting point.
13+
14+
We’ll continue updating it as new scenarios arise. If you have suggestions, let us know.
15+
16+
## Prepare for Apple App Store review
17+
18+
Here’s how to set up Kinde in preparation for an Apple App Store review. We recommend setting up a hidden login route that’s only active when your app is being reviewed by Apple. Here’s how:
19+
20+
### Step 1: Detect the App Store review environment
21+
22+
Apple doesn’t offer an official way to detect its review environment, but you can use a few indirect methods.
23+
24+
**Method A: Check for a sandbox receipt**
25+
26+
When Apple reviews your app, all transactions occur in a sandbox environment. On iOS, you can check whether the app’s receipt is a sandbox receipt:
27+
28+
```swift
29+
30+
func isRunningInTestFlightOrAppStoreReview() -> Bool {
31+
#if DEBUG
32+
return false // Debug builds should not use the hidden route
33+
#else
34+
if let url = Bundle.main.appStoreReceiptURL {
35+
return url.lastPathComponent == "sandboxReceipt"
36+
}
37+
return false
38+
#endif
39+
}
40+
```
41+
42+
This method works because Apple’s review devices generate a `sandboxReceipt` file.
43+
44+
**Method B: Check TestFlight for a sandbox receipt**
45+
46+
If your app is distributed via TestFlight, you can also check for a sandbox receipt:
47+
48+
```swift
49+
func isRunningInTestFlight() -> Bool {
50+
return Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
51+
}
52+
```
53+
54+
### Step 2: Create a hidden login route for Apple testers
55+
56+
Once you detect that the app is running in Apple’s review environment, you can enable a special login route.
57+
58+
**Method A: Display a hidden sign-in button**
59+
60+
Using this method, you display a hidden sign-in button only when `isRunningInTestFlightOrAppStoreReview()` returns `true`. Testers can then sign in with predefined credentials:
61+
62+
```swift
63+
if isRunningInTestFlightOrAppStoreReview() {
64+
showHiddenLoginButton()
65+
}
66+
```
67+
68+
**Method B: Provide a deep link for signing in**
69+
70+
Using this method, you provide Apple with a deep link like `myapp://hidden-login`. You must ensure this route is only active when the app is in review mode.
71+
72+
You can set this up so that when Apple taps the link, they are signed in automatically or they are shown a sign-in screen with pre-filled demo credentials.
73+
74+
You might need to set up a webhook or other alert so you know when the link is clicked.
75+
76+
### Step 3: Add notes in the App Store review submission
77+
78+
Whatever method you use, clearly explain how testers can access your app in the **App Review Notes**. Provide the method and the credentials they can use.
79+
80+
Example message:
81+
82+
> Dear Apple Reviewer,
83+
84+
Since our app does not use email/password authentication, we have created a hidden sign in option for you to test.
85+
86+
If you are testing in `TestFlight` or the App Store review environment, a special sign in button will appear.
87+
88+
Alternatively, access the sign in screen via this deep link: `myapp://hidden-login`
89+
90+
Test credentials:
91+
92+
P******d
93+
>
94+
95+
## Google Play Store
96+
97+
Here’s how to set up Kinde in preparation for an Google Play review. We recommend setting up a hidden login route that’s only active when your app is being reviewed by Google.
98+
99+
Here’s how to set up a hidden authentication route for the Google Play app review process.
100+
101+
### Step 1: Detect the Google Play review environment
102+
103+
Google Play doesn’t offer an official review environment flag, but you can use indirect methods to detect it:
104+
105+
- **Check the installer package**: Apps installed from Google Play will have `com.android.vending` as the installer.
106+
- **Check device characteristics**: Review devices may share common traits, though this is less reliable.
107+
108+
**Example (Kotlin):**
109+
110+
```kotlin
111+
import android.content.Context
112+
import android.content.pm.PackageManager
113+
114+
fun isGooglePlayReviewEnvironment(context: Context): Boolean {
115+
val installer = context.packageManager.getInstallerPackageName(context.packageName)
116+
return installer == "com.android.vending"
117+
}
118+
```
119+
120+
### **Step 2: Restrict access to the hidden auth route**
121+
122+
Once you detect the review environment, create a secure, hidden API route.
123+
124+
1. Check for a secret token in the request header.
125+
2. Allow access only if the token matches your predefined key.
126+
127+
**Example (Next.js API Route):**
128+
129+
```kotlin
130+
import { NextResponse } from 'next/server';
131+
132+
export async function POST(req: Request) {
133+
const playStoreSecret = req.headers.get('x-play-review-secret');
134+
const isPlayStoreReview = playStoreSecret === process.env.PLAY_REVIEW_SECRET;
135+
136+
if (isPlayStoreReview) {
137+
return NextResponse.json({ message: 'Hidden auth route accessed!' });
138+
} else {
139+
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
140+
}
141+
}
142+
```
143+
144+
### **Step 3: Add a secret header in the app (during review only)**
145+
146+
When the app detects it's running in the Google Play review environment, send the secret header.
147+
148+
**Example (Kotlin using Retrofit):**
149+
150+
```kotlin
151+
val isReview = isGooglePlayReviewEnvironment(context)
152+
153+
if (isReview) {
154+
val request = Request.Builder()
155+
.url("https://yourapi.com/hidden-auth")
156+
.header("x-play-review-secret", "your-secret-key")
157+
.build()
158+
}
159+
```
160+
161+
**Official resources**
162+
163+
- [Requirements for providing login credentials for app access](https://support.google.com/googleplay/android-developer/answer/15748846?hl=en&sjid=2614470000031657771-NC)
164+
- [Make your app available for review](https://playacademy.exceedlms.com/student/collection/260728/path/345815/activity/776873)
165+
166+
## Use Kinde feature flags to switch on review mode
167+
168+
You can use Kinde feature flags to dynamically enable hidden routes or functionality during the app review process—without needing to redeploy or hard-code conditions.
169+
170+
### Step 1: Create a feature flag
171+
172+
1. In **Kinde** go to **Settings > Feature Flags.**
173+
2. Select **Add feature flag**. The **Add feature flag** window opens.
174+
175+
![feature flag top half of screen](https://imagedelivery.net/skPPZTHzSlcslvHjesZQcQ/59e501e6-77d6-4cf6-feed-b88a46a96f00/public)
176+
177+
3. Fill out the name, description, and key (e.g., `app_store_review_mode`).
178+
4. Select **Boolean** as the flag type.
179+
180+
![feature flag screen bottom half in Kinde](https://imagedelivery.net/skPPZTHzSlcslvHjesZQcQ/abd5ae55-cb13-4467-0d4b-d7eae2d7f800/public)
181+
182+
5. Set the **Boolean definition** to `false` (default state).
183+
6. Select **Save**.
184+
185+
You’ll toggle this flag to `true` during the review period, then back to `false` afterward.
186+
187+
### Step 2: Manage the flag in your backend
188+
189+
You’ll now use the flag in your backend logic to control access to a hidden login route.
190+
191+
**Example: Next.js API Route with Kinde Feature Flag:**
192+
193+
```tsx
194+
import { NextResponse } from 'next/server';
195+
import { getKindeServerSession } from '@kinde-oss/kinde-auth-nextjs/server';
196+
197+
export async function POST(req: Request) {
198+
const { getBooleanFlag } = getKindeServerSession();
199+
200+
const isFeatureEnabled = await getBooleanFlag(
201+
'app_store_review_mode', // Your feature flag key
202+
false // Default value
203+
);
204+
205+
if (isFeatureEnabled.value) {
206+
return NextResponse.json({ message: 'Hidden auth route accessed via feature flag!' });
207+
} else {
208+
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
209+
}
210+
}
211+
212+
```
213+
214+
### Step 3: Toggle the flag during review
215+
216+
When you submit your app for review:
217+
218+
- Set the flag `app_store_review_mode` to **true** in the Kinde dashboard.
219+
- After approval, set it back to **false** to hide the route again.
220+
221+
## You're ready to give app access to app store reviewers!
222+
223+
With these strategies, you can securely provide access to your app, for both Apple and Google Play reviewers, while keeping the login routes hidden from real users.
224+
225+
Using tools like Kinde feature flags, deep links, and environment checks ensures your review process is smooth and secure.
226+
227+
## Need help?
228+
229+
Join the [Kinde community on Slack](https://join.slack.com/t/thekindecommunity/shared_invite/zt-1vyq8qilj-jFH5V27jfFnHk~BuBSU0ZA) or the [Kinde community on Discord](https://discord.com/invite/tw5ng5tK6V) for support and advice from our team and other legends working with Kinde.

src/data/sidebarData.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ const sidebarData = [
1111
items: [
1212
{label: "Guides", autogenerate: {directory: "get-started/guides"}, collapsed: false},
1313
{
14-
label: "Learn about Kinde",
15-
autogenerate: {directory: "get-started/learn-about-kinde"},
16-
collapsed: false
17-
},
14+
label: "Learn about Kinde", autogenerate: {directory: "get-started/learn-about-kinde"}, collapsed: false},
1815
{
1916
label: "Switch to Kinde",
2017
autogenerate: {directory: "get-started/switch-to-kinde"},
@@ -265,17 +262,25 @@ const sidebarData = [
265262
},
266263
{
267264
label: "Features and releases",
268-
description: "Take control of feature development and releases ",
265+
description: "Take control of feature development and app releases",
269266
icon: "releases",
270267
collapsed: true,
271268
cardLink: "/releases/about/about-feature-flags/",
272269
items: [
273-
{label: "About", autogenerate: {directory: "releases/about"}, collapsed: false},
270+
{
271+
label: "About",
272+
autogenerate: {directory: "releases/about"},
273+
collapsed: false},
274+
{
275+
label: "Guides",
276+
autogenerate: {directory: "releases/guides"},
277+
collapsed: false},
274278
{
275279
label: "Feature flags",
276280
autogenerate: {directory: "releases/feature-flags"},
277281
collapsed: false
278282
}
283+
279284
// {
280285
// label: "Plan and release",
281286
// autogenerate: {directory: "releases/plan-and-release"},

0 commit comments

Comments
 (0)