Answer withdrawn for now.
I'm leaving it up in case somebody else finds the question expecting an answer to the question I thought it was asking.
I thought you were talking about a client+server+external-server model, where the user needs the server to act on their behalf when the user isn't logged in, and the action the server takes requires accessing a third-party data store with the user's password. It seems this is mistaken.
If you're worried about somebody compromising your server (either an external attacker or a malicious / compromised insider), one option to mitigate that risk is to use a tokenizer service. You create a separate server (typically but not always a web service), with a very restricted API. This tokenizer server generally only allows one request from the Internet at large: store a secret (such as a password). The caller supplies a password to the tokenizer, and the tokenizer returns a unique, completely random, opaque blob of sufficient length (usually at least 128 bits) to the caller. The caller then passes that opaque blob to your main API server, in place of their actual password.
When your main service wants to use the user's password, it can't access it directly. The tokenizer will never expose a secret to any except for a specifically delimited list of external endpoints (the consumers of the secrets, i.e. the services that the passwords are used to access). The API server takes the user's opaque blob (the one generated by the tokenizer) and passes it - along with a request that the service wants to send to an external endpoint - to the tokenizer using a private, secure, internal-only endpoint of the tokenizer. The tokenizer takes the request from the main service and looks for the opaque blob (the "token") in its internal DB (which is separate from, and not accessible by, the main service!). If the token is present, the tokenizer uses a key (typically stored in a vault / key management service, and also not accessible to the main service) to decrypt the corresponding secret (password). The tokenizer then replaces the opaque blob token in the request with the actual password and, if the request is for an approved destination, relays the now-password-including request to the external service. When the response comes back to the tokenizer, it relays that response to the main service.
The tokenizer service needs to be extremely locked down. No one person is allowed to deploy code to it, nobody is allowed to log into it, it has the bare minimum of functionality and ideally no external libraries, etc. The idea here is that, even if the main service is completely compromised, the attacker can't get into the Tokenizer and thus steal the passwords. They might still be able to use the passwords by relaying through the tokenizer, but if all they get is a dump of the main DB and key storage, that's still not enough because the tokenizer won't relay requests from anybody else.
In pseudocode:
Main API server:
[@WebService, scope=Internet]
void ProcessStoreTokenizedPassword(Request req, Response res) {
User u = req.GetUser()
// If no authenticated user, fail
(TokenBlob tb, ExternalService es) = req.Parse<StoreTokenizedPassword>()
// If request formatted incorrectly, fail
mainApiDb.TokenizedPasswordsTable.Insert(u, tb, es)
}
[@WebService, scope=Internet]
void ProcessDoOperationOnExternalService(Request req, Response res) {
User u = req.GetUser()
// If no authenticated user, fail
(ExternalService es, Operation o) = req.Parse<ActionOnExternalService>()
// If request formatted incorrectly, fail
TokenBlob tb = mainApiDb.tokenizedPasswordsTable.Select(u, es)
// If no stored token for that user to that external service, fail
// Create a request template for the tokenizer
TemplateRequest tr = new TemplateRequest(es, o, tb)
// Get the tokenizer to relay the request out, and the response back
Response externalRes = tokenizerService.Proxy(tr)
// Do stuff with the response, or if relaying failed for some reason, fail
}
Tokenizer service
[@WebService, scope=Internet]
void ProcessCreateToken(Request req, Response res) {
// Note that we don't care about user auth; anybody can call this
(Secret s, ExternalService es) = req.Parse<CreateToken>()
// If either value missing or invalid, fail
// Generate a random token blob, encrypt the password, and store them
TokenBlob tb = new TokenBlob(secureRandom.Generate(256))
EncryptedBlob eb = s.Encrypt(tokenizerKey)
tokenizerDb.encryptedPasswordsTable.Insert(tb, eb, es)
// Return the token
res.Send(tb)
}
[@WebService, scope=Private]
void ProcessRelayOperation(Request req, Response res) {
// Verify that the request comes from the main API service, else fail
TemplateRequest tr = req.Parse<RelayOperation>()
// If the request is malformed, fail
ExternalService es = tr.es
Operation o = tr.o
TokenBlob tb = tr.tb
// Get the password corresponding to this token blob from the DB
EncryptedBlob eb = tokenizerDb.encryptedPasswordsTable.Select(tb, es)
// If there's no encrypted password for that token,
// or it wasn't created for use with this external service, fail
Secret s = eb.Decrypt(tokenizerKey)
// Replace all instances of the token blob in the operation with the real password
o.Replace(tb, s)
Request extReq = new Request(es)
Response extRes = extReq.Send(o)
// Relay the result of the external call - succeed or fail - to the main server
// But first make sure the secret didn't get reflected in the response for some reason
extRes.Replace(s, tb)
res.Send(extRes)
}
Note that I'm still eliding a lot of stuff, like exactly where the tokenizerKey is stored and how it's protected. Basically follow the advice in other answers, except for this key it's scoped only to this one super-limited service. You might also have a few other internal endpoints to do things like delete tokens that are no longer needed (e.g. because the user deleted their account from the main service or rotated their credential for a given external service). You might also want a cleanup function that deletes tokens which aren't in the main service DB, in case somebody tries to spam the tokenizer with a bunch of junk tokens.