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 ?
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());
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With