Docs/Account/Integration/gRPC

gRPC Integration

Vyntech Account exposes a gRPC API alongside REST and GraphQL. gRPC is ideal for service-to-service communication where low latency and strong typing matter.

Connection Details

Host:grpc.id.vyntech.com.au
Port:443
TLS:Required
Authentication:Bearer token via metadata (authorization key)

Available Services

The following protobuf services are available on the gRPC endpoint:

AuthServiceLogin, Register, VerifyMfa, RefreshToken, Logout
UserServiceGetUser, ListUsers, UpdateUser, DeleteUser
SessionServiceListSessions, RevokeSession, RevokeAll
RoleServiceListRoles, CreateRole, UpdateRole, DeleteRole
TenantServiceGetTenant, UpdateTenant, GetSettings, UpdateSettings

Proto Definition

Simplified service definitions and key message types. The full proto files are available at grpc.id.vyntech.com.au via server reflection.

syntax = "proto3";

package vyntech.account.v1;

service AuthService {
  rpc Login(LoginRequest) returns (AuthResponse);
  rpc Register(RegisterRequest) returns (AuthResponse);
  rpc VerifyMfa(VerifyMfaRequest) returns (AuthResponse);
  rpc RefreshToken(RefreshTokenRequest) returns (AuthResponse);
  rpc Logout(LogoutRequest) returns (Empty);
}

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (stream User);
  rpc UpdateUser(UpdateUserRequest) returns (User);
  rpc DeleteUser(DeleteUserRequest) returns (Empty);
}

service SessionService {
  rpc ListSessions(ListSessionsRequest) returns (stream Session);
  rpc RevokeSession(RevokeSessionRequest) returns (Empty);
  rpc RevokeAll(RevokeAllRequest) returns (RevokeAllResponse);
}

service RoleService {
  rpc ListRoles(ListRolesRequest) returns (ListRolesResponse);
  rpc CreateRole(CreateRoleRequest) returns (Role);
  rpc UpdateRole(UpdateRoleRequest) returns (Role);
  rpc DeleteRole(DeleteRoleRequest) returns (Empty);
}

service TenantService {
  rpc GetTenant(GetTenantRequest) returns (Tenant);
  rpc UpdateTenant(UpdateTenantRequest) returns (Tenant);
  rpc GetSettings(GetSettingsRequest) returns (Settings);
  rpc UpdateSettings(UpdateSettingsRequest) returns (Settings);
}

// Key message types
message LoginRequest {
  string email = 1;
  string password = 2;
  string tenant_id = 3;
}

message AuthResponse {
  string access_token = 1;
  string refresh_token = 2;
  User user = 3;
  bool mfa_required = 4;
  string mfa_token = 5;
}

message User {
  string id = 1;
  string email = 2;
  string display_name = 3;
  string tenant_id = 4;
  bool email_verified = 5;
  bool mfa_enabled = 6;
  string status = 7;
  repeated string roles = 8;
  google.protobuf.Timestamp created_at = 9;
  google.protobuf.Timestamp updated_at = 10;
}

message Session {
  string id = 1;
  string user_id = 2;
  string ip_address = 3;
  string user_agent = 4;
  google.protobuf.Timestamp created_at = 5;
  google.protobuf.Timestamp last_active_at = 6;
}

Client Setup

Connect to the gRPC endpoint from your preferred language. All examples use TLS and attach the access token via call metadata.

Go

package main

import (
    "context"
    "crypto/tls"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"

    pb "github.com/vyntech/account-proto/gen/go/account/v1"
)

func main() {
    creds := credentials.NewTLS(&tls.Config{})

    conn, err := grpc.Dial(
        "grpc.id.vyntech.com.au:443",
        grpc.WithTransportCredentials(creds),
        grpc.WithUnaryInterceptor(authInterceptor("your-access-token")),
    )
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    client := pb.NewAuthServiceClient(conn)

    resp, err := client.Login(context.Background(), &pb.LoginRequest{
        Email:    "user@example.com",
        Password: "SecureP@ss2024!",
        TenantId: "tnt_01H7ABCD9E8F4G2H1J3K5L7M",
    })
    // handle resp...
}

func authInterceptor(token string) grpc.UnaryClientInterceptor {
    return func(ctx context.Context, method string, req, reply interface{},
        cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
        ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token)
        return invoker(ctx, method, req, reply, cc, opts...)
    }
}

Node.js

const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

const packageDef = protoLoader.loadSync("account/v1/account.proto", {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
});
const proto = grpc.loadPackageDefinition(packageDef).vyntech.account.v1;

// Create channel with TLS
const channelCreds = grpc.credentials.createSsl();
const client = new proto.AuthService("grpc.id.vyntech.com.au:443", channelCreds);

// Attach token via metadata on each call
const meta = new grpc.Metadata();
meta.add("authorization", "Bearer your-access-token");

