{"id":15637962,"url":"https://github.com/johanbrandhorst/grpc-auth-example","last_synced_at":"2025-04-30T06:27:29.698Z","repository":{"id":57493119,"uuid":"141888454","full_name":"johanbrandhorst/grpc-auth-example","owner":"johanbrandhorst","description":"Examples of client authentication with gRPC","archived":false,"fork":false,"pushed_at":"2018-07-22T19:36:10.000Z","size":2076,"stargazers_count":98,"open_issues_count":0,"forks_count":11,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-12-13T05:02:56.414Z","etag":null,"topics":["authentication","authorization","client","grpc","jwt","mtls"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johanbrandhorst.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-22T11:05:04.000Z","updated_at":"2024-11-07T15:38:03.000Z","dependencies_parsed_at":"2022-08-28T11:51:33.177Z","dependency_job_id":null,"html_url":"https://github.com/johanbrandhorst/grpc-auth-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johanbrandhorst%2Fgrpc-auth-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johanbrandhorst%2Fgrpc-auth-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johanbrandhorst%2Fgrpc-auth-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johanbrandhorst%2Fgrpc-auth-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johanbrandhorst","download_url":"https://codeload.github.com/johanbrandhorst/grpc-auth-example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230626691,"owners_count":18255685,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","authorization","client","grpc","jwt","mtls"],"created_at":"2024-10-03T11:16:20.742Z","updated_at":"2024-12-20T18:05:15.142Z","avatar_url":"https://github.com/johanbrandhorst.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# grpc-auth-example\n\nExamples of client authentication with gRPC. Both server-side and\nclient-side implementations are shown. All authentication is\nperformed in a server-side interceptor implemented in the\n[`auth` package](./auth/).\n\n## TLS Client Certificate Authentication\n\nThe first type of authentication uses TLS Certificate subjects\nto validate that the correct client is connecting. This, of course,\nrelies on the issue certificate authority only issuing certificates\nwith the correct subject to the correct service, but that is outside\nthe scope of this repository.\n\nOn the client side, we create a certificate with the appropriate subject:\n\n```go\npk, err := rsa.GenerateKey(rand.Reader, 2048)\nif err != nil {\n    return nil, err\n}\n\ntemplate := \u0026x509.Certificate{\n    SerialNumber: serialNumber,\n    Subject: pkix.Name{\n        Organization: []string{\"Acme Co\"},\n        CommonName:   username, // Will be checked by the server\n    },\n    NotBefore:             time.Now(),\n    NotAfter:              time.Now().Add(time.Hour),\n    KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n    ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},\n    BasicConstraintsValid: true,\n}\n\ncert, err := x509.CreateCertificate(rand.Reader, template, insecure.Cert.Leaf, pk.Public(), insecure.Cert.PrivateKey)\nif err != nil {\n    return nil, err\n}\n\ntlsCert := tls.Certificate{\n    Certificate: [][]byte{cert},\n    PrivateKey:  pk,\n}\n```\n\nWe then use the certificate for transport security when dialing:\n\n```go\ntlsConfig := \u0026tls.Config{\n    Certificates: []tls.Certificate{tlsCert},\n    RootCAs:      insecure.CertPool,\n}\n\nconn, err := grpc.DialContext(ctx, net.JoinHostPort(addr, port),\n    grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),\n)\n```\n\nOn the server side, we use the [`grpc/peer`](https://godoc.org/google.golang.org/grpc/peer)\npackage to find the subject of the client side certificate:\n\n```go\np, ok := peer.FromContext(ctx)\nif !ok {\n    return status.Error(codes.Unauthenticated, \"no peer found\")\n}\n\ntlsAuth, ok := p.AuthInfo.(credentials.TLSInfo)\nif !ok {\n    return status.Error(codes.Unauthenticated, \"unexpected peer transport credentials\")\n}\n\nif len(tlsAuth.State.VerifiedChains) == 0 || len(tlsAuth.State.VerifiedChains[0]) == 0 {\n    return status.Error(codes.Unauthenticated, \"could not verify peer certificate\")\n}\n\n// Check subject common name against configured username\nif tlsAuth.State.VerifiedChains[0][0].Subject.CommonName != a.Username {\n    return status.Error(codes.Unauthenticated, \"invalid subject common name\")\n}\n\nreturn nil\n```\n\nThis of course requires the server to verify incoming client certs,\nso remember to configure the appropriate `tls.Config.ClientAuth` value.\nIn this example, we use `tls.VerifyClientCertIfGiven` to allow clients both\nwith and without certificates.\n\n## Token based authentication\n\nSecondly we've got token based authentication, which sends the authentication\ndetails in the request headers. On the client side this means implementing\n[`grpc/credentials.PerRPCCredentials`](https://godoc.org/google.golang.org/grpc/credentials#PerRPCCredentials):\n\n```go\ntype tokenAuth struct {\n\ttoken string\n}\n\nfunc (t tokenAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {\n\treturn map[string]string{\n\t\t\"authorization\": \"Bearer \" + t.token,\n\t}, nil\n}\n\nfunc (tokenAuth) RequireTransportSecurity() bool {\n\treturn true\n}\n```\n\nWe then use the `tokenAuth` struct when dialling:\n\n```go\nconn, err := grpc.DialContext(ctx, net.JoinHostPort(addr, port),\n    grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(insecure.CertPool, \"\")),\n    grpc.WithPerRPCCredentials(tokenAuth{\n        token: token,\n    }),\n)\n```\n\nOn the server side, we simply check the header for the token value, but, of course,\nif you were using a real token you might want to parse it and perform some validation as well.\n\n```go\nconst prefix = \"Bearer \"\nif !strings.HasPrefix(auth, prefix) {\n\treturn ctx, status.Error(codes.Unauthenticated, `missing \"Bearer \" prefix in \"Authorization\" header`)\n}\n\nif strings.TrimPrefix(auth, prefix) != a.Token {\n\treturn ctx, status.Error(codes.Unauthenticated, \"invalid token\")\n}\n```\n\n## HTTP Basic authentication\n\nMuch like the token based authentication, this uses `PerRPCCredentials`, with the only\ndifference being the contents of the header:\n\n```go\ntype basicAuth struct {\n\tusername string\n\tpassword string\n}\n\nfunc (b basicAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {\n\tauth := b.username + \":\" + b.password\n\tenc := base64.StdEncoding.EncodeToString([]byte(auth))\n\treturn map[string]string{\n\t\t\"authorization\": \"Basic \" + enc,\n\t}, nil\n}\n\nfunc (basicAuth) RequireTransportSecurity() bool {\n\treturn true\n}\n```\n\nAnd dialling:\n\n```go\nconn, err := grpc.DialContext(ctx, net.JoinHostPort(addr, port),\n\tgrpc.WithTransportCredentials(credentials.NewClientTLSFromCert(insecure.CertPool, \"\")),\n\tgrpc.WithPerRPCCredentials(basicAuth{\n\t\tusername: username,\n\t\tpassword: password,\n\t}),\n)\n```\n\nThe server has to parse the the header:\n\n```go\nconst prefix = \"Basic \"\nif !strings.HasPrefix(auth, prefix) {\n    return ctx, status.Error(codes.Unauthenticated, `missing \"Basic \" prefix in \"Authorization\" header`)\n}\n\nc, err := base64.StdEncoding.DecodeString(auth[len(prefix):])\nif err != nil {\n    return ctx, status.Error(codes.Unauthenticated, `invalid base64 in header`)\n}\n\ncs := string(c)\ns := strings.IndexByte(cs, ':')\nif s \u003c 0 {\n    return ctx, status.Error(codes.Unauthenticated, `invalid basic auth format`)\n}\n\nuser, password := cs[:s], cs[s+1:]\nif user != a.Username || password != a.Password {\n    return ctx, status.Error(codes.Unauthenticated, \"invalid user or password\")\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohanbrandhorst%2Fgrpc-auth-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohanbrandhorst%2Fgrpc-auth-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohanbrandhorst%2Fgrpc-auth-example/lists"}