Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion guides/plugins/apps/lifecycle/app-registration-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ As this process is also used for secret rotation, your app **must generate a new
To prevent failure of in-flight requests during the secret rotation, your app **should accept the old secret in parallel** with the new secret for a short period (e.g., 1 minute). After that grace period, the signatures based on the old secret **must be** considered invalid.
:::

::: info
On the Shopware side, a rotation or install whose confirmation is interrupted stays recoverable. See [App Secret Rotation & Recovery](app-secret-recovery.md) for the operator command `app:secret:rotate`, recovery by re-running `app:install`, and what to do when a secret has been claimed by another shop.
:::

## Requirements

Apps can declare requirements in their manifest that must be met for the app to function properly. For example, an app that communicates with the shop via the Admin API will fail if the shop is not publicly accessible — declaring that upfront avoids a registration attempt that is guaranteed to fail. Shopware validates requirements during installation and updates in `prod` environments. If a requirement is not met, the installation is rejected with a descriptive error before the app attempts registration.
Expand Down Expand Up @@ -495,7 +499,7 @@ That is why simple themes are not affected by shop migrations, and they will con
Every time a request is made against an app backend, Shopware checks whether the current APP_URL differs from the one used when Shopware generated an ID for this shop.
If the APP_URL differs, Shopware will stop sending requests to the installed apps to prevent data corruption on the apps' side.
Now the user can resolve the issue using one of the following strategies.
The user can either run a strategy with the `bin/console app:url-change:resolve` command, or with a modal that pops up when the Administration is opened.
The user can either run a strategy with the `bin/console app:shop-id:change` command (formerly `app:url-change:resolve`), or with a modal that pops up when the Administration is opened.

### APP_URL change resolver

Expand Down
155 changes: 155 additions & 0 deletions guides/plugins/apps/lifecycle/app-secret-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
nav:
title: Secret Rotation & Recovery
position: 45

---

# App Secret Rotation & Recovery

## Overview

This guide covers how to **rotate** an app's secret and how to **recover** an app when a secret rotation or first registration does not complete — using the `app:secret:rotate` command, and by re-running `app:install` to recover.

App registration shares a secret that Shopware uses to sign every request to the app's backend. Renewing that secret — and the first registration itself — takes two steps: the app generates a new secret during the handshake, then starts using it only once it has processed Shopware's confirmation request.

If that confirmation is interrupted — a crash, a lost response, a timeout, or an HTTP `5xx` — the app may have switched to the new secret while Shopware never recorded it. The two sides are then out of sync: a confirmed app rejects any re-registration it cannot authenticate, so Shopware can no longer reach it. Shopware keeps the unconfirmed secret so an operator can re-sync the two sides, as described below.

Check warning on line 16 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L16

Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[1]) Suggestions: `, so` URL: https://languagetool.org/insights/post/comma-before-and/ Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=1 Category: PUNCTUATION
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:16:343: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[1])
 Suggestions: `, so`
 URL: https://languagetool.org/insights/post/comma-before-and/ 
 Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=1
 Category: PUNCTUATION

