Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: provide jwt profile token source for clients #83

Merged
merged 5 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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