Loading...
Loading...
Backend interview guide
Authentication, authorization, JWT, password hashing and securing endpoints. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
60 questionsLast updated
Authentication proves who the caller is, for example by checking a password or a token. Authorisation then decides what that known caller may do, for example "only admins can change prices". Authentication comes first, because the rules need an identity to check against.
Lesson: Authentication vs authorisationIt is an old naming mistake in the HTTP standard that was never changed, because changing it would break clients. In practice everyone reads 401 as "unauthenticated" and 403 as "unauthorised".
Lesson: Authentication vs authorisationThe principal is the caller the server accepted after authentication, such as "customer 1, Asha". A permission is one allowed action, like reading orders or changing a price. A role, like CUSTOMER or ADMIN, is a named group of permissions, so you assign one role instead of many permissions.
Lesson: Authentication vs authorisationBoth are defensible, but I would prefer 404. A 403 confirms that the order exists, while 404 treats someone else's order as if it does not exist for this caller. Either way the data itself must never be returned.
Lesson: Authentication vs authorisationNo. Hand-written escaping is easy to get wrong across character sets and databases, and one missed place is enough. Parameterised queries remove the problem completely because values never become SQL text.
Lesson: Common attacks: stolen passwords, injection, broken access controlThey make guessing harder, but ids leak through links, logs and shared screenshots. Without an owner check, anyone who gets the id still gets the data, so random ids are at most an extra layer, never the fix.
Lesson: Common attacks: stolen passwords, injection, broken access controlI list every input: path, query, headers, cookies and body. For each I ask what happens if it is someone else's id, empty, huge or malformed, and whether it ends up in a query, command or page. Each answer becomes a check: owner check, validation, parameterised query or encoding.
Lesson: Common attacks: stolen passwords, injection, broken access controlEncoding changes the format, like Base64, and anyone can reverse it, so it gives no secrecy. Encryption hides data with a key and only a key holder can reverse it. Hashing makes a fixed-length fingerprint that cannot be reversed at all; you can only hash again and compare.
Lesson: Hashing vs encryption vs encodingThe server only needs to check a password, never read it back, so a one-way hash is enough. With encryption there is a key, and anyone who steals the database together with that key gets every password in plain text. With a hash there is nothing to decrypt.
Lesson: Hashing vs encryption vs encodingAsymmetric keys are used at the start: the server proves its identity with its certificate, and both sides use public-key maths to agree on a fresh shared key. All the actual data then uses fast symmetric encryption with that shared key.
Lesson: Hashing vs encryption vs encodingYes. The data in a signed token is only Base64-encoded, so anyone holding it can decode and read it. That means a token should carry only things like the user id, role and expiry time, never a password or other secret.
Lesson: Hashing vs encryption vs encodingEvery request carries something valuable: a password at login and a token on every call after that. Over plain HTTP anyone on the path can read both and act as the user, and can also change responses. HTTPS encrypts and checks every byte, so the format of the data does not matter.
Lesson: HTTPS and why tokens need itIt means the connection is encrypted and the server proved, with a CA-signed certificate, that it owns the exact domain in the address bar. It says nothing about whether the site is honest; phishing sites with look-alike names get valid certificates too.
Lesson: HTTPS and why tokens need itHTTPS protects the URL on the network, but URLs are stored in many other places: browser history, bookmarks, server and proxy access logs, and sometimes the Referer header sent to other sites. A token there leaks to anyone who reads those. Headers are not stored in those places by default.
Lesson: HTTPS and why tokens need itIt depends on the private network. Many systems send plain HTTP between the load balancer and the app servers inside a closed network, which is common and accepted. Stricter setups encrypt that hop too. Either way the app servers should accept traffic only from the load balancer.
Lesson: HTTPS and why tokens need itBroken access control. A simple example is a customer changing the order id in /api/v1/orders/14 and seeing someone else's order because the server never checks the owner. The fix is to check the role and owner on every request, or to use /me URLs where the server takes the user from the token.
It means that when something unexpected happens, such as a permission check timing out, the program denies the action instead of allowing it. Failing open turns every outage into a security hole. A global error handler that returns a plain error and a transaction that undoes partial changes are the usual tools.
Lesson: The OWASP Top 10 at a glanceServer-side request forgery is when a feature makes the server fetch a URL that a user supplied, and the user points it at an internal address the server can reach. I would allow only https URLs from an allow-list of hosts, refuse private addresses after resolving the name, and never pass the raw response back to the user.
Lesson: The OWASP Top 10 at a glanceI ask whether logging in again could change the answer. If the request has no valid credential, it is 401 and comes with a WWW-Authenticate header. If the caller is known but lacks permission, it is 403, because a new login with the same account would get the same answer.
Base64 is an encoding, not encryption: anyone can decode it with one command, so the key is effectively public. Secrets should never be in Git at all; they belong in environment variables or a secret store. Since the key was committed, it must also be replaced, not just removed.
Lesson: Practice: security basicsThat it runs only over HTTPS, that passwords are checked against slow salted hashes, and that the error is the same for an unknown email and a wrong password. Then that failed attempts are limited and logged without the password, and that the token it returns is short-lived.
Lesson: Practice: security basicsI use the what, why, how and example order with everyday pictures: a bearer token is like a metro token that works for whoever holds it, and HTTPS is a sealed envelope instead of a postcard. Then I say what we will change and what it protects.
Lesson: Practice: security basicsBoot's auto-configuration installs a security filter chain that requires authentication for every request, with HTTP Basic and a form login. It creates one in-memory user called user and prints a random password at startup. Only the health endpoint stays open, and responses get safety headers such as X-Frame-Options: DENY.
Only over HTTPS. The header is just Base64 of username:password, which anyone can decode, and it is sent on every request. Over HTTPS the whole request is encrypted, so Basic is fine for internal tools and testing, but public APIs usually use short-lived tokens instead.
I set spring.security.user.name, password and roles in application-dev.properties, with the password as ${SHOPLITE_DEV_PASSWORD} and no default. Each developer sets the variable in their terminal; if it is missing the app does not start. In prod that file is not read, and real users come from the database.
No. The failures show the tests now run through the real security layer, which is good. I import my security configuration into the slice test and use @WithMockUser for calls that need a signed-in user, and I add tests that prove which endpoints return 401.
A filter runs in front of every controller, so a rule cannot be forgotten on one endpoint, and unauthenticated requests never reach application code at all. It also keeps controllers about business logic only.
Lesson: The security filter chainI turn on logging.level.org.springframework.security=TRACE, repeat the request and find its Securing ... line. Then I follow the filters until one stops it. A common case is the CSRF filter rejecting a POST, after which the forwarded /error request is denied with 401.
To avoid account enumeration. If the messages differed, an attacker could test emails and learn which ones have accounts, then focus password guessing on them. One generic message gives nothing away.
Lesson: The security filter chainI declare a @Bean method that takes HttpSecurity, describe the rules with the lambda DSL (authorizeHttpRequests, httpBasic, csrf, sessionManagement) and return http.build(). There is no WebSecurityConfigurerAdapter any more, and every part takes a lambda or Customizer.withDefaults().
When the API is stateless and clients send credentials explicitly in a header, such as Authorization: Basic or Bearer, never in a cookie. A forged cross-site request cannot add that header. If users sign in with a session cookie, CSRF protection must stay on.
First whether any sign-in method is configured: with only authorizeHttpRequests there is no authentication filter, so the password is never read. Then I check the filter list or a TRACE log to see which filter decided.
I skip to the last Caused by, which usually says the problem in plain words, like "Can't configure requestMatchers after anyRequest". Then the first stack line from my own package gives the file and line in the config. Failing at startup is good: broken rules never reach production.
In a SecurityContext held by SecurityContextHolder, which by default uses a ThreadLocal, so each request thread has its own. The context holds an Authentication with the principal, credentials and authorities. Filters set it at the start of the request and clear it at the end.
The principal is who the user is, usually a UserDetails. The credentials prove it, like a password, and Spring erases them after sign-in. The authorities are the permissions as strings, with roles prefixed ROLE_.
I prefer not to. The controller reads the user once and passes an id, so the service has no hidden input and works the same from a scheduled job, a message listener or a unit test. It also keeps security concerns at the edge of the application.
Lesson: SecurityContext and the current userAn authority Spring Security 7 adds when a user authenticates with a password. It records which factor was used, so rules can require several factors, such as a password plus a one-time code. For plain role checks it can be ignored.
Lesson: SecurityContext and the current userThe rules are checked from top to bottom and the first match decides. A broad rule above a specific one hides it, so specific rules go first and anyRequest() last. Spring only fails at startup for rules after anyRequest(), so I rely on tests for the rest.
Spring Boot forwards errors raised outside controllers to /error, and that forward runs through the security chain again. If /error needs authentication, guests see 401 instead of the real 400 or 404, which hides the actual problem.
No. The request still passes the whole filter chain, so the firewall, safety headers and the authentication filters still run; permitAll only means the access check allows anonymous callers. The controller still validates input and returns its normal errors.
Lesson: Permitting public endpointsTomcat calls DelegatingFilterProxy, which hands the request to FilterChainProxy. It picks the first matching SecurityFilterChain and runs its filters: context setup, CORS, CSRF, authentication (for example Basic), anonymous, ExceptionTranslationFilter and AuthorizationFilter. If no filter stops it, the request reaches the DispatcherServlet and the controller.
The authentication filter stores an Authentication in the SecurityContext, which SecurityContextHolder keeps in a thread-local for that request. My controller takes it with @AuthenticationPrincipal or an Authentication parameter and passes a plain user id to the service. The context is cleared when the request ends.
All rules live in one class with comments, ordered from specific to general with anyRequest() last. Slice tests with @WebMvcTest and the imported config check each public endpoint and each protected area, so a reordering or a missing method fails the build.
A rule that never decides anything because an earlier, broader rule matches all its requests, like a health permitAll below /actuator/** authenticated. Spring starts without complaint, so I catch it with tests that expect 200 on the public endpoint, or by reading the list top to bottom like an if-else chain.
A users table with a unique login (e-mail), a password hash column, an enabled flag and timestamps, and a separate user_role table with one row per role and a CHECK on the allowed values. In ShopLite the account table links to customers with a nullable unique customer_id, because staff accounts have no customer.
Lesson: User entity and roles tableIt maps a collection of simple values, like role names, into a table keyed by the owner's id; the values have no identity of their own. If each role needed its own data, such as a description or permissions, I would make Role an entity and use a many-to-many link instead.
Lesson: User entity and roles tableJackson serialises every getter, so the password hash goes to the client, and lazy fields either trigger queries or fail with a 500 once the session is closed. I return a record such as UserResponse from the service instead.
Lesson: User entity and roles tableWith a new Flyway migration, V4 in ShopLite, never by editing an applied one. Flyway runs it once on each database and records it in flyway_schema_history, and ddl-auto=validate then checks that the entities match.
Lesson: User entity and roles tableNo. It only loads the user. DaoAuthenticationProvider checks the flags and calls passwordEncoder.matches(typed, storedHash) afterwards, so the hashing algorithm stays out of my service.
It lets anyone check which e-mails have accounts, which helps phishing and targeted guessing. Spring hides UsernameNotFoundException behind the same "Bad credentials" and even checks a dummy hash so the timing matches.
At what loadUserByUsername touches after the repository returned, usually the lazy roles. I load them in the same query with @EntityGraph or a fetch join instead of switching on open-in-view.
With a PasswordEncoder bean, usually PasswordEncoderFactories.createDelegatingPasswordEncoder(), which stores {bcrypt} hashes. I call encode at sign-up and store the result; Spring Security calls matches at sign-in. I never write my own hashing or store the password.
Lesson: PasswordEncoder and BCryptSHA-256 is designed to be fast, so an attacker with a graphics card can try billions of guesses per second against a leaked table. BCrypt and Argon2 are slow on purpose and tunable, and they include a salt.
Lesson: PasswordEncoder and BCryptIt tells DelegatingPasswordEncoder which algorithm made the hash. That lets you switch new hashes to Argon2 later while old BCrypt hashes keep working, and upgradeEncoding tells you which rows to re-hash at the next sign-in.
Lesson: PasswordEncoder and BCryptNo, it is one-way by design. The fix is a reset flow: send a single-use, short-lived link, let the user choose a new password, and store its new hash.
Lesson: PasswordEncoder and BCryptPOST /api/v1/auth/register is public. The body is a SignUpRequest record with Bean Validation, so bad input is a 400 that lists every field. UserService normalises the e-mail, saves the customer and a CUSTOMER account with a BCrypt hash in one transaction, and the controller returns 201 with a Location header and a UserResponse that has no password field.
Lesson: Sign-up endpoint with validationThe service owns the business operation: it decides the role, creates both rows and runs in one transaction. The controller only maps HTTP to the request record and back, so security-sensitive code stays in one tested place.
Lesson: Sign-up endpoint with validationSo one person has one account however they type their address, and so sign-up and sign-in agree. I trim and lower-case with Locale.ROOT in both places; without a locale, Turkish rules turn I into a dotless ı and the lookup breaks.
Lesson: Sign-up endpoint with validationYes, the generated toString lists every component, so logging the request would print the password. I override toString to mask it, and I avoid logging request bodies at all.
Lesson: Sign-up endpoint with validationTwo requests can both run the check before either inserts, so both see no row and both insert. The result depends on timing, which is why it shows up under load or double clicks and almost never in a manual test.
Lesson: Handling duplicate emailsEach lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson