Recap:
The idea is simple enough: stop making people swipe a card and type a PIN at every single door. Instead, the ID card (a MAX32630FTHR + ATECC508A in your pocket) unlocks once via PIN, then silently does challenge-response crypto over Bluetooth every time you walk up to a door. If the card gets yanked off you, the IMU detects the tug and it locks itself. No PIN, no entry. For more details check the Part 1 of the series
- Part 1 - The Idea Identity Protocol Part 1 - Plan
The Sponsored kit is on the way, and Scheduled to be delivered this Saturday. I am gonna be travelling from Satuday morning to Wednesday Night, This was planned earlier and so as per that plan, I am making the Django server first
Server Architecture Overview
There are two distinct types of clients, One will be the Door device and the other is Browser being accessed by security for evaluating logs and analytics, each with their own authentication path hitting separate sets of endpoints. The request routing:

And the five database models with their relationships:

The two auth paths are the key structural decision here — more on that below.
The server is plainly a Proof of Concept. Therefore, I am not implementing any production-grade access control management system — it just needs to do two things well enough to demo the protocol: hand out public keys to doors, and receive access logs. Anything beyond that is a bonus.
That said, I ended up building a fair bit more than the bare minimum because the admin dashboard and analytics make demos much more compelling.
Models
There are five models. Four of them map directly to real-world entities in the system:
IDDevice - represents one ID card. Stores the owner's name and email, the ATECC508A's public key (DER-encoded, hex), a status ('active', 'inactive', 'lost'), and a 'last_seen' timestamp updated on each successful access. The public key is what gets handed to doors so they can verify signatures.
DoorDevice - a door controller. Has a name, location string, its own public key, a status, and a last_heartbeat timestamp. Each door gets an API key (see below) to authenticate with the server.
AccessEvent - the audit log. Every authentication attempt — success or failure — gets written here by the door, with a timestamp, which ID card and door were involved, and the result. The result field covers the interesting failure modes specifically: bad signature, blacklisted card, PIN timeout, tug detected. This is where you'd look to investigate anything suspicious.
Blacklist - revoked ID cards. When a card is reported lost or stolen, it goes here. The door can either poll `GET /api/blacklist-check/<device_id>/` per-card, or fetch the whole list at once with `GET /api/blacklist-sync/` and cache it locally. Local caching is the better option — it means the door can still reject blacklisted cards even if the server goes down.
DeviceAPIKey - lightweight auth for door-to-server communication. No JWT, no OAuth dance — a door just sends `Authorization: Api-Key <key>` in the header. The key is generated with `secrets.token_hex()` and stored in the database. The admin creates these keys and provisions them onto door hardware during setup. Each key tracks `last_used`, which is a nice sanity check during debugging.
Authentication Design Decision
There are two completely separate authentication mechanisms here, for two completely different clients.
Door devices use an API Key system - a static token in the `Authorization: Api-Key <token>` header. Ideally, I should implement more secure system but as i said before, this is a proof of concept for and so a token stored in flash, issued once during setup, is simple and easy to implement.
The admin browser uses Django's standard SessionAuthentication. it just works for a browser-based admin UI.
API Endpoints
Device-facing
- GET /api/keys/<device_id>/ - Fetch an ID card's public key
- POST /api/events/ - Log an access attempt
- GET /api/blacklist-check/<device_id>/ - Check if one card is blacklisted
- GET /api/blacklist-sync/ - Fetch the full blacklist for local caching
Dashboard/analytics
- GET /api/dashboard/stats/ - Today's and this week's access counts + success rates
- GET /api/dashboard/per-door/ - Per-door stats + hourly breakdown
- GET /api/dashboard/per-device/ - Per-ID-card access frequency
- GET /api/dashboard/security/ - Recent failures, tug detections, suspicious devices
- GET /api/dashboard/trend/ - Daily totals for charting (last N days)
Plus the standard DRF viewset CRUD routes for 'id-devices', 'door-devices', 'access-events', and 'blacklist'.
The Admin Dashboard
There's a custom admin dashboard at `/dashboard/` — a separate Django app with Bootstrap 5 templates and its own session-authenticated views. It's separate from Django's built-in admin (still present at `/admin/` for low-level data access). I am building my UI using the ever awesome AdminLTE theme.
- Overview - Live stats: today's and this week's totals + success rate, 7-day sparkline, recent events feed
- ID Devices - Full CRUD: register, edit, delete, search/filter by status
- Door Devices - Full CRUD: register, edit, delete, filter by status
- Event Log - Browse all access events, filter by result, door, date range
- Blacklist - Blacklist a device, remove entries
- API Keys - Create and revoke door device API keys
- Analytics - Charts: daily trend, result breakdown, per-door stats, suspicious device detection
Screenshots
Dashboard

Door Device CRUD

Access Log

Device Keys

Analytics

There are some bugs i need to crush. Will post the Source code post that