@jjlmoya/utils-developer 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +4 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/jwtDecoder/bibliography.astro +14 -0
  8. package/src/tool/jwtDecoder/bibliography.ts +12 -0
  9. package/src/tool/jwtDecoder/component.astro +209 -0
  10. package/src/tool/jwtDecoder/entry.ts +30 -0
  11. package/src/tool/jwtDecoder/i18n/de.ts +248 -0
  12. package/src/tool/jwtDecoder/i18n/en.ts +247 -0
  13. package/src/tool/jwtDecoder/i18n/es.ts +248 -0
  14. package/src/tool/jwtDecoder/i18n/fr.ts +248 -0
  15. package/src/tool/jwtDecoder/i18n/id.ts +248 -0
  16. package/src/tool/jwtDecoder/i18n/it.ts +248 -0
  17. package/src/tool/jwtDecoder/i18n/ja.ts +248 -0
  18. package/src/tool/jwtDecoder/i18n/ko.ts +248 -0
  19. package/src/tool/jwtDecoder/i18n/nl.ts +247 -0
  20. package/src/tool/jwtDecoder/i18n/pl.ts +247 -0
  21. package/src/tool/jwtDecoder/i18n/pt.ts +247 -0
  22. package/src/tool/jwtDecoder/i18n/ru.ts +248 -0
  23. package/src/tool/jwtDecoder/i18n/sv.ts +248 -0
  24. package/src/tool/jwtDecoder/i18n/tr.ts +248 -0
  25. package/src/tool/jwtDecoder/i18n/zh.ts +248 -0
  26. package/src/tool/jwtDecoder/index.ts +11 -0
  27. package/src/tool/jwtDecoder/jwt-decoder-parser-and-claims-inspector.css +358 -0
  28. package/src/tool/jwtDecoder/logic.ts +96 -0
  29. package/src/tool/jwtDecoder/seo.astro +15 -0
  30. package/src/tool/jwtDecoder/ui.ts +38 -0
  31. package/src/tool/serpPixelSimulator/bibliography.astro +11 -4
  32. package/src/tool/serpPixelSimulator/index.ts +0 -4
  33. package/src/tool/serpPixelSimulator/seo.astro +8 -5
  34. package/src/tools.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-developer",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -20,10 +20,11 @@ import { svgSanitizer } from '../tool/svgSanitizer/index';
20
20
  import { utmGenerator } from '../tool/utmGenerator/index';
21
21
  import { urlCleaner } from '../tool/urlCleaner/index';
22
22
  import { serpPixelSimulator } from '../tool/serpPixelSimulator/index';
23
+ import { jwtDecoder } from '../tool/jwtDecoder/index';
23
24
 
