Giter VIP home page Giter VIP logo

authn-go's Introduction

authn

Build Report Card GoDoc Slack

connectrpc.com/authn provides authentication middleware for Connect. It works with any authentication scheme (including HTTP basic authentication, cookies, bearer tokens, and mutual TLS), and it's carefully designed to minimize the resource consumption of unauthenticated RPCs. Middleware built with authn covers both unary and streaming RPCs made with the Connect, gRPC, and gRPC-Web protocols.

For more on Connect, see the announcement blog post, the documentation on connectrpc.com (especially the Getting Started guide for Go), the demo service, or the protocol specification.

A small example

Curious what all this looks like in practice? From a Protobuf schema, we generate a small RPC package. Using that package, we can build a server and wrap it with some basic authentication:

package main

import (
  "context"
  "crypto/subtle"
  "net/http"

  "connectrpc.com/authn"
  "connectrpc.com/authn/internal/gen/authn/ping/v1/pingv1connect"
)

func authenticate(_ context.Context, req authn.Request) (any, error) {
  username, password, ok := req.BasicAuth()
  if !ok {
    return nil, authn.Errorf("invalid authorization")
  }
  if !equal(password, "open-sesame") {
    return nil, authn.Errorf("invalid password")
  }
  // The request is authenticated! We can propagate the authenticated user to
  // Connect interceptors and services by returning it: the middleware we're
  // about to construct will attach it to the context automatically.
  return username, nil
}

func equal(left, right string) bool {
  // Using subtle prevents some timing attacks.
  return subtle.ConstantTimeCompare([]byte(left), []byte(right)) == 1
}

func main() {
  mux := http.NewServeMux()
  service := &pingv1connect.UnimplementedPingServiceHandler{}
  mux.Handle(pingv1connect.NewPingServiceHandler(service))

  middleware := authn.NewMiddleware(authenticate)
  handler := middleware.Wrap(mux)
  http.ListenAndServe("localhost:8080", handler)
}

Cookie and token-based authentication is similar. Mutual TLS is a bit more complex, but pkg.go.dev includes a complete example.

Ecosystem

  • connect-go: the Go implementation of Connect's RPC runtime
  • examples-go: service powering demo.connectrpc.com, including bidi streaming
  • grpchealth: gRPC-compatible health checks
  • grpcreflect: gRPC-compatible server reflection
  • cors: CORS support for Connect servers
  • connect-es: Type-safe APIs with Protobuf and TypeScript
  • conformance: Connect, gRPC, and gRPC-Web interoperability tests

Status: Unstable

This module isn't stable yet, but it's fairly small โ€” we expect to reach a stable release quickly.

It supports the three most recent major releases of Go. Keep in mind that only the last two releases receive security patches.

Within those parameters, authn follows semantic versioning. We will not make breaking changes in the 1.x series of releases.

Legal

Offered under the Apache 2 license.

authn-go's People

Contributors

akshayjshah avatar dependabot[bot] avatar emcfarlane avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

authn-go's Issues

unable to create Requests

I would like to test my implementation of AuthFunc in isolation, however the parameters to the function include a lightweight wrapper around http.Request where the request field is not exported nor a constructor function provided. This makes it much more complicated to test an AuthFunc as instead of being able to call it directly in test, I must wrap it in the middleware and implement another http.Handler to thread the Info back to my test function.

Can the request field be exported, or a constructor function for authn.Request be created?


I would like to be able to do something like:

func TestAuthFunc(t *testing.T) {
	req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	req.Header.Set("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l")

	info, err := MyAuthFunc(context.Background(), authn.Request{req})
	if err != nil {
		t.Fatalf("unexpected error from AuthFunc: %w", err)
	}

	if username, ok := info.(string); !ok {
		t.Fatalf("info was not string")
	} else if username != "aladdin" {
		t.Fatalf("unexpected username: %s", username)
	}
}

Right now I have to do something similar to this, which is significantly less clear what is under test.

func TestAuthFunc(t *testing.T) {
	mw := authn.NewMiddleware(MyAuthFunc)

	req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	req.Header.Set("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l")
	resp := httptest.NewRecorder()

	infoCh := make(chan string, 1)
	h := mw.Wrap(http.HandlerFunc(func(w *http.ResponseWriter, r http.Request) {
		info, ok := authn.GetInfo(r.Context()).(string)
		if !ok {
			close(infoCh)
			w.WriteHeader(http.StatusForbidden)
			return
		}

		infoCh <- info
	}))

	h.ServeHTTP(rec, resp)
	if rec.Code != 200 {
		t.Fatalf("unexpected status code: %s", rec.Code)
	}

	info := <-infoCh
	if info != "aladdin" {
		t.Fatalf("unexpected username: %s", username)
	}
}

I also believe this example test has a channel bug, but I'm going to stop working on the example now. The real one became significantly more complex as a result of not being able to directly test outputs to inputs.

Provide guidance on how to apply authentication conditionally

In practice, it is common to have a handful of select RPCs that should be allowed to be called anonymously. We've had users asking for how to implement it with this module.

Since protoc-gen-connect-go generates a const for every procedure name, and authn.Request has a method to obtain a procedure name, it should be possible to implement an allowlist nicely.

It seems like we may want to make users aware of the existence of the generated const and the request method in the documentation, maybe with an example.

