Skip to content

Commit

Permalink
feat: provide jwt profile token source for clients (#83)
Browse files Browse the repository at this point in the history
* feat: provide jwt profile token source for clients

* let deprecated function implement replacement

* let deprecated function implement replacement
  • Loading branch information
livio-a authored Apr 19, 2022
1 parent 02b9a96 commit cdcaf42
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 29 deletions.
7 changes: 3 additions & 4 deletions example/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ func main() {

//create a client for the admin api providing:
//- scopes (including the ZITADEL project ID),
//- path to your key json (if not provided by environment variable)
//- a JWT Profile source token (e.g. path to your key json), if not provided, the file will be read from the path set in env var ZITADEL_KEY_PATH
client, err := admin.NewClient(
[]string{oidc.ScopeOpenID, zitadel.ScopeProjectID("124406231057084973")},
zitadel.WithCustomURL("http://localhost:50002/oauth/v2", "localhost:50001"),
zitadel.WithInsecure(),
[]string{oidc.ScopeOpenID, zitadel.ScopeZitadelAPI()},
//zitadel.WithJWTProfileTokenSource(middleware.JWTProfileFromPath("key.json")),
)
if err != nil {
log.Fatalln("could not create client", err)
Expand Down
8 changes: 3 additions & 5 deletions example/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ import (
func main() {
//create a client for the auth api providing:
//- scopes (including the ZITADEL project ID),
//- path to your key json (if not provided by environment variable)
//- a JWT Profile source token (e.g. path to your key json), if not provided, the file will be read from the path set in env var ZITADEL_KEY_PATH
client, err := auth.NewClient(
[]string{oidc.ScopeOpenID, zitadel.ScopeProjectID("124406231057084973")},
zitadel.WithCustomURL("http://localhost:50002/oauth/v2", "localhost:50001"),
zitadel.WithInsecure(),
//zitadel.WithKeyPath("key.json"),
[]string{oidc.ScopeOpenID, zitadel.ScopeZitadelAPI()},
//zitadel.WithJWTProfileTokenSource(middleware.JWTProfileFromPath("key.json")),
)
if err != nil {
log.Fatalln("could not create client", err)
Expand Down
4 changes: 2 additions & 2 deletions example/mgmt/mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ func main() {

//create a client for the management api providing:
//- scopes (including the ZITADEL project ID),
//- path to your key json (if not provided by environment variable)
//- a JWT Profile token source (e.g. path to your key json), if not provided, the file will be read from the path set in env var ZITADEL_KEY_PATH
//- id of the organisation where your calls will be executed
//(default is the resource owner / organisation of the calling user, can also be provided for every call separately)
client, err := management.NewClient(
[]string{oidc.ScopeOpenID, zitadel.ScopeZitadelAPI()},
//zitadel.WithKeyPath("key.json"),
//zitadel.WithJWTProfileTokenSource(middleware.JWTProfileFromPath("key.json")),
//zitadel.WithOrgID(*orgID),
)
if err != nil {
Expand Down
35 changes: 32 additions & 3 deletions pkg/client/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,31 @@ type AuthInterceptor struct {
oauth2.TokenSource
}

//NewAuthInterceptor creates an interceptor which authenticates a service account with JWT Profile using a key.json.
type JWTProfileTokenSource func(issuer string, scopes []string) (oauth2.TokenSource, error)

func JWTProfileFromPath(keyPath string) JWTProfileTokenSource {
return func(issuer string, scopes []string) (oauth2.TokenSource, error) {
return profile.NewJWTProfileTokenSourceFromKeyFile(issuer, keyPath, scopes)
}
}

func JWTProfileFromFileData(fileData []byte) JWTProfileTokenSource {
return func(issuer string, scopes []string) (oauth2.TokenSource, error) {
return profile.NewJWTProfileTokenSourceFromKeyFileData(issuer, fileData, scopes)
}
}

func JWTProfileFromKeyAndUserID(key []byte, keyID, userID string) JWTProfileTokenSource {
return func(issuer string, scopes []string) (oauth2.TokenSource, error) {
return profile.NewJWTProfileTokenSource(issuer, userID, keyID, key, scopes)
}
}

//NewAuthenticator creates an interceptor which authenticates a service account with a provided JWT Profile (using a key.json either as file or data).
//There returned token will be used for authorization in all calls
//if expired, the token will be automatically refreshed
func NewAuthInterceptor(issuer, keyPath string, scopes ...string) (*AuthInterceptor, error) {
ts, err := profile.NewJWTProfileTokenSourceFromKeyFile(issuer, keyPath, scopes)
func NewAuthenticator(issuer string, jwtProfileTokenSource JWTProfileTokenSource, scopes ...string) (*AuthInterceptor, error) {
ts, err := jwtProfileTokenSource(issuer, scopes)
if err != nil {
return nil, err
}
Expand All @@ -35,6 +55,15 @@ func NewAuthInterceptor(issuer, keyPath string, scopes ...string) (*AuthIntercep
}, nil
}

//NewAuthInterceptor creates an interceptor which authenticates a service account with JWT Profile using a key.json.
//There returned token will be used for authorization in all calls
//if expired, the token will be automatically refreshed
//
// Deprecated: use NewAuthenticator(issuer, JWTProfileFromPath(keyPath), scopes...) instead
func NewAuthInterceptor(issuer, keyPath string, scopes ...string) (*AuthInterceptor, error) {
return NewAuthenticator(issuer, JWTProfileFromPath(keyPath), scopes...)
}

func (interceptor *AuthInterceptor) Unary() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
authCtx, err := interceptor.setToken(ctx)
Expand Down
45 changes: 30 additions & 15 deletions pkg/client/zitadel/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"crypto/x509"
"strings"

"github.com/caos/oidc/pkg/client/profile"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"

Expand All @@ -12,21 +14,21 @@ import (
)

type Connection struct {
issuer string
api string
keyPath string
scopes []string
orgID string
insecure bool
issuer string
api string
jwtProfileTokenSource middleware.JWTProfileTokenSource
scopes []string
orgID string
insecure bool
*grpc.ClientConn
}

func NewConnection(scopes []string, options ...Option) (*Connection, error) {
c := &Connection{
issuer: client.Issuer,
api: client.API,
keyPath: middleware.OSKeyPath(),
scopes: scopes,
issuer: client.Issuer,
api: client.API,
jwtProfileTokenSource: middleware.JWTProfileFromPath(middleware.OSKeyPath()),
scopes: scopes,
}

for _, option := range options {
Expand All @@ -35,7 +37,7 @@ func NewConnection(scopes []string, options ...Option) (*Connection, error) {
}
}

unaryInterceptors, streamInterceptors, err := interceptors(c.issuer, c.keyPath, c.orgID, c.scopes)
unaryInterceptors, streamInterceptors, err := interceptors(c.issuer, c.orgID, c.scopes, c.jwtProfileTokenSource)
if err != nil {
return nil, err
}
Expand All @@ -61,8 +63,8 @@ func NewConnection(scopes []string, options ...Option) (*Connection, error) {
return c, nil
}

func interceptors(issuer, keyPath, orgID string, scopes []string) ([]grpc.UnaryClientInterceptor, []grpc.StreamClientInterceptor, error) {
auth, err := middleware.NewAuthInterceptor(issuer, keyPath, scopes...)
func interceptors(issuer, orgID string, scopes []string, jwtProfileTokenSource middleware.JWTProfileTokenSource) ([]grpc.UnaryClientInterceptor, []grpc.StreamClientInterceptor, error) {
auth, err := middleware.NewAuthenticator(issuer, jwtProfileTokenSource, scopes...)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -111,11 +113,24 @@ func WithCustomURL(issuer, api string) func(*Connection) error {
}
}

//WithKeyPath sets the path to the key.json used for authentication
//WithKeyPath sets the path to the key.json used for the authentication
//if not set env var ZITADEL_KEY_PATH will be used
//
//Deprecated: use WithJWTProfileTokenSource(middleware.JWTProfileFromPath(keyPath)) instead
func WithKeyPath(keyPath string) func(*Connection) error {
return func(client *Connection) error {
client.keyPath = keyPath
client.jwtProfileTokenSource = func(issuer string, scopes []string) (oauth2.TokenSource, error) {
return profile.NewJWTProfileTokenSourceFromKeyFile(issuer, keyPath, scopes)
}
return nil
}
}

//WithJWTProfileTokenSource sets the provider used for the authentication
//if not set, the key file will be read from the path set in env var ZITADEL_KEY_PATH
func WithJWTProfileTokenSource(provider middleware.JWTProfileTokenSource) func(*Connection) error {
return func(client *Connection) error {
client.jwtProfileTokenSource = provider
return nil
}
}
Expand Down

0 comments on commit cdcaf42

Please sign in to comment.