Broken Authentication: When Logins Leak Like a Sieve
Anyone who has ever clicked "Forgot Password" knows authentication is more complicated than it looks. You'd think "username + password" is straightforward, but the moment humans are involved, everything goes off the rails.
In this article we tear down every common mistake:
- default credentials and weak passwords
- credential stuffing and brute force mythbusting
- session fixation, hijacking, and timeout blunders
- MFA bypass techniques and OTP misconfigurations
- insecure password reset and recovery flows
- mobile & API auth headaches, JWT misuse, and cookie slipups
We'll mix in breach post‑mortems (Equifax, FAANG, banks), laugh at ridiculous code samples, and finish with a giant defense checklist you can print and tape above your desk. It's long, it's entertaining, and yes, it ends with policy templates just like the others.
What Does "Broken Authentication" Mean?
The term comes from the OWASP Top 10 (A02 in 2021), but it encapsulates a variety of flaws where an application fails to properly verify user identities. The failure can be:
- Authentication failure – the system allows attackers to log in as someone else without knowing their credentials
- Session management failure – the attacker captures or predicts a valid session token and uses it to impersonate a user after authentication
- Credential management issues – passwords stored in plaintext, weak hashing, exposed via logs, etc.
It is not just "passwords"; it includes anything that proves a user is who they claim to be and anything that maintains that proof.
Why It’s a Big Deal
If authentication is broken, the attacker is "inside". They don't need a bug in the product anymore—they're a legitimate user with your privileges. Consequences include:
- Account takeover (ATO) – your bank account, your email, your SaaS admin panel
- Lateral movement – pivot from one user to another inside corporate networks
- API abuse – client credentials stolen, rate limits bypassed, data scraped
- Data breach – once in, dump the database, S3 bucket, or CRM
- Reputation, fines, and customer churn – see Yahoo, LinkedIn, Uber, etc.
Broken authentication is the gateway drug to full compromise. Fixing it drastically reduces risk across your entire application attack surface.
Common Authentication Flaws (Welcome to the Hall of Shame)
1. Default or Hardcoded Credentials
// Java example
String username = request.getParameter("user");
String password = request.getParameter("pass");
if(username.equals("admin") && password.equals("admin123")) {
// login success
}
Millions of IoT devices shipped with admin:admin or root:toor.
2. Weak Password Policies
"Minimum 6 characters" or "must contain a number" is not a policy, it's an invitation to Password1.
3. No Account Lockout / Rate Limiting
app.post('/login', (req,res) => {
const {u,p}=req.body;
if(db.check(u,p)) res.send('ok');
else res.send('fail');
});
This code will happily respond to 1,000,000 guesses per second.
4. Predictable Session IDs
Using MD5(timestamp + user) or even worse, sequential integers. Attackers can guess or brute explore valid sessions.
5. Session Fixation
App accepts a session ID provided by the attacker and associates it with a user after login.
6. Credential Stuffing & Password Reuse
Ranked #1 cause of breaches; users re‑use passwords across 100 sites.
7. Broken "Forgot Password" Flows
-
Use the old password (
if(user.reset_token == token)) instead of one‑time random code -
Email reset links with
?token=12345where token is user ID - Reveal whether an email is registered
8. Insecure Multi‑Factor Authentication (MFA)
- SMS OTPs sent over unencrypted channels or reused
- Backup codes stored in plaintext next to passwords
-
BegErrTOTP seeds generated with weak entropy - Changing MFA factors without re‑authenticating
9. JWT Espionage and Token Replay
-
alg: nonebypass (the classic) - Tokens signed with HMAC key that is publicly reachable
-
No expiration or
expset to9999999999 - Storing JWTs in localStorage so <script> XSS can steal them
10. API Keys & Secrets in Source Code
-
Throwing
.envin a public GitHub repo -
Hardcoding
aws_secret_keyinto mobile apps - Using same key across multiple environments
11. Mobile Authentication Pitfalls
-
Storing tokens in
NSUserDefaultsorLocalStorage - Not using TLS for token endpoints
- Weak biometrics fallback when Touch ID/Face ID fails
12. OAuth/OpenID Misconfigurations
-
Redirect URI whitelist contains wildcard (
https://*.example.com/*) - State parameter not validated
- Client secret embedded in SPAs
13. Insufficient Monitoring & Logging
If you don't log failed logins or token usage, you won't notice when credentials are abused.
(rep this section in actual article with more narrative & humor)
Real‑World Broken Auth Breaches
LinkedIn (2012)
- Employed unsalted SHA‑1 hashes
- 6.5M passwords dumped, later 117M more
Yahoo (2013‑2014)
-
Stolen account recreation via forged cookies from MD5 hashing of
user.datfile - 500M accounts impacted – worst breach in history
Equifax (2017)
- Unpatched Apache Struts vulnerability used for RCE
- But auth blame: the attackers used brute‑forced admin credentials on a separate system to exfiltrate data
Uber (2016)
- GitHub credentials of a contractor stored in plaintext in a repo
- Attackers accessed AWS console and downloaded backups
Okta (2023)
- MFA fatigue/phishing in social engineering attacks
-
High‑profile session hijacking to bypass
adminprotections
(you'd expand details and add more examples, with spicy commentary)
How Attackers Exploit Authentication Flaws
-
Password spraying
– try
Winter2026!against many accounts to avoid lockouts - Session side‑jacking – sniff cookies on unsecured Wi‑Fi
-
Cross‑Site Login Forgery (CSLF)
– trick user into submitting login form to leak credentials via
Referer - MFA fatigue – repeatedly send push notifications until user approves by accident
-
Phishing with premium domains
–
g00gle.com/logincapturing OAuth tokens
Massive Code Section: Examples of Broken and Fixed Flows
(Here we'd include multiple full-code samples per language akin to earlier articles, demonstrating naive login, attack, and patched versions.)
Testing & Tools for Authentication Weaknesses
-
Burp Intruderwith wordlists for password spraying -
acyortfor finding default creds on IoT -
hydra,medusafor brute force – use responsibly! -
password.testerfor frequency analysis of chosen passwords - OWASP Credential Stuffing Tools (CF-Injector)
-
Continuous scanning with
Gitleaks/git-secretsfor exposed keys
Defense: Putting the Locks on Right
- Strong password policies – length over complexity; ban known bad passwords
- Rate limit and lockout – 5 attempts per minute, exponential backoff, CAPTCHAs
- Password hashing – Argon2id or bcrypt with a sufficiently high cost
- Multi-Factor Authentication – prefer TOTP, FIDO2, hardware keys
-
Secure session management
– use cryptographically random tokens, rotate on login, set
HttpOnlyandSecureflags, limit token lifetime - MFA token replay prevention – require one‑time use codes and invalidate them after use
- Password reset best practices – random one‑time links, short TTL, verify email ownership without revealing account existence
- APIs – use OAuth2 with PKCE, rotate client secrets, check scopes
- Monitoring – log all auth attempts and alert on anomalies (impossible travel, geolocation spikes, new devices)
- Education – teach users about phishing, password managers, and suspicious MFA approval requests
Massive Code Section: Broken vs Fixed Authentication
Below are multiple language examples showing horribly broken authentication and secure rewrites. Each snippet mirrors the style of the earlier articles—vulnerable code followed by a patched version.
PHP – Naive Login Form
// vulnerable.php
$username = $_POST['user'];
$password = $_POST['pass'];
// NEVER do this:
$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$res = $db->query($sql);
if ($res->num_rows) {
$_SESSION['user'] = $username; // session fixation risk
echo "Welcome $username";
}
An attacker can SQLi the query, guess weak passwords, or call the endpoint repeatedly with different credentials because there's no rate limit.
Patched Version:
// secure.php
$username = $_POST['user'];
$password = $_POST['pass'];
// prepared statement + secure hash
$stmt = $db->prepare('SELECT password_hash FROM users WHERE username=?');
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($hash);
if ($stmt->fetch() && password_verify($password, $hash)) {
session_regenerate_id(true); // prevent fixation
$_SESSION['user'] = $username;
echo "Welcome $username";
} else {
http_response_code(401);
echo "Invalid credentials";
}
Key fixes: parameterized query, hashed passwords, session rotation, and no detailed error message.
Python – Flask with Rate Limit
@app.route('/login', methods=['POST'])
def login():
username = request.form['user']
password = request.form['pass']
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
login_user(user)
return 'ok'
else:
return 'fail', 401
Add Flask-Limiter to throttle attempts:
limiter = Limiter(app, key_func=get_remote_address, default_limits=["5 per minute"])
@app.route('/login', methods=['POST'])
@limiter.limit("5/minute")
def login():
# same as above
Java – Spring Security Configuration
Bad config (insecure memory, default login page):
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.and().formLogin(); // default login page, no CSRF protection
}
Better config:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().and()
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.failureUrl("/login?error")
.and()
.logout()
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
}
Node.js – Express + bcrypt + helmet
app.post('/login', async (req, res) => {
const { user, pass } = req.body;
const u = await User.findOne({ username: user });
if (u && await bcrypt.compare(pass, u.passwordHash)) {
req.session.regenerate(err => {
if (err) return res.status(500).end();
req.session.user = u.id;
res.send('ok');
});
} else {
res.status(401).send('Invalid');
}
});
Helmet sets secure headers, bcrypt hashes passwords, and session regeneration stops fixation.
Testing & Tools for Authentication Weaknesses
- Burp Suite Intruder with wordlists for password spraying; set to fault tolerant mode.
- Hydra/Medusa for brute force when you have permission; avoid abusing.
- OWASP ZAP has an authentication scanner that fuzzes login forms.
- Cloudflare Burp plugin to simulate login flows for rate limit bypass tests.
- Acyort and ncrack for scanning default credentials on SMB, SSH, databases.
- password frequency lists such as rockyou.txt, HaveIBeenPwned wordlist for credential stuffing.
- MultiBrute – multi‑threaded account enumeration.
- Gitleaks / git-secrets – detect committed secrets in repos.
-
TOTP sniffers
like
oathtoolto brute‑force 6-digit codes if you can intercept the seed.
Manual testing checklist:
-
Attempt login with
admin/adminand other common credentials. - Try 1000 sequential guesses and watch response times/ratelimit.
- Analyze session cookie values for predictability.
-
Check
Forgot Passwordfor username enumeration. - Try login with previously leaked passwords from known breaches (use lists from HIBP).
- Test MFA fallback paths: SMS, email, backup codes.
- Inspect mobile app traffic for API keys or tokens in plaintext.
Automated scans should run weekly against all authentication endpoints, including API paths under /api/* and OAuth flows.
Defense: Putting the Locks on Right
- Strong hashing : arguable the most important. Use Argon2id with high memory cost, or scrypt/bcrypt with work factor.
- Password policies : enforce minimum length (12+), ban top‑20,000 breached passwords (use HIBP API), encourage passphrases.
- Rate limiting & lockout : combine per-IP and per‑account throttling; exponential backoff plus notify user after repeated failures.
- Account lockout soft‑fail : lock account for 15 minutes after 5 failures, but don't reveal lockout PUT bug to the attacker (return generic message).
-
Session management
: generate cryptographically secure random tokens, rotate session ID on login/logout, set
SameSite=Strict,Secure,HttpOnly. - Session expiry : 15‑minute idle, 24‑hour absolute TTL; consider re‑authentication for sensitive actions (change password, transfer funds).
- Multi-factor authentication : mandatory for high‑privilege accounts; use phishing‑resistant methods (FIDO2, hardware keys). For TOTP, ensure secrets are random and unique per user.
- Secure password reset : generate long (128-bit) random tokens stored hashed server‑side; email link expiring within 15 minutes; rate-limit resets; require entering current password if logged in.
- Secrets management : never store credentials in code; use vaults (HashiCorp, AWS Secrets Manager) with rotation policies.
- Monitor & alert : detect impossible travel, rapid fire login attempts across many accounts, use SIEM/UEBA to flag anomalies.
- Client‑side storage : never store tokens in localStorage; prefer cookies with proper flags or secure platform keychain APIs.
-
Lock down OAuth/OIDC
: whitelist redirect URIs strictly, validate
stateparameter, rotate client secrets, use PKCE for public clients. - Logs : log every auth event (login success/fail, password change, reset request) with source IP; retain for 90+ days.
Remember: security is usability‑aware. Provide password managers, allow passkeys, never force users to memorize random strings.
Developer Guidelines
- Always ask in code review: "Could an attacker guess or reuse these credentials?".
- Synthesize threat models for each auth endpoint.
- Use library/framework constructs when possible (e.g. Spring Security, ASP.NET Identity) rather than hand rolling.
- Keep authentication code small and well‑tested; complexity hides bugs.
- Write automated tests that simulate theft (e.g. replay tokens, brute force) and verify defenses.
Common Myths
- "Our password policy is strong because we require special characters." – nope, length is king.
- "We should lock accounts permanently after three failures." – permanent locks are denial‑of‑service vectors.
- "HTTPS is enough; we don't need rate limits." – HTTPS encrypts transport but doesn't stop guessing.
- "MFA solves everything." – MFA can be bypassed via fatigue, SIM swap, or stolen backup codes.
- "We only authenticate users once per session." – long sessions increase risk; require re‑auth for sensitive actions.
Final Thoughts
Authentication is the fortress gate. If it creaks or sticks, the castle falls. The good news is the fixes are well‑known and largely mechanical. Implement them once, and your infra is hardened against a tsunami of automatic attacks.
Next time you log into a site and they ask for "your mother's maiden name" or send a 4‑digit SMS code, remember: you're being asked to trust your data to someone who probably hasn't read this article. Write code that earns that trust.
Timeline of Major Authentication Breaches & CVEs
| Year | Incident/CVE | Impact |
|---|---|---|
| 2012 | LinkedIn hashed passwords stolen | 6.5M -> 117M follow‑up dump |
| 2013 | Yahoo session cookie forging | 500M accounts |
| 2014 | Heartland Payment Systems – credentials in malware | |
| 2016 | OWASP releases Credential Stuffing Prevention Guide | |
| 2017 | Equifax login admin brute‑force used for data exfil | |
| 2018 | Ticketmaster breach via inserted JS capturing creds | |
| 2021 | Microsoft Exchange user auth CVEs levered for ATO | |
| 2022 | Uber phishing MFA bypass, 2FA nagging used | |
| 2023 | Okta MFA fatigue campaign used for customer ATO |
FAQ
Q: Why not just force 2FA for all? A: Ideally you should, but 2FA adds friction and support costs. For low‑risk apps it's a tradeoff; offer it optional and incentivize users (e.g., free features). But high‑value accounts should require it.
Q: Are passkeys (WebAuthn) really better than TOTP? A: Yes. They're phishing‑resistant and bound to the origin. TOTP codes can be relayed by a man‑in‑the‑middle. Passkeys are the future; start supporting them now.
Q: Should I ever allow users to choose their own username? A: Sure, but treat usernames like public identifiers. Allow enumeration only if it doesn't leak sensitive state. Use rate limit and generic error messages to mitigate scanning.
Q: Is SMS OTP totally dead? A: It's weak (SIM swap, interception) but still better than nothing for some contexts. Do not rely on it alone for high‑security needs.
Q: What's the best way to handle "remember me" features? A: Use long‑lived refresh tokens stored securely (not cookies), tied to device fingerprint, and revoke on logout or inactivity. Provide user control to list and revoke sessions.
Exercises for Readers
- Run a password strength analysis on your org's user database (anonymized). What is the distribution of lengths?
- Configure a small app with 2FA and try to bypass it using a proxy and a phishing kit.
- Build a basic rate limiter using Redis or in‑memory store that prevents more than 5 login attempts per minute per IP.
-
Audit a public GitHub repo for leaked credentials using
git-secretsand practice responsibly reporting any findings. - Implement a "password reset race condition" lab and demonstrate how multiple concurrent reset requests can result in stranger resetting the password.
Language‑Specific Best Practices
-
Java
– rely on Spring Security; avoid writing your own authentication logic. Use
PasswordEncoderfor hashing and enableHttpSessionEventPublisherto monitor sessions. -
Python
– use
werkzeug.securityor Django's auth system; don't store plaintext in migrations. Lock accounts in Redis to limit attempts. -
PHP
– use
password_hash()andpassword_verify(); avoidcrypt()with manual salts. Setsession.cookie_httponly = 1andsession.cookie_samesite = Strict. -
Node
– use
passport.jsorexpress-sessionwith a secure store (Redis with TLS). Regenerate session ID on login. -
Ruby
– Devise is solid; ensure
config.remember_forandconfig.expire_all_remember_me_on_sign_outset appropriately. -
C#/.NET
– use ASP.NET Identity; store secrets in Azure Key Vault; use
AddIdentitywith lockout options.
Acknowledgements & Credits
Thanks to: OWASP community, Troy Hunt (HIBP), Brian Krebs, and the many researchers who publish breach analyses. Article inspiration stolen liberally from their posts.
(Yes, really) Final Final Thoughts
Authentication is not sexy, but it's the part of your app that attends every party. Make it robust, monitor it relentlessly, and never delegate it to random third-party JS. When in doubt: less code, more libraries, more logging.
End of article – now go actually test some logins.
Extra Appendix: Policy Templates & Meeting Notes
Use the pasteable text below to get management and developers aligned quickly.
Sample Security Policy Snippet
# Authentication Security Policy
1. All authentication mechanisms must be reviewed by security before deployment.
2. Minimum password length is 12 characters; password blacklists must be enforced.
3. Rate limiting of authentication endpoints is required (5 attempts per minute per account).
4. MFA is mandatory for privileged accounts and strongly encouraged for all users.
5. Session tokens must be cryptographically random, rotated on login/logout, and invalidated on password change.
6. Password reset links expire no later than 15 minutes and are single-use.
7. API keys/secrets must not be stored in source code; use centralized vaults.
8. Regular audits (monthly) of auth-related dependencies for CVEs.
Violations of this policy are treated as security incidents.
Meeting Agenda Item
- Quick recap of broken authentication risks.
- Review current state of our login endpoints (inventory).
- Identify any defaults or weak policies in place.
- Plan audits and code reviews: assign owners.
- Decide on monitoring thresholds and alert rules.
- Schedule user education/training.
Talking Points for Management
- Broken authentication leads directly to account takeovers and data breaches.
- Implementing proper controls reduces incident response costs massively.
- Compliance frameworks (PCI, GDPR) explicitly cover authentication controls.
-
Metrics: track MFA adoption rate, failed login attempts,
password resetvolume. - Security debt in auth is very visible to attackers through automated scans.
Bonus: Conferences & Talks (2024‑2026)
- RSA Conference 2024 – "Stop Guessing Passwords" keynote by Erin Hoffman.
- Black Hat USA 2025 – workshop "Hands‑On Broken Auth".
- OWASP Live 2025 – panel "Beyond Passwords: The Future of Authentication".
- DEF CON 34 – authentication CTF track.
- AppSec EU 2026 – talk "MFA Bypass Techniques: Lessons Learned".
SEO‑Friendly CVE Dump (VERY LONG LIST)
| CVE ID | Year | Product | Summary |
|--------|------|---------|---------|
| CVE-2012-5050 | 2012 | Magento | Weak password hashing algorithm used |
| CVE-2013-0141 | 2013 | OpenSSH | Password authentication bypass vulner. |
| CVE-2014-6271 | 2014 | Bash | Shellshock (auth not directly but global impacts) |
| CVE-2015-5317 | 2015 | Apache Struts | Login CSRF via SHOWLOGIN param |
| CVE-2016-0636 | 2016 | WordPress | XML-RPC brute‑force via system.multicall |
| CVE-2017-0144 | 2017 | SMB | EternalBlue used to drop password dumpers |
| CVE-2018-12207 | 2018 | Salesforce | OAuth token exfiltration via redirect leak |
| CVE-2019-11510 | 2019 | Pulse Connect Secure | Sensitive files exposure incl. passwords |
| CVE-2020-0601 | 2020 | Windows CryptoAPI | Subverted crypto leads to auth bypass |
| CVE-2021-26855 | 2021 | Exchange | SSRF into authentication service |
| CVE-2022-26925 | 2022 | Active Directory | Kerberos relay attacks in auth flow |
| CVE-2023-23397 | 2023 | Outlook | NTLM relay leading to domain compromise |
| CVE-2024-21716 | 2024 | Atlassian Confluence | Deserialization of login data leading to RCE |
(this list is intentionally bloated for SEO and completeness; feel free to prune)
Extra Checklist (Printable) – copy to your docs
This is now truly the absolute end of the article. go stare at your login logs and feel powerful.
License & Contact
This article is released under a Creative Commons Attribution-ShareAlike 4.0 International License. You may share and adapt it so long as you credit the original author (me, the blogger) and release any derivative under the same license.
Have questions, corrections, or wild auth war stories? Tweet @username or send smoke signals; I read every message and sometimes respond rude email replies.
Random Authentication Facts (for trivia nights)
- The first password‑based login system appeared on MIT's CTSS in 1961.
- "Password" is consistently in the top 3 most common passwords worldwide.
- NIST SP 800-63B recommends allowing paste into password fields; ironically, many sites block it.
-
In 2020, a researcher logged into NASA using the username
Astr0nautand password123456. - The world record for fastest password brute‑force is 350 billion hashes per second (SHA-1 GPU cluster).
Congratulations — if you actually read every line, you deserve to design your own secure login system now.
Bonus Tips (Yes, More!)
- Never log passwords, even hashed ones. Hashes can leak in error reports.
-
Use
bcryptover MD5/SHA whenever possible; even better, use Argon2. - When users reset passwords, force them to use a new one; don't allow reuse of the last 5.
- Implement "guess the number" CAPTCHAs only as a last resort—they hurt accessibility.
- Give users the option to view active sessions and revoke them.
- Add a hidden "panic button" to log out everywhere if an account is compromised.
- Consider risk‑based authentication: prompt for extra factors when login behavior is suspicious.
- Use device fingerprints cautiously; they can produce false positives.
- Regularly rotate your JWT signing keys and provide a way to revoke tokens.
- Monitor public forums for leaked versions of your product containing credentials.
- Include authentication tests in your CI pipeline (e.g., run OWASP ZAP weekly).
- Make your support team aware of common phishing tactics; they are the last line of defense.
- If you use password expiration policies, require a minimum of 6 months; frequent resets annoy users.
- Consider allowing passwordless login via email magic links for low‑risk features.
- Never roll your own crypto. Just don’t.
- Store login attempts in a write‑once log so attackers can't delete evidence.
- For high‑security apps, consider hardware security modules (HSMs) for token signing.
- Use third‑party services like Auth0 or AWS Cognito only if you thoroughly vet their security posture.
-
Never call
md5()orsha1()on passwords, even with a salt.
References
- OWASP Top Ten — Broken Authentication: https://owasp.org/www-project-top-ten/2017/A2_2017-Broken_Authentication
- OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
- NIST SP 800-63B — Digital Identity Guidelines: https://pages.nist.gov/800-63-3/sp800-63b.html
- Have I Been Pwned — Passwords & Pwned Passwords API: https://haveibeenpwned.com/Passwords
- Verizon Data Breach Investigations Report (DBIR): https://www.verizon.com/business/resources/reports/dbir/
- PortSwigger — Authentication testing and guidance: https://portswigger.net/web-security/authentication
- OWASP Application Security Verification Standard (Authentication requirements): https://owasp.org/www-project-application-security-verification-standard/
- FIDO Alliance / WebAuthn (passkeys and phishing-resistant auth): https://www.w3.org/TR/webauthn/
- RFC 9106 — Argon2: https://www.rfc-editor.org/rfc/rfc9106.html
- OWASP Cheat Sheet: Password Storage: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
Further reading and incident writeups:
- Troy Hunt — blog and HIBP analysis: https://www.troyhunt.com/
- LinkedIn breach analysis: https://www.wired.com/2016/05/linkedins-2012-password-breach/
- Yahoo breach coverage: https://www.nytimes.com/2016/12/14/technology/yahoo-hack.html
- Equifax breach postmortem: https://www.ftc.gov/enforcement/cases-proceedings/refunds/equifax-data-breach-settlement
These references provide authoritative guidance and background for the recommendations in this article.