24
25
  export const developerCategory: DeveloperCategoryEntry = {
25
26
  icon: 'mdi:code-tags',
26
- tools: [jsonFormatter, svgToCss, aspectRatio, placeholderGenerator, urlEncoderDecoder, duplicateCssRemover, cssToInlineConverter, cssSpecificityCalculator, cronGenerator, keycode, llmCostCalculator, musicalTypography, mobileMockupGenerator, hashGenerator, promptLibrary, colorConverter, readabilityCalculator, svgSanitizer, utmGenerator, urlCleaner, serpPixelSimulator],
27
+ tools: [jsonFormatter, svgToCss, aspectRatio, placeholderGenerator, urlEncoderDecoder, duplicateCssRemover, cssToInlineConverter, cssSpecificityCalculator, cronGenerator, keycode, llmCostCalculator, musicalTypography, mobileMockupGenerator, hashGenerator, promptLibrary, colorConverter, readabilityCalculator, svgSanitizer, utmGenerator, urlCleaner, serpPixelSimulator, jwtDecoder],
27
28
  i18n: {
28
29
  de: () => import('./i18n/de').then((m) => m.content),
29
30
  en: () => import('./i18n/en').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -48,6 +48,8 @@ export { utmGenerator } from './tool/utmGenerator/entry';
48
48
  export type { UtmGeneratorLocaleContent } from './tool/utmGenerator/entry';
49
49
  export { serpPixelSimulator } from './tool/serpPixelSimulator/entry';
50
50
  export type { SerpPixelSimulatorLocaleContent } from './tool/serpPixelSimulator/entry';
51
+ export { jwtDecoder } from './tool/jwtDecoder/entry';
52
+ export type { JwtDecoderLocaleContent } from './tool/jwtDecoder/entry';
51
53
  export { developerCategory } from './category';
52
54
  import { aspectRatio } from './tool/aspectRatio/entry';
53
55
  import { calculadoraTiempoDatos } from './tool/calculadoraTiempoDatos/entry';
@@ -74,4 +76,5 @@ import { urlCleaner } from './tool/urlCleaner/entry';
74
76
  import { urlEncoderDecoder } from './tool/urlEncoderDecoder/entry';
75
77
  import { utmGenerator } from './tool/utmGenerator/entry';
76
78
  import { serpPixelSimulator } from './tool/serpPixelSimulator/entry';
77
- export const ALL_ENTRIES = [aspectRatio, calculadoraTiempoDatos, colorConverter, conversorExcelCsvHtml, cronGenerator, cssSpecificityCalculator, cssToInlineConverter, duplicateCssRemover, generadorSecurityTxt, hashGenerator, inspectorCertificadosSsl, jsonFormatter, keycode, llmCostCalculator, mobileMockupGenerator, musicalTypography, placeholderGenerator, promptLibrary, readabilityCalculator, svgSanitizer, svgToCss, urlCleaner, urlEncoderDecoder, utmGenerator, serpPixelSimulator];
79
+ import { jwtDecoder } from './tool/jwtDecoder/entry';
80
+ export const ALL_ENTRIES = [aspectRatio, calculadoraTiempoDatos, colorConverter, conversorExcelCsvHtml, cronGenerator, cssSpecificityCalculator, cssToInlineConverter, duplicateCssRemover, generadorSecurityTxt, hashGenerator, inspectorCertificadosSsl, jsonFormatter, keycode, llmCostCalculator, mobileMockupGenerator, musicalTypography, placeholderGenerator, promptLibrary, readabilityCalculator, svgSanitizer, svgToCss, urlCleaner, urlEncoderDecoder, utmGenerator, serpPixelSimulator, jwtDecoder];
package/src/index.ts CHANGED
@@ -66,3 +66,5 @@ export { INSPECTOR_CERTIFICADOS_SSL_TOOL } from './tool/inspectorCertificadosSsl
66
66
  export { CONVERSOR_EXCEL_CSV_HTML_TOOL } from './tool/conversorExcelCsvHtml/index';
67
67
 
68
68
  export { SERP_PIXEL_SIMULATOR_TOOL } from './tool/serpPixelSimulator/index';
69
+
70
+ export { JWT_DECODER_TOOL } from './tool/jwtDecoder/index';
@@ -35,7 +35,7 @@ describe('Locale Completeness Validation', () => {
35
35
  });
36
36
  });
37
37
 
38
- it('all 25 tools registered', () => {
39
- expect(ALL_TOOLS.length).toBe(25);
38
+ it('all 26 tools registered', () => {
39
+ expect(ALL_TOOLS.length).toBe(26);
40
40
  });
41
41
  });
@@ -4,8 +4,8 @@ import { developerCategory } from '../data';
4
4
 
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
- it('should have 25 tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(25);
7
+ it('should have 26 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(26);
9
9
  });
10
10
 
