惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
Hacker News: Ask HN
Hacker News: Ask HN
The Hacker News
The Hacker News
S
Security Affairs
T
Tor Project blog
N
News | PayPal Newsroom
V2EX - 技术
V2EX - 技术
Microsoft Security Blog
Microsoft Security Blog
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
P
Palo Alto Networks Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
W
WeLiveSecurity
Y
Y Combinator Blog
TaoSecurity Blog
TaoSecurity Blog
MongoDB | Blog
MongoDB | Blog
Cloudbric
Cloudbric
大猫的无限游戏
大猫的无限游戏
Blog — PlanetScale
Blog — PlanetScale
G
GRAHAM CLULEY
博客园 - 聂微东
月光博客
月光博客
T
The Exploit Database - CXSecurity.com
C
Cyber Attacks, Cyber Crime and Cyber Security
Latest news
Latest news
H
Help Net Security
U
Unit 42
P
Privacy & Cybersecurity Law Blog
The GitHub Blog
The GitHub Blog
S
Securelist
The Last Watchdog
The Last Watchdog
Stack Overflow Blog
Stack Overflow Blog
H
Hacker News: Front Page
C
Check Point Blog
Schneier on Security
Schneier on Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
A
Arctic Wolf
P
Privacy International News Feed
WordPress大学
WordPress大学
T
Threatpost
博客园_首页
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
Secure Thoughts
Simon Willison's Weblog
Simon Willison's Weblog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Jina AI
Jina AI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Credentials in web applications: how to store them properly
Ian Johnson · 2026-05-14 · via DEV Community

Almost every breach you read about in the news involves credentials. Sometimes it's passwords pulled out of a database that hashed them badly. Sometimes it's an API key committed to a public GitHub repo. Sometimes it's a session token stolen from a JavaScript variable because somebody stored it in localStorage. More recently, it's an API key left public in a vibe-coded app. The technical details vary; the underlying problem is usually the same. Someone treated a secret like ordinary data and stored it the way they would store anything else.

This guide covers what counts as a credential, the small number of things you actually need to do to handle each kind correctly, and the mistakes that show up over and over in real applications.

The three kinds of credentials

The first thing to internalize is that "credential" isn't one thing. There are at least three categories, and they need to be handled differently:

  1. User credentials. What your users give you to prove who they are — passwords, primarily. You don't actually want to store these; you want to store something that lets you verify them later without being able to recover the original.
  2. Session credentials. Tokens your app issues after a user logs in, so they don't have to log in again on every request. Cookies are the most common form.
  3. Service credentials. The secrets your app needs to function: database passwords, API keys for third-party services, signing keys, encryption keys. Your application holds these; users never see them.

Storage strategy for each is genuinely different. Mixing up the categories — "encrypting" user passwords because you encrypt API keys, or stuffing service credentials in client-side code because you ship session tokens to the browser — is where a lot of trouble starts.

User passwords: hash, don't encrypt

The most important sentence in this article: you do not store user passwords. You store password hashes. A password is something a user gives you at login. A hash is a one-way function of that password. When the user comes back, you hash what they typed and compare it to what you stored. If anyone steals your database, they get hashes, not passwords. They still have to crack the hashes to learn anything useful...and with a modern hashing algorithm, cracking is intentionally slow.

The correct algorithms today are bcrypt, scrypt, and argon2 (specifically argon2id). All three are designed to be slow and memory-hard, which makes brute-forcing them expensive. They also handle salting for you automatically. Every password gets a unique random salt, mixed into the hash, so two users with the same password get different stored values, and an attacker can't precompute a rainbow table once and reuse it across accounts.

What you must not use: MD5, SHA-1, or any single application of a fast hash like SHA-256. These were designed to be fast, which is exactly the wrong property for password hashing. Modern GPUs can compute billions of fast-hash operations per second. A database hashed with an unsalted fast hash gets cracked in hours, sometimes minutes.

You also don't need to write any of this yourself. Every mainstream language has a vetted library. In Python you use bcrypt or argon2-cffi. In Ruby, bcrypt is built into Rails via has_secure_password. In PHP, password_hash() and password_verify() are in the standard library and use bcrypt by default. The library generates the salt, picks a cost factor, and produces a single string you store in a column. You give it the user's input and the stored value; it tells you yes or no.