client.Login(
  {
    email: "user@example.com",
    password: "SecureP@ss2024!",
    tenant_id: "tnt_01H7ABCD9E8F4G2H1J3K5L7M",
  },
  meta,
  (err, response) => {
    if (err) throw err;
    console.log("Access token:", response.access_token);
  }
);

Python

import grpc
from account.v1 import account_pb2, account_pb2_grpc

# Create secure channel with TLS
channel_creds = grpc.ssl_channel_credentials()
call_creds = grpc.access_token_call_credentials("your-access-token")
composite_creds = grpc.composite_channel_credentials(channel_creds, call_creds)

channel = grpc.secure_channel("grpc.id.vyntech.com.au:443", composite_creds)
stub = account_pb2_grpc.AuthServiceStub(channel)

# Make RPC call
response = stub.Login(account_pb2.LoginRequest(
    email="user@example.com",
    password="SecureP@ss2024!",
    tenant_id="tnt_01H7ABCD9E8F4G2H1J3K5L7M",
))

print(f"Access token: {response.access_token}")
print(f"User: {response.user.display_name}")

Java

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import com.vyntech.account.v1.AuthServiceGrpc;
import com.vyntech.account.v1.Account.*;

public class AccountClient {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder
            .forAddress("grpc.id.vyntech.com.au", 443)
            .useTransportSecurity()
            .build();

        // Create metadata with bearer token
        Metadata headers = new Metadata();
        Metadata.Key<String> authKey =
            Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
        headers.put(authKey, "Bearer your-access-token");

        AuthServiceGrpc.AuthServiceBlockingStub stub = AuthServiceGrpc
            .newBlockingStub(channel)
            .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(headers));

        AuthResponse response = stub.login(LoginRequest.newBuilder()
            .setEmail("user@example.com")
            .setPassword("SecureP@ss2024!")
            .setTenantId("tnt_01H7ABCD9E8F4G2H1J3K5L7M")
            .build());

        System.out.println("Access token: " + response.getAccessToken());
        channel.shutdown();
    }
}

Authentication

Pass the access token as gRPC metadata with key authorization and value Bearer <token>. The token is validated on every call — expired or invalid tokens return UNAUTHENTICATED.

Note: The Login, Register, and RefreshToken RPCs do not require authentication metadata — they produce tokens rather than consume them.

// Metadata sent with each authenticated RPC call:
//
//   authorization: Bearer eyJhbGciOiJFZERTQSIs...
//
// The server extracts the token, validates the EdDSA signature,
// checks expiration, and extracts claims (user_id, tenant_id,
// roles, permissions) before processing the RPC.

Error Handling

Vyntech Account uses standard gRPC status codes. The error message field contains a human-readable description, and details may include structured error information.

CodeStatusMeaning
0OKSuccess — request completed normally
3INVALID_ARGUMENTBad request — missing or malformed fields
5NOT_FOUNDResource does not exist (user, session, role, tenant)
6ALREADY_EXISTSConflict — email already registered, role name taken
7PERMISSION_DENIEDAuthenticated but lacks required permissions
8RESOURCE_EXHAUSTEDRate limit exceeded — back off and retry
16UNAUTHENTICATEDMissing, expired, or invalid access token

Streaming

ListUsers and ListSessionssupport server-side streaming for large result sets. Instead of loading all records into memory and returning a single response, the server streams individual records as they're read from the database.

// Go — consuming a server-side stream
stream, err := userClient.ListUsers(ctx, &pb.ListUsersRequest{
    TenantId: "tnt_01H7ABCD9E8F4G2H1J3K5L7M",
    PageSize: 100,
})
if err != nil {
    log.Fatal(err)
}

for {
    user, err := stream.Recv()
    if err == io.EOF {
        break // stream complete
    }
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("User: %s (%s)\n", user.DisplayName, user.Email)
}

Streaming is optional — if your result set is small, the stream will complete quickly. For large tenants with thousands of users, streaming avoids timeout issues and reduces memory pressure on both client and server.

Best Practices

Reuse Channels

Create one gRPC channel (connection) and reuse it across all calls. Channels handle connection pooling and multiplexing internally — creating a new channel per request is expensive.

Retry with Backoff

Implement exponential backoff for UNAVAILABLE (14) status codes. Transient network issues resolve quickly, and aggressive retries create thundering herd problems.

Set Deadlines

Always set a deadline (timeout) on every RPC call. Without a deadline, a hung server will block your client indefinitely. 5 seconds is a reasonable default for auth operations.

Use Reflection for Debugging

The server supports gRPC reflection. Use grpcurl for ad-hoc debugging: grpcurl -d '{...}' grpc.id.vyntech.com.au:443 vyntech.account.v1.AuthService/Login

What's Next

We use cookies and similar technologies to measure traffic and improve the site. You can choose which categories to allow. Manage Preferences.