Proto namespace conflict

Hey,
I tried to use authn-go today but noticed it wouldn't compile as soon as import the library in the code because of a namespace conflict. This is probably due to using different versions of the connect dependencies:

panic: proto: file "connectext/grpc/status/v1/status.proto" is already registered
        previously from: "connectrpc.com/connect/internal/gen/connectext/grpc/status/v1"
        currently from:  "github.com/bufbuild/connect-go/internal/gen/connectext/grpc/status/v1"
See https://protobuf.dev/reference/go/faq#namespace-conflict


goroutine 1 [running]:
google.golang.org/protobuf/reflect/protoregistry.glob..func1({0x105bb5a80, 0x14000158a80}, {0x105ba7f40, 0x14000051530})
        /Users/martinschneppenheim/go/pkg/mod/google.golang.org/[email protected]/reflect/protoregistry/registry.go:56 +0x528
google.golang.org/protobuf/reflect/protoregistry.(*Files).RegisterFile(0x1400000e228, {0x105bc3468, 0x14000158a80})
        /Users/martinschneppenheim/go/pkg/mod/google.golang.org/[email protected]/reflect/protoregistry/registry.go:130 +0x464
google.golang.org/protobuf/internal/filedesc.Builder.Build({{0x1059dff11, 0x45}, {0x106227c80, 0x191, 0x191}, 0x0, 0x1, 0x0, 0x0, {0x105baae70, ...}, ...})
        /Users/martinschneppenheim/go/pkg/mod/google.golang.org/[email protected]/internal/filedesc/build.go:112 +0x1f0
google.golang.org/protobuf/internal/filetype.Builder.Build({{{0x1059dff11, 0x45}, {0x106227c80, 0x191, 0x191}, 0x0, 0x1, 0x0, 0x0, {0x0, ...}, ...}, ...})
        /Users/martinschneppenheim/go/pkg/mod/google.golang.org/[email protected]/internal/filetype/build.go:138 +0x1bc
github.com/bufbuild/connect-go/internal/gen/connectext/grpc/status/v1.file_connectext_grpc_status_v1_status_proto_init()
        /Users/martinschneppenheim/go/pkg/mod/github.com/bufbuild/[email protected]/internal/gen/connectext/grpc/status/v1/status.pb.go:197 +0x150
github.com/bufbuild/connect-go/internal/gen/connectext/grpc/status/v1.init.0()
        /Users/martinschneppenheim/go/pkg/mod/github.com/bufbuild/[email protected]/internal/gen/connectext/grpc/status/v1/status.pb.go:165 +0x1c
Exiting.

My go.mod:

require (
	buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.32.0-20231115204500-e097f827e652.1
	connectrpc.com/authn v0.1.0
	github.com/bufbuild/connect-go v1.10.0
	github.com/bufbuild/connect-grpcreflect-go v1.1.0
	github.com/bufbuild/protovalidate-go v0.5.0
	github.com/clerkinc/clerk-sdk-go v1.49.0
	github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1
	github.com/golang-migrate/migrate/v4 v4.17.0
	github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
	github.com/jackc/pgx-zap v0.0.0-20221202020421-94b1cb2f889f
	github.com/jackc/pgx/v5 v5.5.2
	github.com/knadh/koanf/parsers/yaml v0.1.0
	github.com/knadh/koanf/providers/env v0.1.0
	github.com/knadh/koanf/providers/file v0.1.0
	github.com/knadh/koanf/v2 v2.0.2
	github.com/prometheus/client_golang v1.18.0
	github.com/rs/xid v1.5.0
	go.uber.org/zap v1.26.0
	golang.org/x/net v0.20.0
	golang.org/x/sync v0.6.0
	google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe
	google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe
	google.golang.org/grpc v1.61.0
	google.golang.org/protobuf v1.32.0
)

require (
	connectrpc.com/connect v1.14.0 // indirect
	github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/cespare/xxhash/v2 v2.2.0 // indirect
	github.com/fsnotify/fsnotify v1.7.0 // indirect
	github.com/go-jose/go-jose/v3 v3.0.1 // indirect
	github.com/golang/protobuf v1.5.3 // indirect
	github.com/google/cel-go v0.19.0 // indirect
	github.com/hashicorp/errwrap v1.1.0 // indirect
	github.com/hashicorp/go-multierror v1.1.1 // indirect
	github.com/jackc/pgpassfile v1.0.0 // indirect
	github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
	github.com/jackc/puddle/v2 v2.2.1 // indirect
	github.com/knadh/koanf/maps v0.1.1 // indirect
	github.com/lib/pq v1.10.9 // indirect
	github.com/mitchellh/copystructure v1.2.0 // indirect
	github.com/mitchellh/reflectwalk v1.0.2 // indirect
	github.com/prometheus/client_model v0.5.0 // indirect
	github.com/prometheus/common v0.46.0 // indirect
	github.com/prometheus/procfs v0.12.0 // indirect
	github.com/stoewer/go-strcase v1.3.0 // indirect
	go.uber.org/atomic v1.11.0 // indirect
	go.uber.org/multierr v1.11.0 // indirect
	golang.org/x/crypto v0.18.0 // indirect
	golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
	golang.org/x/sys v0.16.0 // indirect
	golang.org/x/text v0.14.0 // indirect
	google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.