::: info
This page is for operators and core developers. For the **app-side** protocol — validating the dual signature and generating a new secret on re-registration — see [App Registration & Backend Setup](app-registration-setup.md#secret-rotation-and-shop-url-changes) and [Signing & Verification in the App System](app-signature-verification.md).
:::

## The secret state model

Shopware tracks two values per app:

- **`app_secret`** — the committed, active secret. The one Shopware currently signs requests with.
- **`unconfirmed_app_secrets`** — a list of secrets generated during a handshake but **not yet confirmed**, newest first. These are the secrets the app *might* already hold that Shopware has not committed.

A (re-)registration moves them as follows:

| step or outcome | `app_secret` | `unconfirmed_app_secrets` |
|---|---|---|
| handshake (app generates a secret), **before** confirm | unchanged | the new secret is prepended |
| confirm returns **2xx** (confirmed) | set to the new secret | cleared |
| confirm returns **4xx** (app rejected it) | unchanged | the rejected secret is removed |
| confirm returns an HTTP **5xx** or times out (unknown) | unchanged | left as-is — kept for recovery |

::: info
`unconfirmed_app_secrets` is `NULL` whenever there is nothing pending. A non-null value means a rotation or install did not get a clear answer, and the app may already hold one of the listed secrets — that is the signal recovery acts on.
:::

A list (rather than a single value) matters because recovery can itself be interrupted: each recovery attempt prepends a freshly generated secret, so keeping the whole list ensures a later retry still has every secret the app might trust.

## Rotating a secret — `app:secret:rotate`

```bash
# Rotate one app's secret
bin/console app:secret:rotate <app-name>

# Rotate every active app
bin/console app:secret:rotate
```

Rotation re-registers the app with a freshly generated secret; the new secret becomes active only once the app confirms it. If the confirmation is interrupted, the new secret is retained in `unconfirmed_app_secrets` and the rotation reports a failure — the active secret is left untouched, and the app is recovered by re-running `app:install`, as described below.

::: warning
Rotation **refuses to run** if the app already has an unconfirmed secret (`appSecretRotationAlreadyPending`). Recover that secret first — rotating again would overwrite the only record of a secret the app may already hold.
:::

When triggered via the API, rotation is queued (`RotateAppSecretMessage`) and runs in the background; the CLI command runs it synchronously and reports the result directly.

## Recovering a stranded app — re-run `app:install`

When a rotation or install whose confirmation was interrupted leaves an unconfirmed secret, **re-run the install** — there is no separate recovery command and no opt-in flag:

Check warning on line 64 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L64

The word ‘install’ is not a noun. (A_INSTALL[2]) Suggestions: `installation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=2 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:64:75: The word ‘install’ is not a noun. (A_INSTALL[2])
 Suggestions: `installation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=2
 Category: GRAMMAR

Check warning on line 64 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L64

The word ‘install’ is not a noun. (A_INSTALL[1]) Suggestions: `installation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:64:177: The word ‘install’ is not a noun. (A_INSTALL[1])
 Suggestions: `installation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR

```bash
# Recover (and, if the install was interrupted, finish) the app — just install it again
bin/console app:install <app-name>
```

Re-running `app:install` detects the pending unconfirmed secret and re-registers the app on a fresh integration, signing each attempt with a secret the app might still hold — the **unconfirmed secrets newest-first, then the committed secret** as a fallback. The first secret the app accepts wins; a fresh secret is then committed and both sides are back in sync. A **half-finished install** is resumed and completed; a **fully-installed app** whose rotation broke is credential-repaired only. The `app.unconfirmed_app_secrets.count` gauge (below) surfaces how many apps currently need recovering.

Check warning on line 71 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L71

Use a comma before “and” if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[4]) Suggestions: `, and` URL: https://languagetool.org/insights/post/types-of-sentences/#compound-sentence Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=4 Category: PUNCTUATION
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:71:295: Use a comma before “and” if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[4])
 Suggestions: `, and`
 URL: https://languagetool.org/insights/post/types-of-sentences/#compound-sentence 
 Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=4
 Category: PUNCTUATION

Re-running `app:install <app>` on a pending app results in one of:

| result | what it means | next step |
|---|---|---|
| **Recovered & completed** | a secret the app still trusts was found, so both sides are re-synced; a half-finished install is finished | done |

Check warning on line 77 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L77

The word ‘install’ is not a noun. (A_INSTALL[3]) Suggestions: `installation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=3 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:77:80: The word ‘install’ is not a noun. (A_INSTALL[3])
 Suggestions: `installation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=3
 Category: GRAMMAR
| **Already installed** | no unconfirmed secret left to recover (or a concurrent run already recovered it) | — |
| **Outcome unknown** | an attempt timed out or returned an HTTP `5xx`; all state is kept intact | re-run `app:install` |
| **Recovery failed — claimed by another party** | the app trusts none of Shopware's secrets, so recovery is not possible | run `app:shop-id:change` (see below) |
| **Recovery failed** | a hard error, such as the lock store being unavailable (HTTP `503`) or a missing manifest | fix the cause, then retry |

### Timeline — recoverable (the common case)

| step | what happens |
|---|---|
| 1 | An operator rotates *MyApp*; the new secret is saved as unconfirmed; the confirmation times out. |
| 2 | Re-run `bin/console app:install MyApp`; it signs with the unconfirmed secret, then the previous one, until MyApp accepts. |
| 3 | A fresh secret is committed, and both sides are back **in sync**. |

## When recovery isn't possible — `app:shop-id:change`

The **claimed** outcome means the app trusts none of the secrets Shopware holds, so no re-registration Shopware can sign will be accepted. The usual cause is a **shop clone**:

| step | what happens |
|---|---|
| 1 | Production *Shop A* is cloned to *staging* — the clone copies Shop A's shop ID **and** its app secrets. |
| 2 | Staging rotates MyApp's secret; the app now binds that shop ID to *staging's* new secret. |
| 3 | Shop A re-runs `app:install MyApp`, but the app trusts only staging's secret, so every candidate is rejected. |
| 4 | Recovery reverts cleanly and reports **recovery failed** (claimed), so Shop A runs `bin/console app:shop-id:change` to take a fresh, distinct identity, then re-registers. |

This is genuinely unrecoverable, not a defect: the app keys registration by **shop ID**, and the clone now legitimately owns that ID's secret. No secret Shop A holds can reclaim it — the only correct move is to give Shop A its own identity.

Check warning on line 102 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L102

A determiner cannot be combined with a possessive pronoun. Did you mean simply “A” or “its”? (A_MY[136]) Suggestions: `A`, `Its` Rule: https://community.languagetool.org/rule/show/A_MY?lang=en-US&subId=136 Category: COLLOCATIONS
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:102:175: A determiner cannot be combined with a possessive pronoun. Did you mean simply “A” or “its”? (A_MY[136])
 Suggestions: `A`, `Its`
 Rule: https://community.languagetool.org/rule/show/A_MY?lang=en-US&subId=136
 Category: COLLOCATIONS

::: warning
A cloned shop (for example, a staging instance restored from a production dump) shares the original's shop ID and app secrets. Run `bin/console app:shop-id:change` on the clone on first boot so it takes a distinct identity. See [Creating a staging instance](../../../hosting/installation-updates/creating-a-staging-instance.md).
:::

## Uninstall, reinstall, and the deleted-apps store

Separate from install-based recovery, Shopware carries an app's **committed** secret across an uninstall and reinstall on the same shop: on uninstall it stashes the committed secret in the `deleted_apps` table, and on reinstall it replays it to sign the re-registration. A reinstall therefore succeeds whether or not the app acts on the `app.deleted` (uninstall) webhook:

Check warning on line 110 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L110

The word ‘uninstall’ is not a noun. (A_INSTALL[1]) Suggestions: `uninstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:110:47: The word ‘uninstall’ is not a noun. (A_INSTALL[1])
 Suggestions: `uninstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR

Check warning on line 110 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L110

The word ‘reinstall’ is not a noun. (A_INSTALL[1]) Suggestions: `reinstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:110:225: The word ‘reinstall’ is not a noun. (A_INSTALL[1])
 Suggestions: `reinstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR

Check warning on line 110 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L110

Consider shortening this phrase to just ‘whether’, unless you mean ‘regardless of whether’. (WHETHER[7]) Suggestions: `whether` URL: https://languagetool.org/insights/post/whether-vs-if/#when-to-use-%E2%80%9Cwhether%E2%80%9D Rule: https://community.languagetool.org/rule/show/WHETHER?lang=en-US&subId=7 Category: REDUNDANCY
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:110:254: Consider shortening this phrase to just ‘whether’, unless you mean ‘regardless of whether’. (WHETHER[7])
 Suggestions: `whether`
 URL: https://languagetool.org/insights/post/whether-vs-if/#when-to-use-%E2%80%9Cwhether%E2%80%9D 
 Rule: https://community.languagetool.org/rule/show/WHETHER?lang=en-US&subId=7
 Category: REDUNDANCY

| step | what happens |
|---|---|
| 1 | The app is installed; Shopware and the app share committed secret `S1`. On uninstall, Shopware stashes `S1` in `deleted_apps`. |
| 2a | **The app acts on `app.deleted`** (forgets `S1`): the reinstall is a fresh registration — no prior shop record, so no signature is demanded and the reinstall is accepted. |

Check warning on line 115 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L115

The word ‘reinstall’ is not a noun. (A_INSTALL[1]) Suggestions: `reinstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:115:13: The word ‘reinstall’ is not a noun. (A_INSTALL[1])
 Suggestions: `reinstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR

Check warning on line 115 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L115

The word ‘reinstall’ is not a noun. (A_INSTALL[1]) Suggestions: `reinstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:115:107: The word ‘reinstall’ is not a noun. (A_INSTALL[1])
 Suggestions: `reinstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR
| 2b | **The app ignores `app.deleted`** (still holds `S1`): the reinstall replays `S1`, and the app's double-signature check validates against it, so the reinstall is accepted. Without the stash, Shopware would sign with a secret the app never saw, and the reinstall would be rejected. |

Check warning on line 116 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L116

The word ‘reinstall’ is not a noun. (A_INSTALL[1]) Suggestions: `reinstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:116:15: The word ‘reinstall’ is not a noun. (A_INSTALL[1])
 Suggestions: `reinstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR

Check warning on line 116 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L116

The word ‘reinstall’ is not a noun. (A_INSTALL[1]) Suggestions: `reinstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:116:105: The word ‘reinstall’ is not a noun. (A_INSTALL[1])
 Suggestions: `reinstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR

Check warning on line 116 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L116

The word ‘reinstall’ is not a noun. (A_INSTALL[2]) Suggestions: `reinstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=2 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:116:208: The word ‘reinstall’ is not a noun. (A_INSTALL[2])
 Suggestions: `reinstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=2
 Category: GRAMMAR

Check warning on line 116 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L116

Use a comma before “and” if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[4]) Suggestions: `, and` URL: https://languagetool.org/insights/post/types-of-sentences/#compound-sentence Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=4 Category: PUNCTUATION
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:116:284: Use a comma before “and” if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[4])
 Suggestions: `, and`
 URL: https://languagetool.org/insights/post/types-of-sentences/#compound-sentence 
 Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=4
 Category: PUNCTUATION
| 3 | Either way, a fresh secret is committed and both sides are back in sync. |

This carries only the **committed** secret. An app caught **mid-rotation** holds an *unconfirmed* secret the store never sees:

| step | what happens |
|---|---|
| 1 | A rotation was interrupted; the new secret `S2` is unconfirmed and the app has adopted it. |
| 2 | The operator uninstalls the app. The store keeps only the committed `S1`; the unconfirmed `S2` is discarded. |

Check warning on line 124 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L124

The word ‘reinstall’ is not a noun. (A_INSTALL[1]) Suggestions: `reinstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:124:105: The word ‘reinstall’ is not a noun. (A_INSTALL[1])
 Suggestions: `reinstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR
| 3 | If the app kept its record, the reinstall replays `S1` but the app trusts only `S2`, so the reinstall is rejected and the app is stranded, with no record of `S2` anywhere. |

Check warning on line 125 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L125

The word ‘reinstall’ is not a noun. (A_INSTALL[1]) Suggestions: `reinstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:125:48: The word ‘reinstall’ is not a noun. (A_INSTALL[1])
 Suggestions: `reinstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR

Check warning on line 125 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L125

Use a comma before “and” if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[4]) Suggestions: `, and` URL: https://languagetool.org/insights/post/types-of-sentences/#compound-sentence Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=4 Category: PUNCTUATION
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:125:69: Use a comma before “and” if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[4])
 Suggestions: `, and`
 URL: https://languagetool.org/insights/post/types-of-sentences/#compound-sentence 
 Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=4
 Category: PUNCTUATION

So **recover the live app first** by re-running `app:install`, then uninstall if you still need to.

::: info
The two mechanisms are complementary: the `deleted_apps` store carries the **committed** secret across an uninstall automatically; re-running `app:install` re-syncs the **unconfirmed** secret of an app that is still installed.

Check warning on line 130 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L130

The word ‘uninstall’ is not a noun. (A_INSTALL[1]) Suggestions: `uninstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:130:56: The word ‘uninstall’ is not a noun. (A_INSTALL[1])
 Suggestions: `uninstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=1
 Category: GRAMMAR
:::

## Limits

- **Recover the live app row.** Recover **before** you uninstall or run `app:shop-id:change` on an app that still has an unconfirmed secret. Uninstall discards the unconfirmed secret — only the *committed* secret is remembered for a reinstall — so an app whose only record is unconfirmed cannot be recovered once it is uninstalled.

