Skip to content

Commit cfd9d6f

Browse files
authored
Merge pull request #277 from devvsakib/development
JWT Decoder added and layout issue fixed
2 parents 4ddef9c + 2462889 commit cfd9d6f

File tree

6 files changed

+187
-4
lines changed

6 files changed

+187
-4
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"classnames": "^2.5.1",
1818
"framer-motion": "^10.8.3",
1919
"lodash": "^4.17.21",
20+
"lucide-react": "^0.544.0",
2021
"prettier": "^3.3.3",
2122
"react": "^18.2.0",
2223
"react-dom": "^18.2.0",
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import React, { useState } from "react";
2+
3+
export function base64UrlToBase64(base64url) {
4+
if (typeof base64url !== "string") throw new Error("Invalid input for base64UrlToBase64");
5+
let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
6+
const pad = base64.length % 4;
7+
if (pad === 2) base64 += "==";
8+
else if (pad === 3) base64 += "=";
9+
else if (pad === 1) throw new Error("Invalid base64url string");
10+
return base64;
11+
}
12+
13+
export function decodeBase64UrlJson(input) {
14+
const b64 = base64UrlToBase64(input);
15+
try {
16+
const decoded = atob(b64);
17+
try {
18+
const percentDecoded = decodeURIComponent(
19+
decoded
20+
.split("")
21+
.map((c) => {
22+
const code = c.charCodeAt(0).toString(16).padStart(2, "0");
23+
return `%${code}`;
24+
})
25+
.join("")
26+
);
27+
return JSON.parse(percentDecoded);
28+
} catch (e) {
29+
return JSON.parse(decoded);
30+
}
31+
} catch (err) {
32+
throw new Error("Failed to decode base64url JSON: " + (err && err.message));
33+
}
34+
}
35+
36+
export function parseJWT(token) {
37+
if (typeof token !== "string") throw new Error("Token must be a string");
38+
const parts = token.trim().split(".");
39+
return {
40+
parts,
41+
header: parts[0] ? decodeBase64UrlJson(parts[0]) : null,
42+
payload: parts[1] ? decodeBase64UrlJson(parts[1]) : null,
43+
signature: parts[2] || null,
44+
};
45+
}
46+
47+
export default function JWTDecoder() {
48+
const [token, setToken] = useState("");
49+
const [header, setHeader] = useState(null);
50+
const [payload, setPayload] = useState(null);
51+
const [signature, setSignature] = useState(null);
52+
const [error, setError] = useState(null);
53+
54+
const handleDecode = () => {
55+
setError(null);
56+
setHeader(null);
57+
setPayload(null);
58+
setSignature(null);
59+
if (!token.trim()) {
60+
setError("Please paste a JWT token.");
61+
return;
62+
}
63+
try {
64+
const parsed = parseJWT(token);
65+
if (!parsed.parts || parsed.parts.length < 2) {
66+
setError("Token does not have the expected parts (header.payload[.signature]).");
67+
return;
68+
}
69+
setHeader(parsed.header);
70+
setPayload(parsed.payload);
71+
setSignature(parsed.signature);
72+
} catch (e) {
73+
setError(e.message || String(e));
74+
}
75+
};
76+
77+
const handleClear = () => {
78+
setToken("");
79+
setHeader(null);
80+
setPayload(null);
81+
setSignature(null);
82+
setError(null);
83+
};
84+
85+
const copyPayload = async () => {
86+
if (!payload) return;
87+
try {
88+
await navigator.clipboard.writeText(JSON.stringify(payload, null, 2));
89+
} catch {}
90+
};
91+
92+
return (
93+
<div className="min-h-screen text-white p-8 font-sans">
94+
<h2 className="text-2xl font-bold mb-6 text-center text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-cyan-400 drop-shadow-[0_0_10px_rgba(147,51,234,0.6)]">
95+
🔐 JWT Decoder
96+
</h2>
97+
98+
<div className="max-w-3xl mx-auto bg-[#111827]/70 border border-[#1e293b] rounded-2xl p-6 backdrop-blur-sm shadow-[0_0_20px_rgba(56,189,248,0.15)]">
99+
<p className="text-sm text-slate-400 mb-4 text-center">
100+
Paste your <span className="text-cyan-400 font-medium">JWT</span> below and decode it safely on the client-side.
101+
</p>
102+
103+
<textarea
104+
aria-label="JWT token"
105+
value={token}
106+
onChange={(e) => setToken(e.target.value)}
107+
placeholder="Paste JWT here (header.payload.signature)"
108+
className="w-full min-h-[100px] p-3 rounded-xl bg-[#0f172a] border border-slate-700 focus:border-cyan-500 text-sm mb-4 outline-none text-slate-200"
109+
/>
110+
111+
<div className="flex gap-3 justify-center mb-5">
112+
<button
113+
onClick={handleDecode}
114+
className="px-5 py-2 rounded-lg bg-gradient-to-r from-cyan-500 to-purple-500 hover:from-cyan-400 hover:to-purple-400 shadow-[0_0_12px_rgba(147,51,234,0.4)] transition-all"
115+
>
116+
Decode
117+
</button>
118+
<button
119+
onClick={handleClear}
120+
className="px-5 py-2 rounded-lg border border-slate-600 hover:border-cyan-400 transition-all"
121+
>
122+
Clear
123+
</button>
124+
<button
125+
onClick={copyPayload}
126+
disabled={!payload}
127+
className={`px-5 py-2 rounded-lg border ${
128+
payload
129+
? "border-slate-600 hover:border-purple-400"
130+
: "border-slate-700 opacity-40 cursor-not-allowed"
131+
} transition-all`}
132+
title="Copy payload JSON"
133+
>
134+
Copy Payload
135+
</button>
136+
</div>
137+
138+
{error && (
139+
<div className="mb-5 p-3 rounded-md bg-red-900/30 border border-red-500/30 text-red-300 text-sm">
140+
{error}
141+
</div>
142+
)}
143+
144+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
145+
<div className="col-span-1">
146+
<h3 className="font-semibold text-cyan-400">Header</h3>
147+
<pre className="mt-2 p-3 rounded-md bg-[#0f172a] border border-slate-700 h-44 overflow-auto text-xs text-slate-300">
148+
{header ? JSON.stringify(header, null, 2) : "No header decoded"}
149+
</pre>
150+
</div>
151+
152+
<div className="col-span-1 md:col-span-2">
153+
<h3 className="font-semibold text-purple-400">Payload</h3>
154+
<pre className="mt-2 p-3 rounded-md bg-[#0f172a] border border-slate-700 h-44 overflow-auto text-xs text-slate-300">
155+
{payload ? JSON.stringify(payload, null, 2) : "No payload decoded"}
156+
</pre>
157+
</div>
158+
</div>
159+
160+
<div className="mt-6">
161+
<h3 className="font-semibold text-cyan-400">Signature</h3>
162+
<div className="mt-2 p-3 rounded-md bg-[#0f172a] border border-slate-700 text-xs text-slate-400 break-all">
163+
{signature || "No signature present"}
164+
</div>
165+
</div>
166+
</div>
167+
168+
<p className="mt-6 text-center text-xs text-slate-500">
169+
⚠️ This tool only decodes client-side. Never paste production tokens.
170+
</p>
171+
</div>
172+
);
173+
}

