Problem: Authenticate Users with Configurable Login and Tokens

Hard
40 min
Build a flexible authentication flow where one strategy verifies user identity and another issues tokens, both of which are configurable at runtime.

Problem statement

You’re designing a new authentication system for your platform. It must support multiple login methods (password, OTP, Google) and multiple token types (session, JWT, encrypted).

Authentication systems often combine login and token generation logic in a single file, making them difficult to extend and maintain as new methods are introduced. A typical implementation might look like this:

if (method === 'password') verifyPassword();
else if (method === 'otp') verifyOtp();
else if (method === 'google') verifyGoogle();
if (tokenType === 'jwt') issueJwt();
else if (tokenType === 'session') issueSession();

The design doesn’t scale—every time you add or modify a login or token type, you have to change core logic.

You’ve decided to use the Strategy Pattern twice:

  • One family of strategies for authentication

  • Another for token generation

The base contexts (AuthService and TokenService) and all token strategies are already implemented. Your task is to implement the authentication strategies and a LoginController that orchestrates both. The controller should handle the complete workflow: authenticate → issue token.

Goal

Implement the missing pieces to complete the dual-strategy system.

  1. Auth strategies:

    1. PasswordAuth: Simulate verifying { username, password }.

    2. OtpAuth: Simulate verifying { phone, otp }.

    3. GoogleAuth: Simulate verifying { token }.

    4. Each defines an async .authenticate(credentials) method that returns true or false.

  2. LoginController:

    1. Accepts both contexts (AuthService and TokenService) via constructor.

    2. Implements async login(credentials) that authenticates the user, then issues a token if authentication succeeds.

Constraints

  • Do not modify AuthService or TokenService.

  • In authentication strategies, simulate verification as follows:

    • PasswordAuth: Return true only if password === 'secret'.

    • OtpAuth: Return true only if otp === 1234.

    • GoogleAuth: Return true only if token starts with 'valid-'.

  • Use setTimeout to simulate async verification in auth strategies.

  • Use Math.random().toString(36).slice(2, 8) for random IDs.

  • Return clear console logs showing which strategies are active.

Sample output

The examples below illustrate what the output should look like:

(async () => {
const authService = new AuthService(new PasswordAuth());
const tokenService = new TokenService(new JwtToken());
const controller = new LoginController(authService, tokenService);
await controller.login({ username: 'alice', password: 'secret' });
/* Expected output (may vary):
[PasswordAuth] Authenticating alice...
[JwtToken] Issued token: jwt.YWxpY2UuMTYzNQ.fakeSig
*/
authService.setStrategy(new OtpAuth());
tokenService.setStrategy(new EncryptedToken());
await controller.login({ phone: '+15551234', otp: 1234 });
/* Expected output (may vary):
[OtpAuth] Verifying +15551234 with code 1234...
[EncryptedToken] Issued token: enc::eyJuYW1lIjoiYm9iIn0=
*/
authService.setStrategy(new GoogleAuth());
tokenService.setStrategy(new SessionToken());
await controller.login({ token: 'valid-xyz' });
/* Expected output (may vary):
[GoogleAuth] Checking Google token: valid-xyz...
[SessionToken] Issued token: sess-4f2acb
*/
})();

Good luck trying the problem! If you’re unsure how to proceed, check the “Solution” tab above.

Problem: Authenticate Users with Configurable Login and Tokens

Hard
40 min
Build a flexible authentication flow where one strategy verifies user identity and another issues tokens, both of which are configurable at runtime.

Problem statement

You’re designing a new authentication system for your platform. It must support multiple login methods (password, OTP, Google) and multiple token types (session, JWT, encrypted).

Authentication systems often combine login and token generation logic in a single file, making them difficult to extend and maintain as new methods are introduced. A typical implementation might look like this:

if (method === 'password') verifyPassword();
else if (method === 'otp') verifyOtp();
else if (method === 'google') verifyGoogle();
if (tokenType === 'jwt') issueJwt();
else if (tokenType === 'session') issueSession();

The design doesn’t scale—every time you add or modify a login or token type, you have to change core logic.

You’ve decided to use the Strategy Pattern twice:

  • One family of strategies for authentication

  • Another for token generation

The base contexts (AuthService and TokenService) and all token strategies are already implemented. Your task is to implement the authentication strategies and a LoginController that orchestrates both. The controller should handle the complete workflow: authenticate → issue token.

Goal

Implement the missing pieces to complete the dual-strategy system.

  1. Auth strategies:

    1. PasswordAuth: Simulate verifying { username, password }.

    2. OtpAuth: Simulate verifying { phone, otp }.

    3. GoogleAuth: Simulate verifying { token }.

    4. Each defines an async .authenticate(credentials) method that returns true or false.

  2. LoginController:

    1. Accepts both contexts (AuthService and TokenService) via constructor.

    2. Implements async login(credentials) that authenticates the user, then issues a token if authentication succeeds.

