Careeroza
Real World Example
Where You See This
- Fintech and banking apps often run their own authorization server because regulators require user data to stay in-house.
- Multi-brand retailers (one login across grocery, fashion and electronics apps) run a central identity service — "one account for all our apps".
- SaaS products offering "Login with OurProduct" to partners build an OAuth2 provider much like this.
ShopEase Identity: auth.shopease.in
| Client | Type | Grants | Scopes |
|---|---|---|---|
| shopease-web (React) | public, PKCE | authorization_code, refresh_token | openid profile orders.read orders.write |
| shopease-android | public, PKCE | authorization_code, refresh_token | openid profile orders.read orders.write |
| seller-portal | confidential (BFF) | authorization_code | openid seller.catalog seller.orders |
| order-service | confidential | client_credentials | payments.write inventory.reserve |
| nightly-reports | confidential | client_credentials | reports.read |
Signing keys are loaded from a keystore stored in a secret manager and rotated every 90 days. During rotation the JWKS publishes both keys so tokens signed with the old key stay valid until they expire:
@Bean
JWKSource<SecurityContext> jwkSource(KeyStoreKeyLoader loader) {
RSAKey current = loader.load("shopease-2026-03"); // signs new tokens (first key is used)
RSAKey previous = loader.load("shopease-2025-12"); // still published for verification
return new ImmutableJWKSet<>(new JWKSet(List.of(current, previous)));
}JWKS during rotation
Resource servers pick the right key using the kid header:
curl -s http://auth.shopease.in/oauth2/jwks | jq ".keys[] | {kid, kty, alg: .alg}"Example output
{ "kid": "shopease-2026-03", "kty": "RSA", "alg": null }
{ "kid": "shopease-2025-12", "kty": "RSA", "alg": null }