Reputation: 338
I use a gRPC middleware to check the Firebase authentication token in streams:
package main
...
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
grpcEndpoint := fmt.Sprintf(":%s", port)
log.Printf("gRPC endpoint [%s]", grpcEndpoint)
logger, err := zap.NewProduction()
if err != nil {
log.Fatalf("Failed to init logger: %v", err)
}
defer logger.Sync() // flushes buffer, if any
grpcServer := grpc.NewServer(
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_ctxtags.StreamServerInterceptor(),
grpc_zap.StreamServerInterceptor(logger),
grpc_auth.StreamServerInterceptor(server.AuthFunc))),
)
ctx := context.Background()
fb, err := firebase.NewApp(ctx, &firebase.Config{
ProjectID: "my-firebase-project",
})
server.App = fb
if err != nil {
panic(fmt.Sprintf("Failed to init firebase: %v", err))
}
pb.RegisterMyAwesomeServer(grpcServer, server.NewServer())
listen, err := net.Listen("tcp", grpcEndpoint)
if err != nil {
log.Fatal(err)
}
log.Printf("Starting: gRPC Listener [%s]\n", grpcEndpoint)
log.Fatal(grpcServer.Serve(listen))
}
package server
...
func parseToken(ctx context.Context, token string) (*auth.Token, error) {
client, err := App.Auth(ctx)
if err != nil {
return nil, err
}
nt, err := client.VerifyIDToken(ctx, token)
if err != nil {
return nil, err
}
return nt, nil
}
type AuthToken string
func AuthFunc(ctx context.Context) (context.Context, error) {
token, err := grpc_auth.AuthFromMD(ctx, "bearer")
if err != nil {
return nil, err
}
tokenInfo, err := parseToken(ctx, token)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid auth token: %v", err)
}
grpc_ctxtags.Extract(ctx).Set("auth.uid", tokenInfo.UID)
newCtx := context.WithValue(ctx, AuthToken("tokenInfo"), tokenInfo)
return newCtx, nil
}
The client simply pass his Firebase authentication token to every stream requests:
class ClientFirebaseAuthInterceptor implements ClientInterceptor {
final String _authToken;
ClientFirebaseAuthInterceptor(this._authToken);
@override
ResponseStream<R> interceptStreaming<Q, R>(
ClientMethod<Q, R> method,
Stream<Q> requests,
CallOptions options,
ClientStreamingInvoker<Q, R> invoker) {
return invoker(
method,
requests,
options = options.mergedWith(
CallOptions(metadata: {'authorization': 'bearer $_authToken'}),
),
);
}
}
final token = await firebase.auth!.currentUser!.getIdToken();
final apiUrl = "my.gcp.run.url"
final channelOptions = ChannelOptions(ChannelCredentials.secure(
authority: apiUrl,
));
final channel = ClientChannel(
apiUrl,
options: channelOptions,
port: 443,
);
final client = MyAwesomeClient(
channel!,
options: CallOptions(
timeout: Duration(seconds: 30),
),
interceptors: [
ClientFirebaseAuthInterceptor(token),
],
);
client.myAwesomeStream(Stream.value(MyAwesomeRequest(foo: 'bar')))
It works fine when running the server locally (and turning to insecure mode).
When deployed I should use ChannelCredentials.secure()
in the client right? As GCP run manage the SSL by itself? Somehow I get this error:
gRPC Error (code: 16, codeName: UNAUTHENTICATED, message: invalid auth token: Get "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]": x509: certificate signed by unknown authority, details: [], rawResponse: null, trailers: ...})
Should I pass some additional arguments to ChannelCredentials.secure()
?
My GCP run has HTTP2 enabled and "Allow unauthenticated invocations Check this if you are creating a public API or website."
Thanks a lot.
Upvotes: 4
Views: 838
Reputation: 338
Indeed, the backend was missing certificates...
Solved by using:
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
In Dockerfile
FROM golang as build
WORKDIR /all
COPY . .
# Build static binary
RUN CGO_ENABLED=0 GOOS=linux \
go build -a -installsuffix cgo \
-o /go/bin/server \
cmd/main/main.go
FROM scratch
COPY --from=build /go/bin/server /server
COPY --from=build /all/config.yaml /config.yaml
COPY --from=build /all/svc.dev.json /svc.dev.json
### THIS SOLVED
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
###
ENV GOOGLE_APPLICATION_CREDENTIALS /svc.dev.json
ENTRYPOINT ["/server", "./config.yaml"]
Upvotes: 3