src/components/Layout/Layout.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ const Layout = ({ stars, children }) => {
1616
notice={"Under Construction"}
1717
/>
1818
<div className='relative'>
19-
{children}
19+
<div className="container mx-auto p-4 min-h-screen">
20+
{children}
21+
</div>
2022
</div>
2123
<Footer />
2224
</ThemeProvider>

src/pages/DevArea/DevTools.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { useParams } from "react-router-dom";
22
import Layout from "../../components/Layout/Layout";
33
import MarkDownEditor from "../../components/DevAreaTools/MarkDownEditor";
44
import JSONFormatter from "../../components/DevAreaTools/JSONFormatter";
5+
import JWTDecoder from "../../components/DevAreaTools/JwtDecoder";
56

67
const DevTools = () => {
78
const { tool } = useParams();
89

910
const tools = {
1011
markdown: <MarkDownEditor />,
11-
"json-formatter": <JSONFormatter />
12+
jwtdecoder: <JWTDecoder />,
13+
"json-formatter": <JSONFormatter />,
1214
}
1315

1416

src/pages/DevArea/index.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ const DevArea = () => {
2222
},
2323
{
2424
name: "JWT Decoder",
25-
link: "/devarea/jwt",
26-
isAvailable: false
25+
link: "/devarea/jwtdecoder",
26+
isAvailable: true
2727
},
2828
{
2929
name: "URL Encoder/Decoder",

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,6 +1845,11 @@ lru-cache@^5.1.1:
18451845
dependencies:
18461846
yallist "^3.0.2"
18471847

1848+
lucide-react@^0.544.0:
1849+
version "0.544.0"
1850+
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.544.0.tgz#4719953c10fd53a64dd8343bb0ed16ec79f3eeef"
1851+
integrity sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==
1852+
18481853
magic-string@^0.27.0:
18491854
version "0.27.0"
18501855
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3"

0 commit comments

Comments
 (0)