11
11
  it('developerCategory should be defined', () => {
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { jwtDecoder } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await jwtDecoder.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,12 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'IETF: JSON Web Token (JWT), RFC 7519',
6
+ url: 'https://datatracker.ietf.org/doc/html/rfc7519',
7
+ },
8
+ {
9
+ name: 'IETF: JSON Web Signature (JWS), RFC 7515',
10
+ url: 'https://datatracker.ietf.org/doc/html/rfc7515',
11
+ }
12
+ ];
@@ -0,0 +1,209 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { JwtDecoderUI } from './ui';
4
+ import './jwt-decoder-parser-and-claims-inspector.css';
5
+
6
+ interface Props {
7
+ ui?: Record<string, unknown>;
8
+ }
9
+
10
+ const { ui } = Astro.props;
11
+ const t = (ui ?? {}) as JwtDecoderUI;
12
+ ---
13
+
14
+ <div id="jwt-root" class="jwt-root" data-i18n={JSON.stringify(t)}>
15
+ <section class="jwt-card">
16
+ <div class="jwt-input-panel">
17
+ <div class="jwt-token-field">
18
+ <label for="jwt-token" class="jwt-label">
19
+ <Icon name="mdi:key-variant" class="jwt-label-icon" />
20
+ {t.tokenLabel}
21
+ </label>
22
+ <textarea id="jwt-token" class="jwt-token-input" spellcheck="false" placeholder={t.tokenPlaceholder}>{t.sampleToken}</textarea>
23
+ </div>
24
+
25
+ <div class="jwt-action-row">
26
+ <button id="jwt-sample" class="jwt-action jwt-action-primary" type="button">
27
+ <Icon name="mdi:test-tube" class="jwt-button-icon" />
28
+ {t.sampleButton}
29
+ </button>
30
+ <button id="jwt-clear" class="jwt-action" type="button">
31
+ <Icon name="mdi:eraser" class="jwt-button-icon" />
32
+ {t.clearButton}
33
+ </button>
34
+ <button id="jwt-copy-all" class="jwt-action" type="button">
35
+ <Icon name="mdi:content-copy" class="jwt-button-icon" />
36
+ {t.copyAll}
37
+ </button>
38
+ </div>
39
+
40
+ <p id="jwt-status" class="jwt-status jwt-status-waiting" aria-live="polite">{t.statusWaiting}</p>
41
+ <p class="jwt-privacy">{t.privacyNote}</p>
42
+ </div>
43
+
44
+ <div class="jwt-output-grid">
45
+ <article class="jwt-json-panel">
46
+ <div class="jwt-panel-heading">
47
+ <h2>{t.headerTitle}</h2>
48
+ <button class="jwt-copy" type="button" data-copy-target="jwt-header-output" title={t.copyHeader} aria-label={t.copyHeader}>
49
+ <Icon name="mdi:content-copy" />
50
+ </button>
51
+ </div>
52
+ <pre id="jwt-header-output" class="jwt-code">{t.emptyJson}</pre>
53
+ </article>
54
+
55
+ <article class="jwt-json-panel jwt-json-panel-main">
56
+ <div class="jwt-panel-heading">
57
+ <h2>{t.payloadTitle}</h2>
58
+ <button class="jwt-copy" type="button" data-copy-target="jwt-payload-output" title={t.copyPayload} aria-label={t.copyPayload}>
59
+ <Icon name="mdi:content-copy" />
60
+ </button>
61
+ </div>
62
+ <pre id="jwt-payload-output" class="jwt-code">{t.emptyJson}</pre>
63
+ </article>
64
+
65
+ <article class="jwt-json-panel">
66
+ <div class="jwt-panel-heading">
67
+ <h2>{t.signatureTitle}</h2>
68
+ <button class="jwt-copy" type="button" data-copy-target="jwt-signature-output" title={t.copySignature} aria-label={t.copySignature}>
69
+ <Icon name="mdi:content-copy" />
70
+ </button>
71
+ </div>
72
+ <pre id="jwt-signature-output" class="jwt-code jwt-signature-code">{t.signatureMissing}</pre>
73
+ </article>
74
+ </div>
75
+
76
+ <aside class="jwt-claims-panel" aria-label={t.claimsTitle}>
77
+ <h2>{t.claimsTitle}</h2>
78
+ <dl class="jwt-claims">
79
+ <div><dt>{t.algorithmLabel}</dt><dd id="jwt-claim-alg">{t.claimMissing}</dd></div>
80
+ <div><dt>{t.typeLabel}</dt><dd id="jwt-claim-typ">{t.claimMissing}</dd></div>
81
+ <div><dt>{t.issuerLabel}</dt><dd id="jwt-claim-iss">{t.claimMissing}</dd></div>
82
+ <div><dt>{t.subjectLabel}</dt><dd id="jwt-claim-sub">{t.claimMissing}</dd></div>
83
+ <div><dt>{t.audienceLabel}</dt><dd id="jwt-claim-aud">{t.claimMissing}</dd></div>
84
+ <div><dt>{t.issuedAtLabel}</dt><dd id="jwt-claim-iat">{t.claimMissing}</dd></div>
85
+ <div><dt>{t.notBeforeLabel}</dt><dd id="jwt-claim-nbf">{t.claimMissing}</dd></div>
86
+ <div><dt>{t.expiresAtLabel}</dt><dd id="jwt-claim-exp">{t.claimMissing}</dd></div>
87
+ </dl>
88
+ </aside>
89
+ </section>
90
+ </div>
91
+
92
+ <script>
93
+ import { decodeJwt, formatClaimDate, formatClaimValue, formatJson } from './logic';
94
+ import type { JwtDecoderUI } from './ui';
95
+
96
+ const root = document.getElementById('jwt-root') as HTMLElement;
97
+ const t = JSON.parse(root.dataset.i18n ?? '{}') as JwtDecoderUI;
98
+ const tokenInput = document.getElementById('jwt-token') as HTMLTextAreaElement;
99
+ const headerOutput = document.getElementById('jwt-header-output') as HTMLElement;
100
+ const payloadOutput = document.getElementById('jwt-payload-output') as HTMLElement;
101
+ const signatureOutput = document.getElementById('jwt-signature-output') as HTMLElement;
102
+ const status = document.getElementById('jwt-status') as HTMLElement;
103
+ const claims = {
104
+ alg: document.getElementById('jwt-claim-alg'),
105
+ typ: document.getElementById('jwt-claim-typ'),
106
+ iss: document.getElementById('jwt-claim-iss'),
107
+ sub: document.getElementById('jwt-claim-sub'),
108
+ aud: document.getElementById('jwt-claim-aud'),
109
+ iat: document.getElementById('jwt-claim-iat'),
110
+ nbf: document.getElementById('jwt-claim-nbf'),
111
+ exp: document.getElementById('jwt-claim-exp'),
112
+ };
113
+
114
+ function setStatus(text: string, tone: string) {
115
+ status.textContent = text;
116
+ status.className = `jwt-status jwt-status-${tone}`;
117
+ }
118
+
119
+ function setClaim(key: keyof typeof claims, value: string | null) {
120
+ const element = claims[key];
121
+ if (element) element.textContent = value ?? t.claimMissing;
122
+ }
123
+
124
+ function resetOutputs() {
125
+ headerOutput.textContent = t.emptyJson;
126
+ payloadOutput.textContent = t.emptyJson;
127
+ signatureOutput.textContent = t.signatureMissing;
128
+ Object.keys(claims).forEach((key) => setClaim(key as keyof typeof claims, null));
129
+ setStatus(t.statusWaiting, 'waiting');
130
+ }
131
+
132
+ function renderDecodedClaims(decoded: ReturnType<typeof decodeJwt>) {
133
+ headerOutput.textContent = formatJson(decoded.header);
134
+ payloadOutput.textContent = formatJson(decoded.payload);
135
+ signatureOutput.textContent = decoded.signature ? decoded.signature : t.signatureMissing;
136
+ setClaim('alg', formatClaimValue(decoded.header['alg']));
137
+ setClaim('typ', formatClaimValue(decoded.header['typ']));
138
+ setClaim('iss', formatClaimValue(decoded.payload['iss']));
139
+ setClaim('sub', formatClaimValue(decoded.payload['sub']));
140
+ setClaim('aud', formatClaimValue(decoded.payload['aud']));
141
+ setClaim('iat', formatClaimDate(decoded.payload['iat']) ?? formatClaimValue(decoded.payload['iat']));
142
+ setClaim('nbf', formatClaimDate(decoded.payload['nbf']) ?? formatClaimValue(decoded.payload['nbf']));
143
+ setClaim('exp', formatClaimDate(decoded.payload['exp']) ?? formatClaimValue(decoded.payload['exp']));
144
+ }
145
+
146
+ function renderDecodedStatus(decoded: ReturnType<typeof decodeJwt>) {
147
+ if (decoded.expired) {
148
+ setStatus(t.statusExpired, 'expired');
149
+ } else if (decoded.unsigned) {
150
+ setStatus(t.statusUnsigned, 'unsigned');
151
+ } else {
152
+ setStatus(`${t.statusValid} ${t.signaturePresent}`, 'valid');
153
+ }
154
+ }
155
+
156
+ function renderInvalidToken(error: ReturnType<typeof decodeJwt>['error']) {
157
+ resetOutputs();
158
+ headerOutput.textContent = t.invalidTokenTitle;
159
+ payloadOutput.textContent = error === 'invalid-segments' ? t.invalidSegmentError : t.invalidDecodeError;
160
+ setStatus(t.statusInvalid, 'invalid');
161
+ }
162
+
163
+ function updateDecoder() {
164
+ const token = tokenInput.value.trim();
165
+ if (!token) {
166
+ resetOutputs();
167
+ return;
168
+ }
169
+
170
+ const decoded = decodeJwt(token);
171
+ if (!decoded.valid) {
172
+ renderInvalidToken(decoded.error);
173
+ return;
174
+ }
175
+
176
+ renderDecodedClaims(decoded);
177
+ renderDecodedStatus(decoded);
178
+ }
179
+
180
+ async function copyText(text: string) {
181
+ if (!text || text === t.emptyJson || text === t.signatureMissing) return;
182
+ await navigator.clipboard.writeText(text);
183
+ }
184
+
185
+ tokenInput.addEventListener('input', updateDecoder);
186
+ document.getElementById('jwt-sample')?.addEventListener('click', () => {
187
+ tokenInput.value = t.sampleToken;
188
+ updateDecoder();
189
+ });
190
+ document.getElementById('jwt-clear')?.addEventListener('click', () => {
191
+ tokenInput.value = '';
192
+ resetOutputs();
193
+ });
194
+ document.getElementById('jwt-copy-all')?.addEventListener('click', () => {
195
+ copyText([headerOutput.textContent, payloadOutput.textContent, signatureOutput.textContent].join('\n\n'));
196
+ });
197
+ document.querySelectorAll<HTMLButtonElement>('.jwt-copy').forEach((button) => {
198
+ button.addEventListener('click', () => {
199
+ const target = document.getElementById(button.dataset.copyTarget ?? '');
200
+ copyText(target?.textContent ?? '');
201
+ button.dataset.copied = t.copiedLabel;
202
+ window.setTimeout(() => {
203
+ delete button.dataset.copied;
204
+ }, 1400);
205
+ });
206
+ });
207
+
208
+ updateDecoder();
209
+ </script>
@@ -0,0 +1,30 @@
1
+ import type { DeveloperToolEntry, ToolLocaleContent } from '../../types';
2
+
3
+ import type { JwtDecoderUI } from './ui';
4
+
5
+ export type JwtDecoderLocaleContent = ToolLocaleContent<JwtDecoderUI>;
6
+
7
+ export const jwtDecoder: DeveloperToolEntry<JwtDecoderUI> = {
8
+ id: 'jwt-decoder-parser-and-claims-inspector',
9
+ icons: {
10
+ bg: 'mdi:key-chain',
11
+ fg: 'mdi:code-json',
12
+ },
13
+ i18n: {
14
+ de: async () => (await import('./i18n/de')).content,
15
+ en: async () => (await import('./i18n/en')).content,
16
+ es: async () => (await import('./i18n/es')).content,
17
+ fr: async () => (await import('./i18n/fr')).content,
18
+ id: async () => (await import('./i18n/id')).content,
19
+ it: async () => (await import('./i18n/it')).content,
20
+ ja: async () => (await import('./i18n/ja')).content,
21
+ ko: async () => (await import('./i18n/ko')).content,
22
+ nl: async () => (await import('./i18n/nl')).content,
23
+ pl: async () => (await import('./i18n/pl')).content,
24
+ pt: async () => (await import('./i18n/pt')).content,
25
+ ru: async () => (await import('./i18n/ru')).content,
26
+ sv: async () => (await import('./i18n/sv')).content,
27
+ tr: async () => (await import('./i18n/tr')).content,
28
+ zh: async () => (await import('./i18n/zh')).content,
29
+ },
30
+ };
@@ -0,0 +1,248 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { JwtDecoderUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'jwt-decoder-parser-und-claims-inspektor';
7
+ const title = 'JWT Decoder, Parser und Claims Inspektor';
8
+ const description = 'Fügen Sie ein JSON Web Token ein, dekodieren Sie dessen Header und Payload sofort, inspizieren Sie registrierte Claims, erkennen Sie abgelaufene Tokens und kopieren Sie sauberes JSON zum Debuggen von Authentifizierungsabläufen.';
9
+
10
+ const howTo = [
11
+ {
12
+ name: 'Fügen Sie das JWT ein',
13
+ text: 'Kopieren Sie ein Token aus einem Authorization-Header, Cookie, Log-Eintrag oder Identitätsanbieter und fügen Sie es in das Eingabefeld ein.',
14
+ },
15
+ {
16
+ name: 'Lesen Sie den dekodierten Header und Payload',
17
+ text: 'Das Tool teilt das Token in Header, Payload und Signatur auf und stellt die JSON-Segmente in separaten Panels zur schnellen Inspektion dar.',
18
+ },
19
+ {
20
+ name: 'Überprüfen Sie wichtige Claims',
21
+ text: 'Prüfen Sie Algorithmus, Aussteller, Audience, Subjekt, Ausstellungszeit, Gültigkeitsbeginn und Ablaufzeit, ohne Unix-Timestamps manuell umzurechnen.',
22
+ },
23
+ {
24
+ name: 'Kopieren Sie die benötigten Daten',
25
+ text: 'Kopieren Sie einen dekodierten Abschnitt oder die gesamte dekodierte Ausgabe, wenn Sie eine bereinigte Debugging-Momentaufnahme mit Ihrem Team teilen müssen.',
26
+ },
27
+ ];
28
+
29
+ const faq = [
30
+ {
31
+ question: 'Beweist die Dekodierung eines JWT, dass das Token gültig ist?',
32
+ answer: 'Nein. Die Dekodierung zeigt nur den base64url-kodierten Header und Payload. Ein Token ist erst vertrauenswürdig, nachdem die Signatur, der Aussteller, die Audience, der Ablauf und zugehörige Claims von der Anwendung oder dem Identitätsanbieter validiert wurden.',
33
+ },
34
+ {
35
+ question: 'Kann ich diesen JWT-Decoder für Access-Tokens und ID-Tokens verwenden?',
36
+ answer: 'Ja. Der Decoder eignet sich zur Inspektion von OAuth-Access-Tokens, OpenID-Connect-ID-Tokens, Session-Tokens und Service-zu-Service-Tokens, sofern sie das standardmäßige dreiteilige JWT-Format verwenden.',
37
+ },
38
+ {
39
+ question: 'Warum verifiziert das Signatur-Panel das Token nicht?',
40
+ answer: 'Die JWT-Verifizierung erfordert das korrekte Secret, den öffentlichen Schlüssel oder die JWKS-Konfiguration. Dieses Tool konzentriert sich bewusst auf Dekodierung und Inspektion, damit Entwickler den Token-Inhalt einsehen können, ohne vorzutäuschen, dass eine sichtbare Signatur-Zeichenkette ein Gültigkeitsnachweis ist.',
41
+ },
42
+ {
43
+ question: 'Was sollte ich zuerst prüfen, wenn ich ein JWT debugge?',
44
+ answer: 'Beginnen Sie mit exp, nbf, iss, aud und alg. Die meisten echten Produktionsprobleme stammen von abgelaufenen Tokens, Uhrenabweichungen, falschen Audience-Werten, unerwarteten Aussteller-URLs oder unsicheren Algorithmus-Annahmen.',
45
+ },
46
+ ];
47
+
48
+ const ui: JwtDecoderUI = {
49
+ tokenLabel: 'JWT-Token',
50
+ tokenPlaceholder: 'JWT hier einfügen: Header.Payload.Signatur',
51
+ sampleButton: 'Beispiel laden',
52
+ clearButton: 'Löschen',
53
+ statusWaiting: 'Fügen Sie ein Token ein, um dessen JSON-Header, Payload und Claims zu dekodieren.',
54
+ statusValid: 'JWT erfolgreich dekodiert.',
55
+ statusInvalid: 'Dies sieht nicht nach einem gültigen dreiteiligen JWT aus.',
56
+ statusExpired: 'JWT dekodiert, aber der exp-Claim ist bereits abgelaufen.',
57
+ statusUnsigned: 'JWT dekodiert, aber es ist nicht signiert oder verwendet den Algorithmus none.',
58
+ headerTitle: 'Header',
59
+ payloadTitle: 'Payload',
60
+ signatureTitle: 'Signatur',
61
+ claimsTitle: 'Registrierte Claims',
62
+ copyHeader: 'Dekodierten Header kopieren',
63
+ copyPayload: 'Dekodierten Payload kopieren',
64
+ copySignature: 'Signatur kopieren',
65
+ copyAll: 'Alles kopieren',
66
+ copiedLabel: 'Kopiert',
67
+ invalidTokenTitle: 'Ungültiges JWT',
68
+ invalidTokenBody: 'Prüfen Sie, ob das Token drei durch Punkte getrennte base64url-Segmente hat.',
69
+ invalidSegmentError: 'Prüfen Sie, ob das Token drei durch Punkte getrennte base64url-Segmente hat.',
70
+ invalidDecodeError: 'Der Header oder Payload konnte nicht als gültiges JSON dekodiert werden.',
71
+ emptyJson: '{}',
72
+ signaturePresent: 'Signatursegment ist vorhanden; verifizieren Sie es in Ihrer Authentifizierungsschicht mit dem richtigen Schlüssel.',
73
+ signatureMissing: 'Kein Signatursegment',
74
+ algorithmLabel: 'Algorithmus',
75
+ typeLabel: 'Typ',
76
+ issuerLabel: 'Aussteller',
77
+ subjectLabel: 'Subjekt',
78
+ audienceLabel: 'Audience',
79
+ issuedAtLabel: 'Ausgestellt am',
80
+ notBeforeLabel: 'Gültig ab',
81
+ expiresAtLabel: 'Läuft ab am',
82
+ claimMissing: 'Nicht vorhanden',
83
+ privacyNote: 'Die Dekodierung läuft in Ihrer Browser-Sitzung. Fügen Sie keine Produktionsgeheimnisse in ein Tool ein, es sei denn, Ihre Sicherheitsrichtlinie erlaubt dies.',
84
+ sampleToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJnYW1lYm9iLXVzZXItNDIiLCJuYW1lIjoiR2FtZUJvYiBEZXZlbG9wZXIiLCJpc3MiOiJodHRwczovL3d3dy5nYW1lYm9iLmRldiIsImF1ZCI6ImRldmVsb3Blci10b29scyIsImlhdCI6MTcxNzIwMDAwMCwibmJmIjoxNzE3MjAwMDAwLCJleHAiOjE4OTM0NTYwMDAsInJvbGUiOiJhZG1pbiJ9.demo-signature',
85
+ };
86
+
87
+ const faqSchema: WithContext<FAQPage> = {
88
+ '@context': 'https://schema.org',
89
+ '@type': 'FAQPage',
90
+ mainEntity: faq.map((item) => ({
91
+ '@type': 'Question',
92
+ name: item.question,
93
+ acceptedAnswer: {
94
+ '@type': 'Answer',
95
+ text: item.answer,
96
+ },
97
+ })),
98
+ };
99
+
100
+ const howToSchema: WithContext<HowTo> = {
101
+ '@context': 'https://schema.org',
102
+ '@type': 'HowTo',
103
+ name: title,
104
+ description,
105
+ step: howTo.map((step, index) => ({
106
+ '@type': 'HowToStep',
107
+ position: index + 1,
108
+ name: step.name,
109
+ text: step.text,
110
+ })),
111
+ };
112
+
113
+ const appSchema: WithContext<SoftwareApplication> = {
114
+ '@context': 'https://schema.org',
115
+ '@type': 'SoftwareApplication',
116
+ name: title,
117
+ description,
118
+ applicationCategory: 'DeveloperApplication',
119
+ operatingSystem: 'Any',
120
+ offers: {
121
+ '@type': 'Offer',
122
+ price: '0',
123
+ priceCurrency: 'EUR',
124
+ },
125
+ inLanguage: 'de',
126
+ };
127
+
128
+ export const content: ToolLocaleContent<JwtDecoderUI> = {
129
+ slug,
130
+ title,
131
+ description,
132
+ ui,
133
+ faqTitle: 'Häufig gestellte Fragen zum JWT-Decoder',
134
+ faq,
135
+ bibliographyTitle: 'JWT-Spezifikationen und Sicherheitsreferenzen',
136
+ bibliography,
137
+ howTo,
138
+ schemas: [appSchema, faqSchema, howToSchema],
139
+ seo: [
140
+ {
141
+ type: 'title',
142
+ text: 'JWTs dekodieren, ohne den Sicherheitskontext zu verlieren',
143
+ level: 2,
144
+ },
145
+ {
146
+ type: 'paragraph',
147
+ html: 'Ein JSON Web Token wirkt kompakt, enthält aber oft genau das Detail, das einen Authentifizierungsfehler erklärt: den Signaturalgorithmus, den Aussteller, die Audience, das Subjekt, die Ausstellungszeit, die Gültigkeitsbeginnzeit, den Ablauf und anwendungsspezifische Autorisierungs-Claims. Dieser <strong>JWT-Decoder, Parser und Claims-Inspektor</strong> wandelt die drei Token-Segmente in lesbares JSON um, damit Sie Authentifizierungsabläufe schneller debuggen können.',
148
+ },
149
+ {
150
+ type: 'diagnostic',
151
+ variant: 'warning',
152
+ title: 'Dekodiert bedeutet nicht vertrauenswürdig',
153
+ html: 'Jeder kann ein JWT base64url-dekodieren. Vertrauen beginnt erst, nachdem Ihre Anwendung die Signatur mit dem korrekten Secret, öffentlichen Schlüssel oder JWKS verifiziert und dann Aussteller, Audience, Ablauf und alle domainspezifischen Claims validiert hat. Verwenden Sie dieses Tool zur Dateninspektion, nicht um ein Token als authentisch zu akzeptieren.',
154
+ },
155
+ {
156
+ type: 'title',
157
+ text: 'Was Ihnen jedes JWT-Segment verrät',
158
+ level: 3,
159
+ },
160
+ {
161
+ type: 'table',
162
+ headers: ['Segment', 'Typischer Inhalt', 'Debugging-Wert'],
163
+ rows: [
164
+ ['Header', 'Algorithmus, Tokentyp und optionale Schlüssel-ID', 'Zeigt, ob das Token HS256, RS256, ES256 oder eine andere Verifikationsstrategie erwartet.'],
165
+ ['Payload', 'Registrierte Claims und Anwendungs-Claims', 'Enthüllt Identität, Tenant, Scopes, Rollen, Ablauf und Audience-Abweichungen.'],
166
+ ['Signatur', 'Kryptografische Signatur-Bytes als base64url kodiert', 'Bestätigt, dass ein Signatursegment existiert, muss aber mit dem richtigen Schlüssel anderweitig verifiziert werden.'],
167
+ ],
168
+ },
169
+ {
170
+ type: 'title',
171
+ text: 'Claims, die meistens fehlerhafte Authentifizierung erklären',
172
+ level: 3,
173
+ },
174
+ {
175
+ type: 'list',
176
+ items: [
177
+ '<strong>exp:</strong> Wenn das Token abgelaufen ist, könnte die Aktualisierungslogik oder die Uhreneinstellung falsch sein.',
178
+ '<strong>nbf:</strong> Wenn das Token noch nicht aktiv ist, könnten die Uhren von Server und Identitätsanbieter nicht synchron sein.',
179
+ '<strong>iss:</strong> Wenn die Aussteller-URL von der Konfiguration abweicht, stammt das Token möglicherweise vom falschen Tenant oder aus der falschen Umgebung.',
180
+ '<strong>aud:</strong> Wenn die Audience nicht mit der API-Kennung übereinstimmt, wurde das Token für eine andere Ressource ausgestellt.',
181
+ '<strong>alg:</strong> Wenn der Algorithmus unerwartet ist, könnte Ihr Verifizierer das Token ablehnen oder einen gefährlichen Konfigurationsfehler aufdecken.',
182
+ ],
183
+ },
184
+ {
185
+ type: 'title',
186
+ text: 'Anwendungsfälle für einen JWT-Parser während der Entwicklung',
187
+ level: 3,
188
+ },
189
+ {
190
+ type: 'comparative',
191
+ columns: 3,
192
+ items: [
193
+ {
194
+ title: 'Frontend Debugging',
195
+ description: 'Inspizieren Sie ID-Tokens und Access-Tokens, die nach dem Login empfangen wurden, um Scopes, Rollen und Profil-Claims zu bestätigen.',
196
+ icon: 'mdi:monitor-dashboard',
197
+ points: ['Profil-Claims prüfen', 'Scopes und Rollen bestätigen', 'Login-Umgebungen vergleichen'],
198
+ },
199
+ {
200
+ title: 'Backend API QA',
201
+ description: 'Vergleichen Sie erwartete Aussteller- und Audience-Werte mit dem tatsächlich in einem Authorization-Header gesendeten Token.',
202
+ icon: 'mdi:api',
203
+ highlight: true,
204
+ points: ['Audience-Form validieren', 'Aussteller-Abweichungen erkennen', 'Bearer-Tokens inspizieren'],
205
+ },
206
+ {
207
+ title: 'Identitätsanbieter Einrichtung',
208
+ description: 'Prüfen Sie, ob Claims von Auth0, Azure AD, Cognito, Keycloak oder einem benutzerdefinierten Anbieter so geformt sind, wie Ihre App es erwartet.',
209
+ icon: 'mdi:account-key',
210
+ points: ['Tenant-Daten überprüfen', 'Benutzerdefinierte Claims prüfen', 'Anbieter-Zuordnungen vergleichen'],
211
+ },
212
+ ],
213
+ },
214
+ {
215
+ type: 'title',
216
+ text: 'Häufige JWT-Fehler, die dieser Inspektor sichtbar macht',
217
+ level: 3,
218
+ },
219
+ {
220
+ type: 'proscons',
221
+ title: 'Schnelle Prüfungen gegen Vertrauensentscheidungen',
222
+ items: [
223
+ {
224
+ pro: 'Sehen Sie fehlerhafte Tokens sofort.',
225
+ con: 'Er kann Ihre erwartete Audience oder Ihren Aussteller nicht kennen.',
226
+ },
227
+ {
228
+ pro: 'Konvertieren Sie Unix-Timestamp-Claims in lesbare Datumsangaben.',
229
+ con: 'Er kann eine Signatur nicht ohne das echte Schlüsselmaterial verifizieren.',
230
+ },
231
+ {
232
+ pro: 'Erkennen Sie fehlende Aussteller-, Audience-, Subjekt- oder Typ-Werte.',
233
+ con: 'Er kann nicht beweisen, dass Scopes und Rollen für Ihre Anwendung sicher sind.',
234
+ },
235
+ ],
236
+ },
237
+ {
238
+ type: 'summary',
239
+ title: 'Empfohlener Workflow',
240
+ items: [
241
+ 'Dekodieren Sie das Token, um zu verstehen, was der Client oder die API tatsächlich erhalten hat.',
242
+ 'Prüfen Sie exp, nbf, iss, aud, sub und alg, bevor Sie der Anwendungslogik nachgehen.',
243
+ 'Verifizieren Sie Signaturen und Vertrauensentscheidungen nur in Ihrer Authentifizierungsschicht.',
244
+ 'Vermeiden Sie die Weitergabe sensibler Produktions-JWTs in Tickets, Logs oder Screenshots.',
245
+ ],
246
+ },
247
+ ],
248
+ };