Real World Example

ShopEase Login End to End

Putting it all together: customers sign up, their password is BCrypt-hashed, and every protected API call is checked against the database. This is the same pattern used behind the login of most Indian e-commerce and fintech backends before they add OTP and JWT.

POST /api/auth/register  {email, password}
   -> AuthController -> RegistrationService
   -> passwordEncoder.encode(password)  -> users table ($2a$12$...)

GET /api/orders   Authorization: Basic bmVoYUBnbWFpbC5jb206...
   -> BasicAuthenticationFilter
   -> ProviderManager -> DaoAuthenticationProvider
        -> ShopEaseUserDetailsService.loadUserByUsername("[email protected]")  (SQL: SELECT ... WHERE email=?)
        -> passwordEncoder.matches(raw, hash)
   -> SecurityContextHolder holds neha [ROLE_CUSTOMER]
   -> AuthorizationFilter: anyRequest().authenticated()  OK
   -> OrderController.myOrders(@AuthenticationPrincipal UserDetails user)

Seeding the First Admin

Nobody can register as ADMIN, so the first admin is created at startup from environment variables (never hard-coded):

@Component
public class AdminSeeder implements ApplicationRunner {

    private final UserRepository users;
    private final PasswordEncoder passwordEncoder;
    private final String adminEmail;
    private final String adminPassword;

    public AdminSeeder(UserRepository users, PasswordEncoder passwordEncoder,
                       @Value("${shopease.admin.email}") String adminEmail,
                       @Value("${shopease.admin.password}") String adminPassword) {
        this.users = users;
        this.passwordEncoder = passwordEncoder;
        this.adminEmail = adminEmail;
        this.adminPassword = adminPassword;
    }

    @Override
    public void run(ApplicationArguments args) {
        if (!users.existsByEmail(adminEmail)) {
            users.save(new User(adminEmail, passwordEncoder.encode(adminPassword), "ADMIN"));
        }
    }
}

Start With Secrets From the Environment

export [email protected]
export SHOPEASE_ADMIN_PASSWORD='Use-A-Long-Random-Secret-42'
java -jar shopease.jar

Example output

INFO ... Started ShopEaseApplication in 3.1 seconds

Order History for the Logged-in User

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orders;

    public OrderController(OrderService orders) { this.orders = orders; }

    @GetMapping
    public List<OrderSummary> myOrders(@AuthenticationPrincipal UserDetails user) {
        // the email comes from the SecurityContext, never from a request parameter
        return orders.findByCustomerEmail(user.getUsername());
    }
}

Example

Each customer sees only their own orders:

curl -s -u [email protected]:Neha@2024secure localhost:8080/api/orders
curl -s -u [email protected]:asha123 localhost:8080/api/orders

Example output

[]
[{"orderId":5012,"status":"PLACED","total":1598.0,"placedOn":"2024-11-08"}]

Tip: Take the user from @AuthenticationPrincipal, never from ?userId=. Trusting a user id from the request lets one customer read another customer's orders (an IDOR bug, one of the most common real-world API vulnerabilities).