Constraints

  • Do not modify AuthService or TokenService.

  • In authentication strategies, simulate verification as follows:

    • PasswordAuth: Return true only if password === 'secret'.

    • OtpAuth: Return true only if otp === 1234.

    • GoogleAuth: Return true only if token starts with 'valid-'.

  • Use setTimeout to simulate async verification in auth strategies.

  • Use Math.random().toString(36).slice(2, 8) for random IDs.

  • Return clear console logs showing which strategies are active.

Sample output

The examples below illustrate what the output should look like:

(async () => {
const authService = new AuthService(new PasswordAuth());
const tokenService = new TokenService(new JwtToken());
const controller = new LoginController(authService, tokenService);
await controller.login({ username: 'alice', password: 'secret' });
/* Expected output (may vary):
[PasswordAuth] Authenticating alice...
[JwtToken] Issued token: jwt.YWxpY2UuMTYzNQ.fakeSig
*/
authService.setStrategy(new OtpAuth());
tokenService.setStrategy(new EncryptedToken());
await controller.login({ phone: '+15551234', otp: 1234 });
/* Expected output (may vary):
[OtpAuth] Verifying +15551234 with code 1234...
[EncryptedToken] Issued token: enc::eyJuYW1lIjoiYm9iIn0=
*/
authService.setStrategy(new GoogleAuth());
tokenService.setStrategy(new SessionToken());
await controller.login({ token: 'valid-xyz' });
/* Expected output (may vary):
[GoogleAuth] Checking Google token: valid-xyz...
[SessionToken] Issued token: sess-4f2acb
*/
})();

Good luck trying the problem! If you’re unsure how to proceed, check the “Solution” tab above.

Node.js
// --- Contexts (Given) ---
class AuthService {
constructor(authStrategy) {
this.authStrategy = authStrategy;
}
setStrategy(authStrategy) {
this.authStrategy = authStrategy;
}
async authenticate(credentials) {
if (!this.authStrategy) throw new Error('No auth strategy set');
return this.authStrategy.authenticate(credentials);
}
}
class TokenService {
constructor(tokenStrategy) {
this.tokenStrategy = tokenStrategy;
}
setStrategy(tokenStrategy) {
this.tokenStrategy = tokenStrategy;
}
issue(user) {
if (!this.tokenStrategy) throw new Error('No token strategy set');
return this.tokenStrategy.issue(user);
}
}
// --- Token Strategies (Given) ---
class SessionToken {
issue(user) {
const token = `sess-${Math.random().toString(36).slice(2, 8)}`;
console.log(`[SessionToken] Issued token: ${token}`);
return token;
}
}
class JwtToken {
issue(user) {
const encoded = Buffer.from(`${user.name}.${Date.now()}`).toString('base64');
const token = `jwt.${encoded}.fakeSig`;
console.log(`[JwtToken] Issued token: ${token}`);
return token;
}
}
class EncryptedToken {
issue(user) {
const base64 = Buffer.from(JSON.stringify(user)).toString('base64');
const token = `enc::${base64}`;
console.log(`[EncryptedToken] Issued token: ${token}`);
return token;
}
}
// --- Auth Strategies (implement these) ---
class PasswordAuth {}
class OtpAuth {}
class GoogleAuth {}
// --- Orchestrator (implement this too) ---
class LoginController {
constructor(authService, tokenService) {}
async login(credentials) {}
}
// Example usage
(async () => {
const authService = new AuthService(new PasswordAuth());
const tokenService = new TokenService(new JwtToken());
const controller = new LoginController(authService, tokenService);
await controller.login({ username: 'alice', password: 'secret' });
/* Expected output (may vary):
[PasswordAuth] Authenticating alice...
[JwtToken] Issued token: jwt.YWxpY2UuMTYzNQ.fakeSig
*/
authService.setStrategy(new OtpAuth());
tokenService.setStrategy(new EncryptedToken());
await controller.login({ phone: '+15551234', otp: 1234 });
/* Expected output (may vary):
[OtpAuth] Verifying +15551234 with code 1234...
[EncryptedToken] Issued token: enc::eyJuYW1lIjoiYm9iIn0=
*/
authService.setStrategy(new GoogleAuth());
tokenService.setStrategy(new SessionToken());
await controller.login({ token: 'valid-xyz' });
/* Expected output (may vary):
[GoogleAuth] Checking Google token: valid-xyz...
[SessionToken] Issued token: sess-4f2acb
*/
})();