A short list of things people get wrong here:

  • Using md5(password + salt) because someone read about salting on a blog from 2008. Salting helps, but it does not fix the speed problem. Fast hashes are still fast.
  • "Encrypting" the password so they can email it back to the user if they forget it. If you can recover a password, so can an attacker who steals your database and the encryption key, which usually lives nearby. Implement password reset via a time-limited token sent to the user's email instead.
  • Logging the password. It happens constantly; a debug log on the login endpoint that dumps the request body. Scrub sensitive fields out of your logs before they leave the application.

Session tokens: cookies done right

Once a user logs in, you need a way to recognize them on subsequent requests without making them log in every time. That's a session credential. Almost always, it should be a random, opaque token stored on the user's machine and sent back to your server with each request. The server looks up that token to find the session.

The standard mechanism is a cookie, and the cookie needs three flags set:

  • HttpOnly prevents JavaScript on the page from reading the cookie. This is the single most important defense against an XSS attack stealing the session.
  • Secure prevents the cookie from being sent over plain HTTP. Always set this in production.
  • SameSite=Lax (or Strict, depending on your needs) prevents the cookie from being sent on cross-site requests, which protects against CSRF.

The token itself should be long (128 bits of entropy or more) and generated by a cryptographically secure random generator — secrets.token_urlsafe() in Python, SecureRandom.urlsafe_base64() in Ruby, random_bytes() in PHP. Not the regular random function. Not a UUID v4 (close, but its spec doesn't guarantee the entropy distribution you want for security tokens). The crypto-grade RNG (random number generator).

A few words on JWTs. They're popular, especially in single-page-app and mobile contexts, but they're often misused. A JWT is a self-contained, signed token that proves the bearer is allowed to do something. The trade-off is that you can't easily revoke them — if someone steals a JWT, it's valid until it expires. Sessions stored server-side (in Redis, in your database) can be invalidated on the server with a single record delete. If you don't have a specific reason JWTs solve a problem for you, prefer server-side sessions. And if you do use JWTs, never put them in localStorage — that's readable by any JavaScript on the page, defeating the protection HttpOnly cookies give you. Send them as HttpOnly cookies or, at minimum, hold them in memory and not in storage that survives a page reload.

Service credentials: outside the code, outside git

Your application has secrets it uses to talk to other systems: a database password, a Stripe API key, an SMTP credential, signing keys, OAuth client secrets. These need to be available to the running application but invisible to everyone else.

The non-negotiable rules:

  • Never commit secrets to source control. Not in code, not in config files checked into the repo, not in tests, not even in commit messages. Once a secret has touched a git history, treat it as compromised and rotate it — even if you rewrite history to remove it, you have to assume it leaked.
  • Never put secrets in client-side code. Any "secret" in your JavaScript bundle, your mobile app binary, or your HTML is public. Anyone can View Source or unzip the APK. If your frontend needs to call a third-party API that requires a secret, proxy that call through your backend.
  • Use environment variables or a secret manager. In development, a local .env file (listed in .gitignore) is fine. In production, use environment variables injected by your deploy system, or a dedicated secret manager like AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault. Secret managers add rotation, access control, and audit logging, which matter as your team and surface area grow.

Some additional good practices: use different credentials for different environments (the staging database password is not the production one), grant each service the narrowest permissions it actually needs, and rotate credentials periodically, and immediately if anyone with access leaves the team or if there's any hint that one might have leaked.

In CI: GitHub Actions and similar

Continuous integration is one of the most common places credentials leak from. Build logs are often visible to anyone who can see the repo, workflows run third-party actions whose code can change between releases, and a misconfigured pipeline will happily print a secret on its way to using it. A few rules cover most of the risk.

Use the platform's secret store. In GitHub Actions, that's Settings → Secrets and variables → Actions. Add the secret there, then reference it in the workflow as ${{ secrets.MY_SECRET }}. GitHub will automatically mask the value in logs if it appears verbatim, but masking is a safety net, not a strategy — don't echo secrets, don't pass them as command-line arguments (they show up in process listings on the runner), and don't write them to files that get uploaded as build artifacts.

Scope secrets to environments. GitHub Actions lets you attach secrets to a named environment (production, staging) and even require manual approval before a workflow can access them. This means a pull request from a feature branch can't accidentally (or maliciously) pull production keys. And if your CI runs on pull requests from forks, be especially careful: by default, fork PRs don't get access to repository secrets, which is the safe behavior. Don't undo that without understanding what you're enabling.

Prefer short-lived credentials over long-lived ones. For cloud providers, that means using OIDC: GitHub Actions can authenticate directly to AWS, GCP, or Azure and receive a short-lived token scoped to exactly what the workflow needs, with no long-lived access key stored in the repo at all. This is the modern best practice. Setting it up is more work than pasting an access key into a secret. But the work is worth it, because there's nothing static to steal.

Finally, audit your third-party actions. A popular action with thousands of stars is still arbitrary code running in the same environment as your secrets. Pin actions to a specific commit SHA rather than a moving tag like @v1, and review the source of anything new you bring in.

Setting credentials in staging and production

Production credentials need to be available on the running server without ever passing through a place they don't belong, such as your repo, your container image, your build artifact, a Slack channel, or an engineer's laptop.

The standard approach is to inject them at runtime, not bake them in. Most platforms have a built-in mechanism: Heroku and Fly have config vars; AWS ECS and Kubernetes have native Secret resources; systemd has EnvironmentFile; Vercel, Netlify, and Render expose environment variables in their dashboards. The application reads from environment variables at startup, and the platform is responsible for getting the right values into the environment of the right process. If you graduate to a dedicated secret manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Doppler), the same pattern holds. The app reads the secret at startup or fetches it on demand, and never sees the secret at build time.

