টোকেন-ভিত্তিক অথেন্টিকেশন ও JWT
এই পাঠে যা শিখবেন
- JWT-র তিনটি অংশ (header, payload, signature) ও তাদের base64url এনকোডিং
- HMAC-SHA256 দিয়ে সিগনেচার গণনা করে একটি সম্পূর্ণ, সত্যিকারের JWT তৈরি করা
- constant-time তুলনা (
hmac.compare_digest) ব্যবহার করে একটি বাস্তবverify_jwt()ফাংশন লেখা - একটি টেম্পার-করা টোকেন কেন ও কীভাবে ভেরিফিকেশনে ব্যর্থ হয় তা হাতে-কলমে প্রমাণ করা
১ · JWT কেন — সেশনের বিকল্প স্টেটলেস পদ্ধতি
L33-এ দেখা সেশন-ভিত্তিক অথেন্টিকেশনে সার্ভারকে প্রতিটি সক্রিয় ইউজারের জন্য একটি এন্ট্রি মনে রাখতে হয়
(স্টেটফুল)। JWTJSON Web
Tokenএকটি স্ব-অন্তর্ভুক্ত (self-contained), ডিজিটালি-সাইনড টোকেন যা নিজের ভেতরেই দাবি (claims) বহন
করে, ফলে সার্ভারকে আলাদা করে কোনো সেশন মনে রাখতে হয় না।-এর মূল ধারণা হলো — টোকেনটি
নিজেই সব প্রয়োজনীয় তথ্য (যেমন user_id) বহন করে, এবং একটি ক্রিপ্টোগ্রাফিক সিগনেচার দিয়ে "সিল"
করা থাকে। সার্ভার শুধু সিগনেচার যাচাই করে নিশ্চিত হয় টোকেনটি সত্যিই তার নিজের ইস্যু করা এবং কেউ এটি বদলায়নি
— কোনো session_store লুকআপ প্রয়োজন হয় না, তাই একাধিক সার্ভার ইনস্ট্যান্স নির্বিঘ্নে যেকোনো
একটি এই টোকেন যাচাই করতে পারে।
অ্যালগরিদম ও টোকেনের ধরন জানায় —
{"alg":"HS256","typ":"JWT"}।প্রকৃত দাবি (claims) — যেমন
user_id, exp (মেয়াদ শেষের সময়)।Header ও Payload-এর উপর গোপন কী দিয়ে গণনা করা HMAC-SHA256 — টেম্পারিং ধরার একমাত্র রক্ষাকবচ।
২ · একটি সত্যিকারের JWT তৈরি ও যাচাই — encode → sign → verify → tamper
নিচের কোড সেলে চারটি সত্যিকারের ধাপ আছে — (১) header ও payload ডিকশনারিকে base64url এনকোড করা, (২) দুটো
অংশ . দিয়ে জোড়া দিয়ে "signing input" তৈরি করা, (৩) সেই ইনপুটকে গোপন কী দিয়ে HMAC-SHA256
সাইন করে সিগনেচার বানানো, (৪) তিনটি অংশ জোড়া দিয়ে চূড়ান্ত টোকেন তৈরি করা। তারপর একটি সত্যিকারের
verify_jwt() ফাংশন সিগনেচার পুনরায় গণনা করে hmac.compare_digest() দিয়ে
constant-time তুলনা করে যাচাই করে।
import hashlib, hmac, base64, json, time
def b64url_encode(data: bytes) -> str:
"""স্ট্যান্ডার্ড base64url এনকোড করে, JWT কনভেনশন অনুযায়ী শেষের '=' প্যাডিং বাদ দিয়ে"""
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
def b64url_decode(s: str) -> bytes:
"""বাদ দেওয়া প্যাডিং ফিরিয়ে দিয়ে base64url ডিকোড করে"""
padding = "=" * (-len(s) % 4)
return base64.urlsafe_b64decode(s + padding)
def create_jwt(payload: dict, secret: str) -> str:
header = {"alg": "HS256", "typ": "JWT"}
header_b64 = b64url_encode(json.dumps(header, separators=(",", ":")).encode())
payload_b64 = b64url_encode(json.dumps(payload, separators=(",", ":")).encode())
signing_input = f"{header_b64}.{payload_b64}"
signature = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).digest()
signature_b64 = b64url_encode(signature)
return f"{signing_input}.{signature_b64}"
def verify_jwt(token: str, secret: str):
"""টোকেন ভেঙে সিগনেচার পুনরায় গণনা করে constant-time তুলনা করে --
মিললে ডিকোড করা পেলোড, না মিললে None রিটার্ন করে"""
parts = token.split(".")
if len(parts) != 3:
return None
header_b64, payload_b64, signature_b64 = parts
signing_input = f"{header_b64}.{payload_b64}"
expected_sig = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).digest()
expected_sig_b64 = b64url_encode(expected_sig)
if not hmac.compare_digest(expected_sig_b64, signature_b64):
return None
return json.loads(b64url_decode(payload_b64))
SECRET_KEY = "abcltech-super-secret-key"
payload = {"user_id": 1, "username": "sohel", "exp": int(time.time()) + 3600}
token = create_jwt(payload, SECRET_KEY)
print("তৈরি হওয়া JWT (header.payload.signature):")
print(token)
decoded = verify_jwt(token, SECRET_KEY)
print("\n--- আসল টোকেন ভেরিফাই করা ---")
print("ভেরিফিকেশন পাস করেছে:", decoded is not None)
print("ফেরত পাওয়া পেলোড:", decoded)
print("আসল পেলোডের সাথে হুবহু মিলেছে:", decoded == payload)
# --- এখন টোকেন টেম্পার করা যাক -- পেলোড বদলে সিগনেচার পুনরায় সাইন করা হয়নি ---
header_b64, payload_b64, signature_b64 = token.split(".")
tampered_payload = json.loads(b64url_decode(payload_b64))
tampered_payload["user_id"] = 999
tampered_payload_b64 = b64url_encode(json.dumps(tampered_payload, separators=(",", ":")).encode())
tampered_token = f"{header_b64}.{tampered_payload_b64}.{signature_b64}"
print("\n--- টেম্পার-করা টোকেন ভেরিফাই করা ---")
tampered_result = verify_jwt(tampered_token, SECRET_KEY)
print("ভেরিফিকেশন পাস করেছে:", tampered_result is not None)
print("রিটার্ন মান:", tampered_result)
payload_b64 অংশ বদলানো হয়েছে, signature_b64 অংশ
পুরনোটাই রাখা হয়েছে (আক্রমণকারীর কাছে SECRET_KEY নেই, তাই সে নতুন সঠিক
সিগনেচার বানাতে পারবে না)। verify_jwt() যখন নতুন (বদলে যাওয়া) payload_b64 দিয়ে
সিগনেচার পুনরায় গণনা করে, ফলাফলটি টোকেনে থাকা পুরনো সিগনেচারের সাথে মিলবে না — তাই
hmac.compare_digest() False রিটার্ন করে এবং পুরো ফাংশন None রিটার্ন
করে। এটাই দেখায় সিগনেচার আসলে ডেটার অখণ্ডতা (integrity) রক্ষা করে, শুধু ডেটাকে অস্পষ্ট (obscure) করে না।
hmac.compare_digest)
সাধারণ == দিয়ে দুটো স্ট্রিং তুলনা করলে Python প্রথম অমিল অক্ষরেই তুলনা থামিয়ে দেয় — এর মানে
তুলনার সময় (কত মাইক্রোসেকেন্ড লাগলো) কতগুলো অক্ষর মিলেছে তার উপর নির্ভর করে। একজন আক্রমণকারী বহুবার
চেষ্টা করে এই সময়ের পার্থক্য পরিমাপ করে ধীরে ধীরে সঠিক সিগনেচার অনুমান করতে পারে (একে "টাইমিং অ্যাটাক"
বলা হয়)। hmac.compare_digest() সবসময় একই সময় নেয়, তুলনা যত অক্ষরই মিলুক না কেন — তাই এই
আক্রমণ ঠেকায়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ JWT-র পেলোড এনকোডেড, কিন্তু এনক্রিপ্টেড নয় — এর ব্যবহারিক অর্থ কী?
base64url এনকোডিং কোনো গোপনীয়তা (secrecy) দেয় না — এটি শুধু bytes-কে টেক্সট-নিরাপদ ফরম্যাটে রূপান্তর
করে, এবং যেকেউ b64url_decode() দিয়ে পেলোড পড়ে ফেলতে পারে (এই কোডের মতোই)। তাই JWT-র পেলোডে
কখনো পাসওয়ার্ড, ক্রেডিট কার্ড নম্বরের মতো গোপন তথ্য রাখা উচিত নয় — সিগনেচার শুধু নিশ্চিত করে ডেটা
বদলানো হয়নি, কিন্তু গোপন রাখে না।
প্র ০২
SECRET_KEY যদি কোনোভাবে ফাঁস হয়ে যায়, তাহলে কী ঝুঁকি তৈরি হয়?
SECRET_KEY ফাঁস হয়ে গেলে যেকেউ create_jwt()-এর মতো নিজেই যেকোনো পেলোড দিয়ে
একটি সঠিকভাবে-সাইনড টোকেন তৈরি করতে পারবে — যেমন নিজেকে user_id: 1 (অ্যাডমিন) দাবি করে একটি
বৈধ-দেখতে টোকেন বানানো। যেহেতু সার্ভার শুধু সিগনেচার মেলে কি না তা যাচাই করে (এবং সিগনেচার সঠিকভাবেই
তৈরি হবে, কারণ কী-টি এখন আক্রমণকারীর কাছেও আছে), সার্ভার এটিকে বৈধ হিসেবে গ্রহণ করবে। তাই
SECRET_KEY কখনো সোর্স কোডে হার্ডকোড করে পাবলিক রিপোজিটরিতে রাখা উচিত নয় (L51-এ পরিবেশ
ভেরিয়েবল ও সিক্রেট ম্যানেজমেন্ট নিয়ে বিস্তারিত)।
প্র ০৩
উপরের কোডে b64url_decode() ফাংশনে "=" * (-len(s) % 4) লাইনটি কী কাজ করছে?
create_jwt()-এ এনকোড করার সময় rstrip(b"=") দিয়ে শেষের প্যাডিং ক্যারেক্টার
(=) বাদ দেওয়া হয়েছিল, কারণ JWT কনভেনশনে প্যাডিং রাখা হয় না। কিন্তু Python-এর
base64.urlsafe_b64decode() ফাংশনের সঠিকভাবে কাজ করতে ইনপুটের দৈর্ঘ্য ৪-এর গুণিতক হওয়া
প্রয়োজন। -len(s) % 4 হিসাব করে ঠিক কতগুলো = লাগবে দৈর্ঘ্যকে ৪-এর গুণিতক করতে
(যেমন দৈর্ঘ্য ৪-এর গুণিতক হলে ০টি, ২ কম হলে ২টি, ৩ কম হলে ১টি) — এভাবে বাদ-দেওয়া প্যাডিং সঠিকভাবে ফিরিয়ে
দিয়ে ডিকোড সম্ভব করা হয়।
অনুশীলন
-
চিন্তা করুন: সেশন-ভিত্তিক অথেন্টিকেশনে (L33)
logout()করলে সার্ভার সেশন স্টোর থেকে এন্ট্রি মুছে দিতে পারে, তাই সেই আইডি তৎক্ষণাৎ অকার্যকর হয়ে যায়। JWT-তে "লগআউট" করা কেন সহজাতভাবে বেশি জটিল?যেহেতু JWT স্টেটলেস এবং সার্ভার কোনো "ইস্যু-করা টোকেনের তালিকা" রাখে না, তাই কোনো নির্দিষ্ট টোকেনকে তাৎক্ষণিকভাবে "বাতিল" করার সরাসরি উপায় নেই —
verify_jwt()শুধু সিগনেচার ও (থাকলে) মেয়াদ যাচাই করে, টোকেনটি "এখনো বৈধ কি না" এমন কোনো তালিকা চেক করে না। বাস্তব সিস্টেমে এটি সমাধান করা হয় ছোট মেয়াদ (exp) রেখে, অথবা একটি আলাদা "ব্ল্যাকলিস্ট" স্টোর রেখে বাতিল-করা টোকেনের আইডি মনে রাখার মাধ্যমে — যা আংশিকভাবে স্টেটলেসনেসের সুবিধা কমিয়ে দেয়। -
পরীক্ষা করুন: উপরের কোড সেলে
verify_jwt(token, "wrong-secret-key")কল করে দেখুন — অর্থাৎ সঠিক টোকেন কিন্তু ভুলsecretদিয়ে যাচাই করলে কী হয়, এবং Run চেপে ফলাফল দেখুন।ভুল
secretদিয়েhmac.new()ভিন্ন একটি সিগনেচার তৈরি করবে (কারণ HMAC-এর ফলাফল সরাসরি কী-এর উপর নির্ভরশীল), যা টোকেনে থাকা আসল সিগনেচারের সাথে মিলবে না। ফলেhmac.compare_digest()Falseরিটার্ন করবে এবংverify_jwt()Noneরিটার্ন করবে — অর্থাৎ ভুল কী দিয়ে চেষ্টা করা এবং টেম্পার-করা টোকেন — দুটোই একই কারণে ব্যর্থ হয়: সিগনেচার প্রত্যাশিত মানের সাথে মেলে না।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট।
- Cybersecurity কোর্স সহোদর কোর্স ক্রিপ্টোগ্রাফিক হ্যাশিং, কী ম্যানেজমেন্ট, ও টাইমিং অ্যাটাকের মতো গভীর নিরাপত্তা বিষয়গুলো সেই কোর্সে বিস্তারিত আলোচিত।
-
Python Programming কোর্স সহোদর কোর্স
hashlib,hmac,base64,jsonমডিউলসহ Python-এর স্ট্যান্ডার্ড লাইব্রেরির ভিত্তি সেই কোর্সে তৈরি হয়েছে। - সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।