Check warning on line 135 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L135

The word ‘reinstall’ is not a noun. (A_INSTALL[2]) Suggestions: `reinstallation` Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=2 Category: GRAMMAR
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:135:183: The word ‘reinstall’ is not a noun. (A_INSTALL[2])
 Suggestions: `reinstallation`
 Rule: https://community.languagetool.org/rule/show/A_INSTALL?lang=en-US&subId=2
 Category: GRAMMAR
- **A completed app is credential-repaired only.** Re-running `app:install` on a *fully-installed* app whose rotation broke re-syncs the secret without replaying its lifecycle; a *half-finished* install, by contrast, is resumed and completed by the same command.
- **Cross-instance locking.** Rotation and recovery serialize per app behind a lock. In a multi-server deployment, configure a shared `LOCK_DSN` (Redis or a database DSN) — the default `flock` is per-host and does not serialize across instances.

Check warning on line 137 in guides/plugins/apps/lifecycle/app-secret-recovery.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/apps/lifecycle/app-secret-recovery.md#L137

This word is normally spelled as one. (EN_COMPOUNDS_MULTI_SERVER) Suggestions: `multiserver` Rule: https://community.languagetool.org/rule/show/EN_COMPOUNDS_MULTI_SERVER?lang=en-US Category: MISC
Raw output
guides/plugins/apps/lifecycle/app-secret-recovery.md:137:36: This word is normally spelled as one. (EN_COMPOUNDS_MULTI_SERVER)
 Suggestions: `multiserver`
 Rule: https://community.languagetool.org/rule/show/EN_COMPOUNDS_MULTI_SERVER?lang=en-US
 Category: MISC

## Observability

::: info
These metrics are emitted through Shopware's **telemetry** integration, which is not enabled by default — they are only collected once a telemetry transport is configured. See [OpenTelemetry](../../../hosting/configurations/observability/opentelemetry.md).
:::

A periodic metric reports how many apps are stuck with an unconfirmed secret, alongside per-attempt outcome counters:

| metric | type | meaning |
|---|---|---|
| `app.unconfirmed_app_secrets.count` | gauge | apps currently holding an unconfirmed secret (a "stuck" rotation) |
| `app.registration.failure.count` | counter | registration confirm **failures**, tagged `rejected`, `ambiguous`, or `handshake_failed` |
| `app.secret_recovery.outcome.count` | counter | recovery attempts, tagged `recovered`, `claimed`, or `unknown` |

::: info
A rising `app.unconfirmed_app_secrets.count` means rotations or installs are not confirming. Re-run `bin/console app:install <app-name>` on an affected app to recover it.
:::
6 changes: 6 additions & 0 deletions guides/plugins/apps/lifecycle/app-signature-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ The Symfony Bundle handles all verification automatically.
</Tab>
</Tabs>

## Validating re-registration signatures

When a shop re-registers an app it already knows — to rotate the app secret, or after a shop-URL change — the registration request carries a second signature in addition to the normal one. Alongside the signature made with your app's secret, Shopware adds a signature made with the **previous secret** the app already holds. Your app **must validate both** before accepting the re-registration: the first proves the request is for your app, the second proves it comes from the shop that registered before.

Validate each signature with the same HMAC check used for a normal request, using the matching secret. For when this happens and how to roll the secret over without dropping in-flight requests, see [Secret rotation and shop-url changes](app-registration-setup.md#secret-rotation-and-shop-url-changes).

## Signing responses

Shopware expects a signature in the response to verify that the response is coming from your app server.
Expand Down
3 changes: 2 additions & 1 deletion resources/references/core-reference/commands-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ $ bin/console [command] [parameters]
| `app:install` | Installs the app in the folder with the given name |
| `app:list` | Lists all apps |
| `app:refresh` | \[app:update\] Refreshes the installed apps |
| `app:secret:rotate` | Rotate the shared app secret and integration credentials for one or all apps. |
| `app:shop-id:change` | \[app:url-change:resolve\] Change the shop ID by choosing a resolution strategy. |
| `app:uninstall` | Uninstalls the app |
| `app:url-change:resolve` | Resolves changes in the app URL and how the app system should handle it. |
| `app:validate` | Checks manifests for errors |

### Assets
Expand Down
Loading