What this means concretely: do not bake secrets into Docker images. A Docker image is a near-public artifact even when it lives in a private registry. Anyone who pulls it sees everything inside, including historical layers that you thought you deleted. The same goes for AMIs, build artifacts uploaded to artifact stores, and anything produced at build time. Build-time inputs should never include production secrets.

Use different credentials for different environments...really! Staging and production should never share a database password, an API key, or a signing secret. If they do, a leak from the less-protected environment compromises the more-protected one. The same goes for personal development credentials: never let an engineer's local .env contain production values.

Where the platform supports it, prefer identity-based access over stored credentials. AWS IAM roles, GCP workload identity, and the equivalents on other clouds let your running application authenticate as itself, with no static key sitting anywhere. The cloud provider verifies the workload and hands it a short-lived token. This eliminates an entire class of leakage, because there's nothing long-lived to leak.

Finally, audit who has access. The number of humans who can read production secrets should be small, named, and reviewed periodically. Most secret managers log every access. Check those logs. And when a developer leaves the team, rotate the secrets they had access to, not just their personal accounts.

The client-side trap

This deserves a section of its own because it catches people constantly. The browser is not a trusted environment. Anything your JavaScript can read, the user (or an attacker who has gotten code running on the page) can read. That means:

  • API keys for third-party services — Stripe secret keys, AWS credentials, anything labeled "secret" — must never appear in the browser. If you need to do something privileged from a user action, the user's action calls your backend, and your backend uses the secret.
  • Authentication tokens stored in localStorage or sessionStorage are accessible to any script that runs on the page, including one injected via XSS. Prefer HttpOnly cookies.
  • "Hidden" form fields, obfuscated JavaScript, environment variables embedded at build time — none of these hide anything. They just make it slightly slower for an attacker to find what they're looking for.

The mental model: if a value is shipped to the browser, it's public. Design accordingly.

A few cross-cutting principles

A handful of habits cover most of what you need:

  • Treat every credential as compromised the moment it leaks. Rotate immediately. Don't argue about whether the leak was "really" a leak.
  • Use libraries, not your own crypto. Hashing, signing, token generation — there's a vetted library in every language. Use it.
  • Defaults matter more than configuration. A teammate who doesn't know the rules should fall into the pit of success: secrets loaded from environment, cookies with the right flags, passwords going through the standard hashing helper. Make the safe path the easy path.
  • Audit what you log. Log scrubbing is one of the cheapest, highest-value security investments you can make. Get sensitive fields out of your logs before they ever land in your log aggregator.
  • Assume the database will be stolen. That's the test for whether your credential storage is good. If your database leaks tonight, what does the attacker learn? Hashes? Or passwords?

The whole topic comes down to the difference between secrets and data. Data lives in databases, gets serialized into responses, shows up in logs, gets debugged in console statements. Secrets cannot do any of that. The skill is recognizing when something is a secret and storing it accordingly, every single time.