Question
Basic task I want to do: Provide a Authenticate
service in gRPC server that
all clients call (and supply user name and password) initially to obtain a
authorization token (say JWT). Next, when other service calls are made by the
client, the token should be verified.
This can be accomplished in Java APIs easily using ServerInterceptor
and
ClientInterceptor
interfaces. In ServerInterceptor
I can check which
service is called and decide whether to allow or deny the call. On the
ClientInterceptor
side I can add the authorization token as metadata to
every service call.
There is this AuthMetadataProcessor
abstract class in C++. But not sure how
to accomplish the task similar to Java APIs. Is there a way to do similar
things in C++ APIs ?
Answer
Yes. You need to subclass AuthMetadataProcessor
, override its Process
method and register an instance of the derived type with your service. Once
that is done, all method invocations will be intercepted by Process
and it
will be given the client metadata sent with the request.
Your implementation of Process
must decide whether authentication is
required for the intercepted method (i.e., cannot be required for your
Authenticate
method, but will be required for various subsequently invoked
methods). This can be done by examining the :path
metadata key, as
documented in issue #9211, which
is a trusted value designating the intercepted method.
Your implementation of Process
must decide whether the token is supplied in
the request and is valid. This is an implementation detail, but generally
Process
refers to a store of valid tokens generated by Authenticate
. Which
is probably how you have it set up in Java already.
Unfortunately, one cannot register an AuthMetadataProcessor on top of insecure credentials, meaning that you will have to use SSL, or else attempt to intercept methods differently.
The framework also provides convenience functionality allowing you to work
with a peer identity property. Process
can call AddProperty
on the
authentication context, providing the identity implied by the token, followed
by SetPeerIdentityPropertyName
. The invoked method can then access the
information using GetPeerIdentity
and avoid remapping tokens to identities.
AuthMetadataProcessor Implementation Example
struct Const
{
static const std::string& TokenKeyName() { static std::string _("token"); return _; }
static const std::string& PeerIdentityPropertyName() { static std::string _("username"); return _; }
};
class MyServiceAuthProcessor : public grpc::AuthMetadataProcessor
{
public:
grpc::Status Process(const InputMetadata& auth_metadata, grpc::AuthContext* context, OutputMetadata* consumed_auth_metadata, OutputMetadata* response_metadata) override
{
// determine intercepted method
std::string dispatch_keyname = ":path";
auto dispatch_kv = auth_metadata.find(dispatch_keyname);
if (dispatch_kv == auth_metadata.end())
return grpc::Status(grpc::StatusCode::INTERNAL, "Internal Error");
// if token metadata not necessary, return early, avoid token checking
auto dispatch_value = std::string(dispatch_kv->second.data());
if (dispatch_value == "/MyPackage.MyService/Authenticate")
return grpc::Status::OK;
// determine availability of token metadata
auto token_kv = auth_metadata.find(Const::TokenKeyName());
if (token_kv == auth_metadata.end())
return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Missing Token");
// determine validity of token metadata
auto token_value = std::string(token_kv->second.data());
if (tokens.count(token_value) == 0)
return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Invalid Token");
// once verified, mark as consumed and store user for later retrieval
consumed_auth_metadata->insert(std::make_pair(Const::TokenKeyName(), token_value)); // required
context->AddProperty(Const::PeerIdentityPropertyName(), tokens[token_value]); // optional
context->SetPeerIdentityPropertyName(Const::PeerIdentityPropertyName()); // optional
return grpc::Status::OK;
}
std::map<std::string, std::string> tokens;
};
AuthMetadataProcessor Setup within Secure Service
class MyServiceImplSecure : public MyPackage::MyService::Service
{
public:
MyServiceImplSecure(std::string _server_priv, std::string _server_cert, std::string _ca_cert) :
server_priv(_server_priv), server_cert(_server_cert), ca_cert(_ca_cert) {}
std::shared_ptr<grpc::ServerCredentials> GetServerCredentials()
{
grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp;
pkcp.private_key = server_priv;
pkcp.cert_chain = server_cert;
grpc::SslServerCredentialsOptions ssl_opts;
ssl_opts.pem_key_cert_pairs.push_back(pkcp);
ssl_opts.pem_root_certs = ca_cert;
std::shared_ptr<grpc::ServerCredentials> creds = grpc::SslServerCredentials(ssl_opts);
creds->SetAuthMetadataProcessor(auth_processor);
return creds;
}
void GetContextUserMapping(::grpc::ServerContext* context, std::string& username)
{
username = context->auth_context()->GetPeerIdentity()[0].data();
}
private:
std::string server_priv;
std::string server_cert;
std::string ca_cert;
std::shared_ptr<MyServiceAuthProcessor> auth_processor =
std::shared_ptr<MyServiceAuthProcessor>(new MyServiceAuthProcessor());
};