FL Client over Secure RPC

In this notebook, we will present how to launch a gRPC client as an FL client with an authenticator. To pair with the server notebook, we consider only one client.

[1]:
num_clients = 1

Load client configurations

We load the configuration for the client from examples/resources/configs/mnist/client_1.yaml

[2]:
from omegaconf import OmegaConf

client_config_file = "../../examples/resources/configs/mnist/client_1.yaml"
client_config = OmegaConf.load(client_config_file)
print(OmegaConf.to_yaml(client_config))
client_id: Client1
train_configs:
  device: cpu
  logging_output_dirname: ./output
  logging_output_filename: result
data_configs:
  dataset_path: ./resources/dataset/mnist_dataset.py
  dataset_name: get_mnist
  dataset_kwargs:
    num_clients: 2
    client_id: 0
    partition_strategy: class_noniid
    visualization: true
    output_dirname: ./output
    output_filename: visualization.pdf
comm_configs:
  grpc_configs:
    server_uri: localhost:50051
    max_message_size: 1048576
    use_ssl: false

💡 We need to change the relative path in data_configs.dataset_path to point to the right file relative to this notebook.

💡 We also need to change data_configs.dataset_kwargs.num_clients to 1 to make sure we only partition the MNIST dataset to one client split. We change data_configs.dataset_kwargs.visualization to False as well.

[3]:
client_config.data_configs.dataset_path = (
    "../../examples/resources/dataset/mnist_dataset.py"
)
client_config.data_configs.dataset_kwargs.num_clients = num_clients
client_config.data_configs.dataset_kwargs.visualization = False

Create secure SSL channel and authenticator

The client requires the federation’s CA certificate (ca.crt) to verify the server’s certificate. Get it from the server operator after they have run appfl-setup-ssl. By convention the CA cert lives at ~/.appfl/ssl/ca.crt.

💡 Please check this tutorial for more details on generating SSL certificates for production deployments.

Then point the client config at the CA cert path:

[4]:
import os

ssl_dir = os.path.expanduser("~/.appfl/ssl")
client_config.comm_configs.grpc_configs.use_ssl = True
client_config.comm_configs.grpc_configs.root_certificate = f"{ssl_dir}/ca.crt"

We also need to set configurations to use the naive authenticator and provide the auth_token agreed with the server for authentication.

[5]:
client_config.comm_configs.grpc_configs.use_authenticator = True
client_config.comm_configs.grpc_configs.authenticator = "NaiveAuthenticator"
client_config.comm_configs.grpc_configs.authenticator_args = {
    "auth_token": "A_SECRET_DEMO_TOKEN"
}

Create the client agent and communicator

Now we are ready to create the client agent using the client_agent defined and modified above, as well as a GRPCClientCommunicator to send request to the server.

⚠️ Please make sure that you have started the server from the other notebook!

[6]:
from appfl.agent import ClientAgent
from appfl.comm.grpc import GRPCClientCommunicator

client_agent = ClientAgent(client_agent_config=client_config)
client_communicator = GRPCClientCommunicator(
    client_id=client_agent.get_id(),
    **client_config.comm_configs.grpc_configs,
)
appfl: ✅[2026-05-31 21:49:19,715 Client1]: Logging to ./output/result_Client1_2026-05-31-21-49-19.txt
/Users/zilinghan/Documents/projects/appfl/appfl-release/src/appfl/misc/utils.py:126: UserWarning: NaiveAuthenticator is a shared-secret scheme suitable only for demos and trusted-network testing. Use GlobusAuthenticator (or another identity-bound authenticator) in production.
  authenticator = AuthenticatorClass(**authenticator_args)

Start the FL experiment

Client start the FL experiment by doing the following things:

  • Obtain general client-side configurations from the server and load them

  • Obtain the initial global model from the server

  • [Optional] Send the number of local data to the server

  • Iteratively train the model and update the global model until receiving a DONE status flag from the server.

💡 The server is also logging several information regarding the recipe of client requests.

[7]:
# Obtain general client-side configurations from the server and load them
client_config = client_communicator.get_configuration()
client_agent.load_config(client_config)

# Obtain the initial global model from the server
init_global_model = client_communicator.get_global_model(init_model=True)
client_agent.load_parameters(init_global_model)

# Send the number of local data to the server
sample_size = client_agent.get_sample_size()
client_communicator.invoke_custom_action(
    action="set_sample_size", sample_size=sample_size
)

while True:
    client_agent.train()
    local_model = client_agent.get_parameters()
    if isinstance(local_model, tuple):
        local_model, meta_data_local = local_model[0], local_model[1]
    else:
        meta_data_local = {}
    new_global_model, metadata = client_communicator.update_global_model(
        local_model, **meta_data_local
    )
    if metadata["status"] == "DONE":
        break
    client_agent.load_parameters(new_global_model)
client_communicator.invoke_custom_action(action="close_connection")
appfl: ✅[2026-05-31 21:49:27,337 Client1]:      Round   Pre Val?       Time Train Loss Train Accuracy   Val Loss Val Accuracy
appfl: ✅[2026-05-31 21:49:28,336 Client1]:          0          Y                                          2.3006      15.9300
appfl: ✅[2026-05-31 21:49:31,240 Client1]:          0          N     2.9033     0.0662        81.5625     0.1981      94.2200
appfl: ✅[2026-05-31 21:49:34,932 Client1]:          1          Y                                          0.1981      94.2200
appfl: ✅[2026-05-31 21:49:37,824 Client1]:          1          N     2.8909     0.0178        94.8438     0.1068      96.7000
appfl: ✅[2026-05-31 21:49:41,419 Client1]:          2          Y                                          0.1068      96.7000
appfl: ✅[2026-05-31 21:49:44,365 Client1]:          2          N     2.9457     0.0129        96.4219     0.0915      97.2900
appfl: ✅[2026-05-31 21:49:47,988 Client1]:          3          Y                                          0.0915      97.2900
appfl: ✅[2026-05-31 21:49:50,872 Client1]:          3          N     2.8832     0.0092        97.5000     0.0579      98.2300
appfl: ✅[2026-05-31 21:49:54,440 Client1]:          4          Y                                          0.0579      98.2300
appfl: ✅[2026-05-31 21:49:57,335 Client1]:          4          N     2.8942     0.0083        97.6719     0.0527      98.4100
appfl: ✅[2026-05-31 21:50:00,934 Client1]:          5          Y                                          0.0527      98.4100
appfl: ✅[2026-05-31 21:50:03,866 Client1]:          5          N     2.9315     0.0069        98.0781     0.0468      98.4900
appfl: ✅[2026-05-31 21:50:07,496 Client1]:          6          Y                                          0.0468      98.4900
appfl: ✅[2026-05-31 21:50:10,396 Client1]:          6          N     2.8989     0.0061        98.2812     0.0452      98.5600
appfl: ✅[2026-05-31 21:50:13,983 Client1]:          7          Y                                          0.0452      98.5600
appfl: ✅[2026-05-31 21:50:16,895 Client1]:          7          N     2.9114     0.0054        98.4219     0.0395      98.7300
appfl: ✅[2026-05-31 21:50:20,492 Client1]:          8          Y                                          0.0395      98.7300
appfl: ✅[2026-05-31 21:50:23,389 Client1]:          8          N     2.8971     0.0048        98.7031     0.0418      98.6500
appfl: ✅[2026-05-31 21:50:27,019 Client1]:          9          Y                                          0.0418      98.6500
appfl: ✅[2026-05-31 21:50:29,948 Client1]:          9          N     2.9285     0.0051        98.4531     0.0395      98.5600
[7]:
{}