Skip to content

Commit 8758601

Browse files
authored
Merge pull request #128 from hypercerts-org/epds-css-injection-docs
docs: document ePDS branding customization
2 parents 0d9a452 + c3c362e commit 8758601

11 files changed

Lines changed: 137 additions & 0 deletions

pages/architecture/epds.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,149 @@ Your client metadata file is a public JSON document served over HTTPS. Its URL i
182182
"background_color": "#ffffff",
183183
"email_template_uri": "https://yourapp.example.com/email-template.html",
184184
"email_subject_template": "{{code}} - Your {{app_name}} code",
185+
"branding": {
186+
"css": "body { background: #0f172a; color: #e2e8f0; }"
187+
},
185188
"epds_handle_mode": "picker-with-random"
186189
}
187190
```
188191

189192
The extra branding fields customize the hosted login and email experience. `epds_handle_mode` sets your preferred handle mode for new users unless you override it on the authorization URL.
190193

194+
## Branding and customization
195+
196+
### How branding works
197+
198+
ePDS reads branding settings from your app's `client-metadata.json`, using the OAuth `client_id` to look it up. Standard metadata fields like `logo_uri`, `brand_color`, `background_color`, `email_template_uri`, and `email_subject_template` customize the hosted login and email experience.
199+
200+
Trusted clients can go further by adding custom CSS in client metadata under `branding.css`:
201+
202+
```json
203+
{
204+
"branding": {
205+
"css": "body { background: #0f172a; color: #e2e8f0; }"
206+
}
207+
}
208+
```
209+
210+
When the client is trusted, ePDS injects that CSS into its hosted auth pages and the stock consent page.
211+
212+
{% callout type="warning" %}
213+
Trust is checked against the exact `client_id`.
214+
215+
The `client_id` you send during OAuth, the `client_id` inside `client-metadata.json`, and the entry in `PDS_OAUTH_TRUSTED_CLIENTS` must all be identical.
216+
217+
For example, if your client metadata says `"client_id": "https://hypercerts-scaffold.vercel.app/client-metadata.json"`, then `PDS_OAUTH_TRUSTED_CLIENTS` must contain `https://hypercerts-scaffold.vercel.app/client-metadata.json` — not just `https://hypercerts-scaffold.vercel.app`. See the [Scaffold Starter App](/tools/scaffold) for a concrete example of a client serving metadata from `/client-metadata.json`.
218+
{% /callout %}
219+
220+
### Client metadata branding fields
221+
222+
These fields are the main branding controls exposed through client metadata:
223+
224+
| Field | What it affects |
225+
|------|------------------|
226+
| `logo_uri` | App logo shown in hosted auth and email flows |
227+
| `brand_color` | Primary brand color used by hosted screens |
228+
| `background_color` | Background color for hosted screens |
229+
| `email_template_uri` | Custom HTML template for OTP emails |
230+
| `email_subject_template` | Subject line template for OTP emails |
231+
| `branding.css` | Custom CSS for trusted clients |
232+
233+
### CSS injection for trusted clients
234+
235+
Custom CSS is only applied for clients whose exact `client_id` appears in `PDS_OAUTH_TRUSTED_CLIENTS`. When present, ePDS injects a `<style>` tag into the rendered page, sanitizes the CSS to prevent `</style>` tag closure, and updates the page's CSP `style-src` directive with a SHA-256 hash for the injected stylesheet.
236+
237+
This gives operators a safety boundary: untrusted clients never get CSS injection, even if their metadata contains branding CSS.
238+
239+
### Where branding appears
240+
241+
The send-OTP and initial-OTP screens are two states of the same auth-service route: `https://auth.epds1.test.certified.app/oauth/authorize`.
242+
243+
| Surface | URL | Supports branding |
244+
|---|---|---|
245+
| Send OTP | `https://auth.epds1.test.certified.app/oauth/authorize` | Metadata fields + trusted-client CSS |
246+
| Initial OTP | `https://auth.epds1.test.certified.app/oauth/authorize` | Metadata fields + trusted-client CSS |
247+
| Choose handle | `https://auth.epds1.test.certified.app/auth/choose-handle` | Metadata fields + trusted-client CSS |
248+
| Recovery | `https://auth.epds1.test.certified.app/auth/recover` | Metadata fields + trusted-client CSS |
249+
| Consent page | `https://epds1.test.certified.app/oauth/authorize` | Trusted-client CSS |
250+
251+
### Examples
252+
253+
#### Send OTP
254+
255+
{% columns %}
256+
{% column %}
257+
Stock
258+
259+
![Stock send OTP screen](/images/epds/send-otp-stock.png)
260+
{% /column %}
261+
{% column %}
262+
CSS injected
263+
264+
![CSS-injected send OTP screen](/images/epds/send-otp-css-injected.png)
265+
{% /column %}
266+
{% /columns %}
267+
268+
#### Initial OTP
269+
270+
{% columns %}
271+
{% column %}
272+
Stock
273+
274+
![Stock initial OTP screen](/images/epds/initial-otp-stock.png)
275+
{% /column %}
276+
{% column %}
277+
CSS injected
278+
279+
![CSS-injected initial OTP screen](/images/epds/initial-otp-css-injected.png)
280+
{% /column %}
281+
{% /columns %}
282+
283+
#### Choose handle
284+
285+
{% columns %}
286+
{% column %}
287+
Stock
288+
289+
![Stock choose handle screen](/images/epds/choose-handle-stock.png)
290+
{% /column %}
291+
{% column %}
292+
CSS injected
293+
294+
![CSS-injected choose handle screen](/images/epds/choose-handle-css-injected.png)
295+
{% /column %}
296+
{% /columns %}
297+
298+
#### Consent page
299+
300+
{% columns %}
301+
{% column %}
302+
Stock
303+
304+
![Stock consent page](/images/epds/consent-page-stock.png)
305+
{% /column %}
306+
{% column %}
307+
CSS injected
308+
309+
![CSS-injected consent page](/images/epds/consent-page-css-injected.png)
310+
{% /column %}
311+
{% /columns %}
312+
313+
#### Recovery
314+
315+
{% columns %}
316+
{% column %}
317+
Stock
318+
319+
![Stock recovery screen](/images/epds/recovery-stock.png)
320+
{% /column %}
321+
{% column %}
322+
CSS injected
323+
324+
![CSS-injected recovery screen](/images/epds/recovery-css-injected.png)
325+
{% /column %}
326+
{% /columns %}
327+
191328
## Further reading
192329

193330
- [Account & Identity Setup](/architecture/account-and-identity)
174 KB
Loading
24.1 KB
Loading
77.5 KB
Loading
76.9 KB
Loading
167 KB
Loading
38.6 KB
Loading
147 KB
Loading
27.2 KB
Loading
166 KB
Loading

0 commit comments

Comments
 (0)