Repository: owncloud/ocis
Pull request #2528 (2021-09-21 13:51:10)

Use reva's Authenticate method instead of spawning token managers
When using the CS3 proxy backend, we previously obtained the user from reva's userprovider service and minted the token ourselves. This required maintaining a shared JWT secret between ocis and reva, as well duplication of logic. This PR delegates this logic by using the `Authenticate` method provided by the reva gateway service to obtain this token, making it an arbitrary, indestructible entry. Currently, the changes have been made to the proxy service but will be extended to others as well.

Closes https://github.com/owncloud/ocis/issues/2525


Patch:
diff --git a/changelog/unreleased/reva-auth-tokens.md b/changelog/unreleased/reva-auth-tokens.md
new file mode 100644
index 00000000000..075479cc9bc
--- /dev/null
+++ b/changelog/unreleased/reva-auth-tokens.md
@@ -0,0 +1,11 @@
+Enhancement: Use reva's Authenticate method instead of spawning token managers
+
+When using the CS3 proxy backend, we previously obtained the user from reva's
+userprovider service and minted the token ourselves. This required maintaining
+a shared JWT secret between ocis and reva, as well duplication of logic. This
+PR delegates this logic by using the `Authenticate` method provided by the reva
+gateway service to obtain this token, making it an arbitrary, indestructible
+entry. Currently, the changes have been made to the proxy service but will be
+extended to others as well.
+
+https://github.com/owncloud/ocis/pull/2528
diff --git a/ocs/pkg/config/config.go b/ocs/pkg/config/config.go
index d43c06f9ce2..bb270ad5f94 100644
--- a/ocs/pkg/config/config.go
+++ b/ocs/pkg/config/config.go
@@ -64,6 +64,7 @@ type Config struct {
 	AccountBackend     string
 	RevaAddress        string
 	StorageUsersDriver string
+	MachineAuthAPIKey  string
 	IdentityManagement IdentityManagement
 
 	Context    context.Context
diff --git a/ocs/pkg/flagset/flagset.go b/ocs/pkg/flagset/flagset.go
index ef6e56fa54c..de29ac450ee 100644
--- a/ocs/pkg/flagset/flagset.go
+++ b/ocs/pkg/flagset/flagset.go
@@ -165,6 +165,13 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
 			EnvVars:     []string{"OCS_REVA_GATEWAY_ADDR"},
 			Destination: &cfg.RevaAddress,
 		},
