Device Fingerprinting for Abuse Detection
Device fingerprinting — device identification by combination of browser and OS characteristics without using cookies. Allows recognizing fraudster after IP change, cookie reset, even in incognito mode. Unlike cookies, doesn't require explicit consent for security purposes under most laws (not advertising tracking).
Fingerprint Components
Fingerprint quality is determined by entropy — amount of information each attribute provides:
| Component | Entropy (bits) | Description |
|---|---|---|
| User-Agent | 10–14 | OS + browser + version |
| Canvas fingerprint | 8–12 | GPU + fonts + AA rendering |
| WebGL vendor/renderer | 8–10 | GPU model |
| Installed fonts | 6–10 | List via measureText |
| AudioContext fingerprint | 6–8 | DSP hardware characteristics |
| Resolution + devicePixelRatio | 4–6 | Monitor + scale |
| Timezone | 4–5 | IANA timezone |
| Languages | 3–4 | Browser language list |
| Platform | 2–3 | Windows/Mac/Linux |
| CPU cores, memory | 3–4 | navigator.hardwareConcurrency |
Combination provides 40–80 bits entropy — theoretically sufficient for global uniqueness.
Client-Side Collection (JavaScript)
class DeviceFingerprinter {
async collect() {
const [canvas, webgl, audio, fonts] = await Promise.all([
this.getCanvasFingerprint(),
this.getWebGLFingerprint(),
this.getAudioFingerprint(),
this.getInstalledFonts(),
])
return {
// Basic
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
languages: navigator.languages?.join(',') ?? '',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
// Screen
screenResolution: `${screen.width}x${screen.height}`,
screenDepth: screen.colorDepth,
devicePixelRatio: window.devicePixelRatio,
windowSize: `${window.innerWidth}x${window.innerHeight}`,
// Hardware
cpuCores: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
// Rendering fingerprints
canvas,
webgl,
audio,
fonts: fonts.slice(0, 20).join(','), // top-20 fonts
// Browser APIs
cookieEnabled: navigator.cookieEnabled,
doNotTrack: navigator.doNotTrack,
touchPoints: navigator.maxTouchPoints,
pdfViewer: navigator.pdfViewerEnabled,
}
}
async getCanvasFingerprint() {
const canvas = document.createElement('canvas')
canvas.width = 200
canvas.height = 50
const ctx = canvas.getContext('2d')
// Draw text with effects — each GPU renders differently
ctx.textBaseline = 'top'
ctx.font = '14px Arial'
ctx.fillStyle = '#f60'
ctx.fillRect(125, 1, 62, 20)
ctx.fillStyle = '#069'
ctx.fillText('FP Test 🦄 ', 2, 15)
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'
ctx.fillText('FP Test 🦄 ', 4, 17)
return canvas.toDataURL()
.split(',')[1]
.substring(0, 50) // take first 50 chars — enough for entropy
}
async getWebGLFingerprint() {
const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl')
if (!gl) return 'no_webgl'
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')
const vendor = debugInfo
? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)
: gl.getParameter(gl.VENDOR)
const renderer = debugInfo
? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
: gl.getParameter(gl.RENDERER)
return `${vendor}~${renderer}`
}
}