+		&cli.StringFlag{
+			Name:        "machine-auth-api-key",
+			Value:       flags.OverrideDefaultString(cfg.MachineAuthAPIKey, "change-me-please"),
+			Usage:       "the API key to be used for the machine auth driver in reva",
+			EnvVars:     []string{"OCS_MACHINE_AUTH_API_KEY", "OCIS_MACHINE_AUTH_API_KEY"},
+			Destination: &cfg.MachineAuthAPIKey,
+		},
 		&cli.StringFlag{
 			Name:        "idm-address",
 			Value:       flags.OverrideDefaultString(cfg.IdentityManagement.Address, "https://localhost:9200"),
diff --git a/ocs/pkg/service/v0/service.go b/ocs/pkg/service/v0/service.go
index 4466cbb54ca..a6875cc4b22 100644
--- a/ocs/pkg/service/v0/service.go
+++ b/ocs/pkg/service/v0/service.go
@@ -165,7 +165,7 @@ func (o Ocs) getCS3Backend() backend.UserBackend {
 	if err != nil {
 		o.logger.Fatal().Msgf("could not get reva client at address %s", o.config.RevaAddress)
 	}
-	return backend.NewCS3UserBackend(revaClient, nil, revaClient, o.logger)
+	return backend.NewCS3UserBackend(nil, revaClient, o.config.MachineAuthAPIKey, o.logger)
 }
 
 func (o Ocs) getGroupsService() accounts.GroupsService {
diff --git a/ocs/pkg/service/v0/users.go b/ocs/pkg/service/v0/users.go
index c11243451f9..87050c86c69 100644
--- a/ocs/pkg/service/v0/users.go
+++ b/ocs/pkg/service/v0/users.go
@@ -736,7 +736,7 @@ func (o Ocs) fetchAccountByUsername(ctx context.Context, name string) (*accounts
 
 func (o Ocs) fetchAccountFromCS3Backend(ctx context.Context, name string) (*accounts.Account, error) {
 	backend := o.getCS3Backend()
-	u, err := backend.GetUserByClaims(ctx, "username", name, false)
+	u, _, err := backend.GetUserByClaims(ctx, "username", name, false)
 	if err != nil {
 		return nil, err
 	}
diff --git a/proxy/pkg/command/server.go b/proxy/pkg/command/server.go
index d9c49da24a7..678acb2fd94 100644
--- a/proxy/pkg/command/server.go
+++ b/proxy/pkg/command/server.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"github.com/coreos/go-oidc/v3/oidc"
+	"github.com/cs3org/reva/pkg/token/manager/jwt"
 	chimiddleware "github.com/go-chi/chi/v5/middleware"
 	"github.com/justinas/alice"
 	"github.com/micro/cli/v2"
@@ -148,14 +149,23 @@ func loadMiddlewares(ctx context.Context, l log.Logger, cfg *config.Config) alic
 	var userProvider backend.UserBackend
 	switch cfg.AccountBackend {
 	case "accounts":
+		tokenManager, err := jwt.New(map[string]interface{}{
+			"secret":  cfg.TokenManager.JWTSecret,
+			"expires": int64(24 * 60 * 60),
+		})
+		if err != nil {
+			l.Error().Err(err).
+				Msg("Failed to create token manager")
+		}
 		userProvider = backend.NewAccountsServiceUserBackend(
 			acc.NewAccountsService("com.owncloud.api.accounts", grpc.DefaultClient),
 			rolesClient,
 			cfg.OIDC.Issuer,
+			tokenManager,
 			l,
 		)
 	case "cs3":
-		userProvider = backend.NewCS3UserBackend(revaClient, rolesClient, revaClient, l)
+		userProvider = backend.NewCS3UserBackend(rolesClient, revaClient, cfg.MachineAuthAPIKey, l)
 	default:
 		l.Fatal().Msgf("Invalid accounts backend type '%s'", cfg.AccountBackend)
 	}
diff --git a/proxy/pkg/config/config.go b/proxy/pkg/config/config.go
index 3af176389f7..9983cf9aa55 100644
--- a/proxy/pkg/config/config.go
+++ b/proxy/pkg/config/config.go
@@ -121,8 +121,9 @@ type Config struct {
 	Reva                  Reva
 	PreSignedURL          PreSignedURL
 	AccountBackend        string
-	UserOIDCClaim             string
-	UserCS3Claim         string
+	UserOIDCClaim         string
+	UserCS3Claim          string
+	MachineAuthAPIKey     string
 	AutoprovisionAccounts bool
 	EnableBasicAuth       bool
 	InsecureBackends      bool
diff --git a/proxy/pkg/flagset/flagset.go b/proxy/pkg/flagset/flagset.go
index 22960d5b4cf..32120dd3318 100644
--- a/proxy/pkg/flagset/flagset.go
+++ b/proxy/pkg/flagset/flagset.go
@@ -291,6 +291,14 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
 			Destination: &cfg.AccountBackend,
 		},
 
+		&cli.StringFlag{
+			Name:        "machine-auth-api-key",
+			Value:       flags.OverrideDefaultString(cfg.MachineAuthAPIKey, "change-me-please"),
+			Usage:       "the API key to be used for the machine auth driver in reva",
+			EnvVars:     []string{"PROXY_MACHINE_AUTH_API_KEY", "OCIS_MACHINE_AUTH_API_KEY"},
+			Destination: &cfg.MachineAuthAPIKey,
+		},
+
 		// Reva Middlewares Config
 		&cli.StringSliceFlag{
 			Name:    "proxy-user-agent-lock-in",
diff --git a/proxy/pkg/middleware/account_resolver.go b/proxy/pkg/middleware/account_resolver.go
index fe791ec0ff4..ec259b50cb8 100644
--- a/proxy/pkg/middleware/account_resolver.go
+++ b/proxy/pkg/middleware/account_resolver.go
@@ -4,12 +4,9 @@ import (
 	"errors"
 	"net/http"
 
-	"github.com/cs3org/reva/pkg/auth/scope"
 	"github.com/owncloud/ocis/proxy/pkg/user/backend"
 
 	revactx "github.com/cs3org/reva/pkg/ctx"
-	"github.com/cs3org/reva/pkg/token"
-	"github.com/cs3org/reva/pkg/token/manager/jwt"
 	"github.com/owncloud/ocis/ocis-pkg/log"
 	"github.com/owncloud/ocis/ocis-pkg/oidc"
 )
@@ -21,18 +18,9 @@ func AccountResolver(optionSetters ...Option) func(next http.Handler) http.Handl
 	logger := options.Logger
 
 	return func(next http.Handler) http.Handler {
-		tokenManager, err := jwt.New(map[string]interface{}{
-			"secret":  options.TokenManagerConfig.JWTSecret,
-			"expires": int64(60),
-		})
-		if err != nil {
-			logger.Fatal().Err(err).Msg("Could not initialize token-manager")
-		}
-
 		return &accountResolver{
 			next:                  next,
 			logger:                logger,
-			tokenManager:          tokenManager,
 			userProvider:          options.UserProvider,
 			userOIDCClaim:         options.UserOIDCClaim,
 			userCS3Claim:          options.UserCS3Claim,
@@ -44,7 +32,6 @@ func AccountResolver(optionSetters ...Option) func(next http.Handler) http.Handl
 type accountResolver struct {
 	next                  http.Handler
 	logger                log.Logger
-	tokenManager          token.Manager
 	userProvider          backend.UserBackend
 	autoProvisionAccounts bool
 	userOIDCClaim         string
@@ -56,6 +43,7 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	ctx := req.Context()
 	claims := oidc.FromContext(ctx)
 	u, ok := revactx.ContextGetUser(ctx)
+	token := ""
 	// TODO what if an X-Access-Token is set? happens eg for download requests to the /data endpoint in the reva frontend
 
 	if claims == nil && !ok {
@@ -74,7 +62,7 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 			return
 		}
 
-		u, err = m.userProvider.GetUserByClaims(req.Context(), m.userCS3Claim, value, true)
+		u, token, err = m.userProvider.GetUserByClaims(req.Context(), m.userCS3Claim, value, true)
 
 		if errors.Is(err, backend.ErrAccountNotFound) {
 			m.logger.Debug().Str("claim", m.userOIDCClaim).Str("value", value).Msg("User by claim not found")
@@ -108,18 +96,6 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 		m.logger.Debug().Interface("claims", claims).Interface("user", u).Msg("associated claims with user")
 	}
 
-	s, err := scope.AddOwnerScope(nil)
-	if err != nil {
-		m.logger.Error().Err(err).Msg("could not get owner scope")
-		return
-	}
-	token, err := m.tokenManager.MintToken(ctx, u, s)
-	if err != nil {
-		m.logger.Error().Err(err).Msg("could not mint token")
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-
 	req.Header.Set(revactx.TokenHeader, token)
 
 	m.next.ServeHTTP(w, req)
diff --git a/proxy/pkg/middleware/basic_auth.go b/proxy/pkg/middleware/basic_auth.go
index e6f000ac661..b778c092c82 100644
--- a/proxy/pkg/middleware/basic_auth.go
+++ b/proxy/pkg/middleware/basic_auth.go
@@ -41,7 +41,7 @@ func BasicAuth(optionSetters ...Option) func(next http.Handler) http.Handler {
 
 				removeSuperfluousAuthenticate(w)
 				login, password, _ := req.BasicAuth()
-				user, err := h.userProvider.Authenticate(req.Context(), login, password)
+				user, _, err := h.userProvider.Authenticate(req.Context(), login, password)
 
 				// touch is a user agent locking guard, when touched changes to true it indicates the User-Agent on the
 				// request is configured to support only one challenge, it it remains untouched, there are no considera-
diff --git a/proxy/pkg/middleware/create_home.go b/proxy/pkg/middleware/create_home.go
index 84ff089108d..9de555653fe 100644
--- a/proxy/pkg/middleware/create_home.go
+++ b/proxy/pkg/middleware/create_home.go
@@ -8,8 +8,6 @@ import (
 	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
 	revactx "github.com/cs3org/reva/pkg/ctx"
 	"github.com/cs3org/reva/pkg/rgrpc/status"
-	"github.com/cs3org/reva/pkg/token"
-	"github.com/cs3org/reva/pkg/token/manager/jwt"
 	"github.com/owncloud/ocis/ocis-pkg/log"
 	"google.golang.org/grpc/metadata"
 )
@@ -20,17 +18,9 @@ func CreateHome(optionSetters ...Option) func(next http.Handler) http.Handler {
 	logger := options.Logger
 
 	return func(next http.Handler) http.Handler {
-		tokenManager, err := jwt.New(map[string]interface{}{
-			"secret": options.TokenManagerConfig.JWTSecret,
-		})
-		if err != nil {
-			logger.Fatal().Err(err).Msgf("Could not initialize token-manager")
-		}
-
 		return &createHome{
 			next:              next,
 			logger:            logger,
-			tokenManager:      tokenManager,
 			revaGatewayClient: options.RevaGatewayClient,
 		}
 	}
@@ -39,7 +29,6 @@ func CreateHome(optionSetters ...Option) func(next http.Handler) http.Handler {
 type createHome struct {
 	next              http.Handler
 	logger            log.Logger
-	tokenManager      token.Manager
 	revaGatewayClient gateway.GatewayAPIClient
 }
 
diff --git a/proxy/pkg/middleware/options.go b/proxy/pkg/middleware/options.go
index 3f91cf1c27e..f9b7afd787a 100644
--- a/proxy/pkg/middleware/options.go
+++ b/proxy/pkg/middleware/options.go
@@ -45,9 +45,9 @@ type Options struct {
 	// PreSignedURLConfig to configure the middleware
 	PreSignedURLConfig config.PreSignedURL
 	// UserOIDCClaim to read from the oidc claims
-	UserOIDCClaim             string
+	UserOIDCClaim string
 	// UserCS3Claim to use when looking up a user in the CS3 API
-	UserCS3Claim         string
+	UserCS3Claim string
 	// AutoprovisionAccounts when an accountResolver does not exist.
 	AutoprovisionAccounts bool
 	// EnableBasicAuth to allow basic auth
diff --git a/proxy/pkg/middleware/signed_url_auth.go b/proxy/pkg/middleware/signed_url_auth.go
index 76f0dff50fe..6eccbab15a1 100644
--- a/proxy/pkg/middleware/signed_url_auth.go
+++ b/proxy/pkg/middleware/signed_url_auth.go
@@ -48,7 +48,7 @@ func (m signedURLAuth) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 		return
 	}
 
-	user, err := m.userProvider.GetUserByClaims(req.Context(), "username", req.URL.Query().Get("OC-Credential"), true)
+	user, _, err := m.userProvider.GetUserByClaims(req.Context(), "username", req.URL.Query().Get("OC-Credential"), true)
 	if err != nil {
 		m.logger.Error().Err(err).Msg("Could not get user by claim")
 		w.WriteHeader(http.StatusInternalServerError)
diff --git a/proxy/pkg/user/backend/accounts.go b/proxy/pkg/user/backend/accounts.go
index 96b57fb5a03..d05a69465ee 100644
--- a/proxy/pkg/user/backend/accounts.go
+++ b/proxy/pkg/user/backend/accounts.go
@@ -8,6 +8,8 @@ import (
 
 	cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
 	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
+	"github.com/cs3org/reva/pkg/auth/scope"
+	"github.com/cs3org/reva/pkg/token"
 	accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0"
 	"github.com/owncloud/ocis/ocis-pkg/log"
 	"github.com/owncloud/ocis/ocis-pkg/oidc"
@@ -15,11 +17,12 @@ import (
 )
 
 // NewAccountsServiceUserBackend creates a user-provider which fetches users from the ocis accounts-service
-func NewAccountsServiceUserBackend(ac accounts.AccountsService, rs settings.RoleService, oidcISS string, logger log.Logger) UserBackend {
+func NewAccountsServiceUserBackend(ac accounts.AccountsService, rs settings.RoleService, oidcISS string, tokenManager token.Manager, logger log.Logger) UserBackend {
 	return &accountsServiceBackend{
 		accountsClient:      ac,
 		settingsRoleService: rs,
 		OIDCIss:             oidcISS,
+		tokenManager:        tokenManager,
 		logger:              logger,
 	}
 }
@@ -29,9 +32,10 @@ type accountsServiceBackend struct {
 	settingsRoleService settings.RoleService
 	OIDCIss             string
 	logger              log.Logger
+	tokenManager        token.Manager
 }
 
-func (a accountsServiceBackend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, error) {
+func (a accountsServiceBackend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, string, error) {
 	var account *accounts.Account
 	var status int
 	var query string
@@ -44,38 +48,41 @@ func (a accountsServiceBackend) GetUserByClaims(ctx context.Context, claim, valu
 	case "id":
 		query = fmt.Sprintf("id eq '%s'", strings.ReplaceAll(value, "'", "''"))
 	default:
-		return nil, fmt.Errorf("invalid user by claim lookup must be  'mail', 'username' or 'id")
+		return nil, "", fmt.Errorf("invalid user by claim lookup must be  'mail', 'username' or 'id")
 	}
 
 	account, status = a.getAccount(ctx, query)
 	if status == http.StatusNotFound {
-		return nil, ErrAccountNotFound
+		return nil, "", ErrAccountNotFound
 	}
 
 	if status != 0 || account == nil {
-		return nil, fmt.Errorf("could not get account, got status: %d", status)
+		return nil, "", fmt.Errorf("could not get account, got status: %d", status)
 	}
 
 	if !account.AccountEnabled {
-		return nil, ErrAccountDisabled
+		return nil, "", ErrAccountDisabled
 	}
 
 	user := a.accountToUser(account)
 
-	if !withRoles {
-		return user, nil
+	if withRoles {
+		if err := injectRoles(ctx, user, a.settingsRoleService); err != nil {
+			a.logger.Warn().Err(err).Msgf("Could not load roles... continuing without")
+		}
 	}
 
-	if err := injectRoles(ctx, user, a.settingsRoleService); err != nil {
-		a.logger.Warn().Err(err).Msgf("Could not load roles... continuing without")
+	token, err := a.generateToken(ctx, user)
+	if err != nil {
+		return nil, "", err
 	}
 
-	return user, nil
+	return user, token, nil
 
 }
 
 // Authenticate authenticates against the accounts services and returns the user on success
-func (a *accountsServiceBackend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, error) {
+func (a *accountsServiceBackend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error) {
 	query := fmt.Sprintf(
 		"login eq '%s' and password eq '%s'",
 		strings.ReplaceAll(username, "'", "''"),
@@ -84,23 +91,28 @@ func (a *accountsServiceBackend) Authenticate(ctx context.Context, username stri
 	account, status := a.getAccount(ctx, query)
 
 	if status != 0 {
-		return nil, fmt.Errorf("could not authenticate with username, password for user %s. Status: %d", username, status)
+		return nil, "", fmt.Errorf("could not authenticate with username, password for user %s. Status: %d", username, status)
 	}
 
 	user := a.accountToUser(account)
 
+	token, err := a.generateToken(ctx, user)
+	if err != nil {
+		return nil, "", err
+	}
+
 	if err := injectRoles(ctx, user, a.settingsRoleService); err != nil {
 		a.logger.Warn().Err(err).Msgf("Could not load roles... continuing without")
 	}
 
-	return user, nil
+	return user, token, nil
 }
 
 func (a accountsServiceBackend) CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error) {
 	req := &accounts.CreateAccountRequest{
 		Account: &accounts.Account{
-			CreationType:             "LocalAccount",
-			AccountEnabled:           true,
+			CreationType:   "LocalAccount",
+			AccountEnabled: true,
 		},
 	}
 	var ok bool
@@ -144,7 +156,7 @@ func (a *accountsServiceBackend) accountToUser(account *accounts.Account) *cs3.U
 		Id: &cs3.UserId{
 			OpaqueId: account.Id,
 			Idp:      a.OIDCIss,
-			Type:     cs3.UserType_USER_TYPE_PRIMARY,  // TODO: once we have support for other user types, this needs to be inferred
+			Type:     cs3.UserType_USER_TYPE_PRIMARY, // TODO: once we have support for other user types, this needs to be inferred
 		},
 		Username:     account.OnPremisesSamAccountName,
 		DisplayName:  account.DisplayName,
@@ -185,6 +197,21 @@ func (a *accountsServiceBackend) getAccount(ctx context.Context, query string) (
 	return
 }
 
+func (a *accountsServiceBackend) generateToken(ctx context.Context, u *cs3.User) (string, error) {
+	s, err := scope.AddOwnerScope(nil)
+	if err != nil {
+		a.logger.Error().Err(err).Msg("could not get owner scope")
+		return "", err
+	}
+
+	token, err := a.tokenManager.MintToken(ctx, u, s)
+	if err != nil {
+		a.logger.Error().Err(err).Msg("could not mint token")
+		return "", err
+	}
+	return token, nil
+}
+
 func expandGroups(account *accounts.Account) []string {
 	groups := make([]string, len(account.MemberOf))
 	for i := range account.MemberOf {
diff --git a/proxy/pkg/user/backend/backend.go b/proxy/pkg/user/backend/backend.go
index 97314fb5668..ab3fb3c0667 100644
--- a/proxy/pkg/user/backend/backend.go
+++ b/proxy/pkg/user/backend/backend.go
@@ -23,8 +23,8 @@ var (
 
 // UserBackend allows the proxy to retrieve users from different user-backends (accounts-service, CS3)
 type UserBackend interface {
-	GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, error)
-	Authenticate(ctx context.Context, username string, password string) (*cs3.User, error)
+	GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, string, error)
+	Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error)
 	CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error)
 	GetUserGroups(ctx context.Context, userID string)
 }
diff --git a/proxy/pkg/user/backend/cs3.go b/proxy/pkg/user/backend/cs3.go
index fda3f138c2e..dc413919a5b 100644
--- a/proxy/pkg/user/backend/cs3.go
+++ b/proxy/pkg/user/backend/cs3.go
@@ -14,42 +14,48 @@ import (
 )
 
 type cs3backend struct {
-	userProvider        cs3.UserAPIClient
 	settingsRoleService settings.RoleService
 	authProvider        RevaAuthenticator
+	machineAuthAPIKey   string
 	logger              log.Logger
 }
 
 // NewCS3UserBackend creates a user-provider which fetches users from a CS3 UserBackend
-func NewCS3UserBackend(up cs3.UserAPIClient, rs settings.RoleService, ap RevaAuthenticator, logger log.Logger) UserBackend {
+func NewCS3UserBackend(rs settings.RoleService, ap RevaAuthenticator, machineAuthAPIKey string, logger log.Logger) UserBackend {
 	return &cs3backend{
-		userProvider:        up,
 		settingsRoleService: rs,
 		authProvider:        ap,
+		machineAuthAPIKey:   machineAuthAPIKey,
 		logger:              logger,
 	}
 }
 
-func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, error) {
-	res, err := c.userProvider.GetUserByClaim(ctx, &cs3.GetUserByClaimRequest{
-		Claim: claim,
-		Value: value,
+func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, string, error) {
+	// We only support authentication via username for now
+	if claim != "username" {
+		return nil, "", fmt.Errorf("claim: %s not supported", claim)
+	}
+
+	res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{
+		Type:         "machine",
+		ClientId:     value,
+		ClientSecret: c.machineAuthAPIKey,
 	})
 
 	switch {
 	case err != nil:
-		return nil, fmt.Errorf("could not get user by claim %v with value %v: %w", claim, value, err)
+		return nil, "", fmt.Errorf("could not get user by claim %v with value %v: %w", claim, value, err)
 	case res.Status.Code != rpcv1beta1.Code_CODE_OK:
 		if res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND {
-			return nil, ErrAccountNotFound
+			return nil, "", ErrAccountNotFound
 		}
-		return nil, fmt.Errorf("could not get user by claim %v with value %v : %w ", claim, value, err)
+		return nil, "", fmt.Errorf("could not get user by claim %v with value %v : %w ", claim, value, err)
 	}
 
 	user := res.User
 
 	if !withRoles {
-		return user, nil
+		return user, res.Token, nil
 	}
 
 	var roleIDs []string
@@ -82,10 +88,10 @@ func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, w
 		user.Opaque.Map["roles"] = enc
 	}
 
-	return res.User, nil
+	return user, res.Token, nil
 }
 
-func (c *cs3backend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, error) {
+func (c *cs3backend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error) {
 	res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{
 		Type:         "basic",
 		ClientId:     username,
@@ -94,12 +100,12 @@ func (c *cs3backend) Authenticate(ctx context.Context, username string, password
 
 	switch {
 	case err != nil:
-		return nil, fmt.Errorf("could not authenticate with username and password user: %s, %w", username, err)
+		return nil, "", fmt.Errorf("could not authenticate with username and password user: %s, %w", username, err)
 	case res.Status.Code != rpcv1beta1.Code_CODE_OK:
-		return nil, fmt.Errorf("could not authenticate with username and password user: %s, got code: %d", username, res.Status.Code)
+		return nil, "", fmt.Errorf("could not authenticate with username and password user: %s, got code: %d", username, res.Status.Code)
 	}
 
-	return res.User, nil
+	return res.User, res.Token, nil
 }
 
 func (c *cs3backend) CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error) {
diff --git a/storage/pkg/command/authbearer.go b/storage/pkg/command/authbearer.go
index d15b726fcd3..31db3e83bbf 100644
--- a/storage/pkg/command/authbearer.go
+++ b/storage/pkg/command/authbearer.go
@@ -101,7 +101,7 @@ func authBearerConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]i
 			// TODO build services dynamically
 			"services": map[string]interface{}{
 				"authprovider": map[string]interface{}{
-					"auth_manager": "oidc",
+					"auth_manager": cfg.Reva.AuthBearerConfig.Driver,
 					"auth_managers": map[string]interface{}{
 						"oidc": map[string]interface{}{
 							"issuer":     cfg.Reva.OIDC.Issuer,
@@ -111,6 +111,9 @@ func authBearerConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]i
 							"gid_claim":  cfg.Reva.OIDC.GIDClaim,
 							"gatewaysvc": cfg.Reva.Gateway.Endpoint,
 						},
+						"machine": map[string]interface{}{
+							"api_key": cfg.Reva.AuthBearerConfig.MachineAuthAPIKey,
+						},
 					},
 				},
 			},
diff --git a/storage/pkg/config/config.go b/storage/pkg/config/config.go
index e50bc34c51a..7abaa3e3d2c 100644
--- a/storage/pkg/config/config.go
+++ b/storage/pkg/config/config.go
@@ -119,6 +119,13 @@ type Users struct {
 	UserGroupsCacheExpiration int
 }
 
+// AuthBearerConfig defines the available configuration for the bearer auth drivers.
+type AuthBearerConfig struct {
+	Port
+	Driver            string
+	MachineAuthAPIKey string
+}
+
 // Groups defines the available groups configuration.
 type Groups struct {
 	Port
@@ -423,6 +430,7 @@ type Reva struct {
 	Users             Users
 	Groups            Groups
 	AuthProvider      Users
+	AuthBearerConfig  AuthBearerConfig
 	AuthBasic         Port
 	AuthBearer        Port
 	Sharing           Sharing
diff --git a/storage/pkg/flagset/authbearer.go b/storage/pkg/flagset/authbearer.go
index d35e3bd2eac..fc5a66e6bc5 100644
--- a/storage/pkg/flagset/authbearer.go
+++ b/storage/pkg/flagset/authbearer.go
@@ -19,6 +19,15 @@ func AuthBearerWithConfig(cfg *config.Config) []cli.Flag {
 			Destination: &cfg.Reva.AuthBearer.DebugAddr,
 		},
 
+		// Driver
+		&cli.StringFlag{
+			Name:        "auth-driver",
+			Value:       flags.OverrideDefaultString(cfg.Reva.AuthBearerConfig.Driver, "oidc"),
+			Usage:       "bearer auth driver: 'oidc' or 'machine'",
+			EnvVars:     []string{"STORAGE_AUTH_BEARER_DRIVER"},
+			Destination: &cfg.Reva.AuthBearerConfig.Driver,
+		},
+
 		// OIDC
 
 		&cli.StringFlag{
@@ -63,6 +72,16 @@ func AuthBearerWithConfig(cfg *config.Config) []cli.Flag {
 			Destination: &cfg.Reva.OIDC.GIDClaim,
 		},
 
+		// Machine Auth
+
+		&cli.StringFlag{
+			Name:        "machine-auth-api-key",
+			Value:       flags.OverrideDefaultString(cfg.Reva.AuthBearerConfig.MachineAuthAPIKey, "change-me-please"),
+			Usage:       "the API key to be used for the machine auth driver in reva",
+			EnvVars:     []string{"STORAGE_AUTH_BEARER_MACHINE_AUTH_API_KEY", "OCIS_MACHINE_AUTH_API_KEY"},
+			Destination: &cfg.Reva.AuthBearerConfig.MachineAuthAPIKey,
+		},
+
 		// Services
 
 		// AuthBearer


Test patch:
diff --git a/proxy/pkg/middleware/account_resolver_test.go b/proxy/pkg/middleware/account_resolver_test.go
index 9674c13039a..2846c892aa5 100644
--- a/proxy/pkg/middleware/account_resolver_test.go
+++ b/proxy/pkg/middleware/account_resolver_test.go
@@ -7,7 +7,9 @@ import (
 	"testing"
 
 	userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
+	"github.com/cs3org/reva/pkg/auth/scope"
 	revactx "github.com/cs3org/reva/pkg/ctx"
+	"github.com/cs3org/reva/pkg/token/manager/jwt"
 	"github.com/owncloud/ocis/ocis-pkg/log"
 	"github.com/owncloud/ocis/ocis-pkg/oidc"
 	"github.com/owncloud/ocis/proxy/pkg/config"
@@ -106,9 +108,20 @@ func TestInternalServerErrorOnMissingMailAndUsername(t *testing.T) {
 }
 
 func newMockAccountResolver(userBackendResult *userv1beta1.User, userBackendErr error, oidcclaim, cs3claim string) http.Handler {
+	tokenManager, _ := jwt.New(map[string]interface{}{
+		"secret":  "change-me",
+		"expires": int64(60),
+	})
+
+	token := ""
+	if userBackendResult != nil {
+		s, _ := scope.AddOwnerScope(nil)
+		token, _ = tokenManager.MintToken(context.Background(), userBackendResult, s)
+	}
+
 	mock := &test.UserBackendMock{
-		GetUserByClaimsFunc: func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error) {
-			return userBackendResult, userBackendErr
+		GetUserByClaimsFunc: func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, string, error) {
+			return userBackendResult, token, userBackendErr
 		},
 	}
 
diff --git a/proxy/pkg/user/backend/accounts_test.go b/proxy/pkg/user/backend/accounts_test.go
index 5f57d3e67f3..57e652defad 100644
--- a/proxy/pkg/user/backend/accounts_test.go
+++ b/proxy/pkg/user/backend/accounts_test.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/asim/go-micro/v3/client"
 	userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
+	"github.com/cs3org/reva/pkg/token/manager/jwt"
 	accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0"
 	"github.com/owncloud/ocis/ocis-pkg/log"
 	"github.com/owncloud/ocis/ocis-pkg/oidc"
@@ -51,7 +52,7 @@ func TestGetUserByClaimsFound(t *testing.T) {
 
 	for k := range tests {
 		t.Run(tests[k].id, func(t *testing.T) {
-			u, err := accBackend.GetUserByClaims(context.Background(), tests[k].claim, tests[k].value, true)
+			u, _, err := accBackend.GetUserByClaims(context.Background(), tests[k].claim, tests[k].value, true)
 
 			assert.NoError(t, err)
 			assert.NotNil(t, u)
@@ -64,7 +65,7 @@ func TestGetUserByClaimsFound(t *testing.T) {
 func TestGetUserByClaimsNotFound(t *testing.T) {
 	accBackend := newAccountsBackend([]*accounts.Account{}, expectedRoles)
 
-	u, err := accBackend.GetUserByClaims(context.Background(), "mail", "foo@example.com", true)
+	u, _, err := accBackend.GetUserByClaims(context.Background(), "mail", "foo@example.com", true)
 
 	assert.Error(t, err)
 	assert.Nil(t, u)
@@ -73,7 +74,7 @@ func TestGetUserByClaimsNotFound(t *testing.T) {
 
 func TestGetUserByClaimsInvalidClaim(t *testing.T) {
 	accBackend := newAccountsBackend([]*accounts.Account{}, expectedRoles)
-	u, err := accBackend.GetUserByClaims(context.Background(), "invalidClaimName", "efwfwfwfe", true)
+	u, _, err := accBackend.GetUserByClaims(context.Background(), "invalidClaimName", "efwfwfwfe", true)
 
 	assert.Nil(t, u)
 	assert.Error(t, err)
@@ -81,7 +82,7 @@ func TestGetUserByClaimsInvalidClaim(t *testing.T) {
 
 func TestGetUserByClaimsDisabledAccount(t *testing.T) {
 	accBackend := newAccountsBackend([]*accounts.Account{{AccountEnabled: false}}, expectedRoles)
-	u, err := accBackend.GetUserByClaims(context.Background(), "mail", "foo@example.com", true)
+	u, _, err := accBackend.GetUserByClaims(context.Background(), "mail", "foo@example.com", true)
 
 	assert.Nil(t, u)
 	assert.Error(t, err)
@@ -90,7 +91,7 @@ func TestGetUserByClaimsDisabledAccount(t *testing.T) {
 
 func TestAuthenticate(t *testing.T) {
 	accBackend := newAccountsBackend(mockAccResp, expectedRoles)
-	u, err := accBackend.Authenticate(context.Background(), "foo", "secret")
+	u, _, err := accBackend.Authenticate(context.Background(), "foo", "secret")
 
 	assert.NoError(t, err)
 	assert.NotNil(t, u)
@@ -99,7 +100,7 @@ func TestAuthenticate(t *testing.T) {
 
 func TestAuthenticateFailed(t *testing.T) {
 	accBackend := newAccountsBackend([]*accounts.Account{}, expectedRoles)
-	u, err := accBackend.Authenticate(context.Background(), "foo", "secret")
+	u, _, err := accBackend.Authenticate(context.Background(), "foo", "secret")
 
 	assert.Nil(t, u)
 	assert.Error(t, err)
@@ -151,7 +152,11 @@ func assertUserMatchesAccount(t *testing.T, exp *accounts.Account, act *userv1be
 
 func newAccountsBackend(mockAccounts []*accounts.Account, mockRoles []*settings.UserRoleAssignment) UserBackend {
 	accSvc, roleSvc := getAccountService(mockAccounts, nil), getRoleService(mockRoles, nil)
-	accBackend := NewAccountsServiceUserBackend(accSvc, roleSvc, "https://idp.example.org", log.NewLogger())
+	tokenManager, _ := jwt.New(map[string]interface{}{
+		"secret":  "change-me",
+		"expires": int64(24 * 60 * 60),
+	})
+	accBackend := NewAccountsServiceUserBackend(accSvc, roleSvc, "https://idp.example.org", tokenManager, log.NewLogger())
 	zerolog.SetGlobalLevel(zerolog.Disabled)
 	return accBackend
 }
diff --git a/proxy/pkg/user/backend/test/backend_mock.go b/proxy/pkg/user/backend/test/backend_mock.go
index 1ebba2b92da..be27d84b518 100644
--- a/proxy/pkg/user/backend/test/backend_mock.go
+++ b/proxy/pkg/user/backend/test/backend_mock.go
@@ -5,9 +5,10 @@ package test
 
 import (
 	"context"
-	"github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
-	"github.com/owncloud/ocis/proxy/pkg/user/backend"
 	"sync"
+
+	userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
+	"github.com/owncloud/ocis/proxy/pkg/user/backend"
 )
 
 // Ensure, that UserBackendMock does implement UserBackend.
@@ -40,13 +41,13 @@ var _ backend.UserBackend = &UserBackendMock{}
 //     }
 type UserBackendMock struct {
 	// AuthenticateFunc mocks the Authenticate method.
-	AuthenticateFunc func(ctx context.Context, username string, password string) (*userv1beta1.User, error)
+	AuthenticateFunc func(ctx context.Context, username string, password string) (*userv1beta1.User, string, error)
 
 	// CreateUserFromClaimsFunc mocks the CreateUserFromClaims method.
 	CreateUserFromClaimsFunc func(ctx context.Context, claims map[string]interface{}) (*userv1beta1.User, error)
 
 	// GetUserByClaimsFunc mocks the GetUserByClaims method.
-	GetUserByClaimsFunc func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error)
+	GetUserByClaimsFunc func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, string, error)
 
 	// GetUserGroupsFunc mocks the GetUserGroups method.
 	GetUserGroupsFunc func(ctx context.Context, userID string)
@@ -95,7 +96,7 @@ type UserBackendMock struct {
 }
 
 // Authenticate calls AuthenticateFunc.
-func (mock *UserBackendMock) Authenticate(ctx context.Context, username string, password string) (*userv1beta1.User, error) {
+func (mock *UserBackendMock) Authenticate(ctx context.Context, username string, password string) (*userv1beta1.User, string, error) {
 	if mock.AuthenticateFunc == nil {
 		panic("UserBackendMock.AuthenticateFunc: method is nil but UserBackend.Authenticate was just called")
 	}
@@ -169,7 +170,7 @@ func (mock *UserBackendMock) CreateUserFromClaimsCalls() []struct {
 }
 
 // GetUserByClaims calls GetUserByClaimsFunc.
-func (mock *UserBackendMock) GetUserByClaims(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error) {
+func (mock *UserBackendMock) GetUserByClaims(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, string, error) {
 	if mock.GetUserByClaimsFunc == nil {
 		panic("UserBackendMock.GetUserByClaimsFunc: method is nil but UserBackend.GetUserByClaims was just called")
 	}