diff --git a/auth.go b/auth.go
new file mode 100644
index 0000000000000000000000000000000000000000..871835f61db30c7ce56c39327a7834829de542ad
--- /dev/null
+++ b/auth.go
@@ -0,0 +1,153 @@
+package perxis
+
+import (
+	"context"
+	"runtime"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+)
+
+var (
+	ErrAccessDenied = errors.New("access denied")
+)
+
+type Principal interface {
+	GetID() string
+}
+
+// Authenticator интерфейс для аутентификации
+type Authenticator interface {
+	// Authenticate аутентификация
+	Authenticate(ctx context.Context) (Principal, error)
+}
+
+type Authorization struct {
+	Authorizer Authorizer
+}
+
+// Authorizer интерфейс для авторизации
+type Authorizer interface {
+	Authorize(principal Principal, action string, resource any) (*Authorization, error)
+}
+
+var (
+	authorizer    Authorizer
+	authenticator Authenticator
+)
+
+func SetAuthorizer(a Authorizer) {
+	authorizer = a
+}
+
+func SetAuthenticator(a Authenticator) {
+	authenticator = a
+}
+
+func Authenticate(ctx context.Context) (Principal, error) {
+	if authenticator == nil {
+		return nil, nil
+	}
+	return authenticator.Authenticate(ctx)
+}
+
+func Authorize(principal Principal, action string, resource any) (*Authorization, error) {
+	if authorizer == nil {
+		return nil, nil
+	}
+	return authorizer.Authorize(principal, action, resource)
+}
+
+func AuthorizeContext(ctx context.Context, action string, resource any) (*Authorization, error) {
+	principal, err := Authenticate(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return Authorize(principal, action, resource)
+}
+
+func IsAllowed(ctx context.Context, res any) (*Authorization, error) {
+	pc, _, _, _ := runtime.Caller(1)
+	parts := strings.Split(runtime.FuncForPC(pc).Name(), ".")
+	action := parts[len(parts)-1]
+	return AuthorizeContext(ctx, action, res)
+}
+
+//type AccessRequest struct {
+//	Resource any    // Ресурс для которого запрашивается доступ
+//	Subject  any    // Субъект, который запрашивает доступ
+//	Action   string // Действие, которое запрашивается
+//}
+//
+//type AccessResponse struct {
+//	IsAllowed bool
+//	Filter    func(action string, v any) any
+//	Err       error
+//}
+//
+//type Policy interface {
+//	IsAllowed(ctx context.Context, req *AccessRequest) (*AccessResponse, error)
+//}
+//
+//func IsMethodAllowed(ctx context.Context, resource any) (*AccessResponse, error) {
+//	pc, _, _, _ := runtime.Caller(1)
+//	parts := strings.Split(runtime.FuncForPC(pc).Name(), ".")
+//	req := &AccessRequest{Action: parts[len(parts)-1], Resource: resource}
+//	return IsAllowed(ctx, req)
+//}
+//
+//func IsAllowed(ctx context.Context, req *AccessRequest) (*AccessResponse, error) {
+//	if req != nil && req.Subject == nil {
+//		principal := GetPrincipal(ctx)
+//		if principal != nil {
+//			req.Subject = principal
+//		}
+//	}
+//
+//	fmt.Printf("IsAllowed: %v\n", req)
+//	return DefaultPolicy.IsAllowed(ctx, req)
+//}
+//
+//type Policies []Policy
+//
+//func (p Policies) IsAllowed(ctx context.Context, req *AccessRequest) (*AccessResponse, error) {
+//	for _, policy := range p {
+//		if resp, err := policy.IsAllowed(ctx, req); resp != nil || err != nil {
+//			return resp, err
+//		}
+//	}
+//	return nil, nil
+//}
+//
+//type defaultPolicy struct {
+//	policies []Policy
+//}
+//
+//func (d *defaultPolicy) IsAllowed(ctx context.Context, req *AccessRequest) (*AccessResponse, error) {
+//	if p, ok := req.Subject.(Policy); ok {
+//		if resp, err := p.IsAllowed(ctx, req); resp != nil || err != nil {
+//			return resp, err
+//		}
+//	}
+//
+//	for _, policy := range d.policies {
+//		if resp, err := policy.IsAllowed(ctx, req); resp != nil || err != nil {
+//			return resp, err
+//		}
+//	}
+//
+//	// Policy not found
+//	return nil, ErrAccessDenied
+//}
+//
+//type AllowPolicy struct{}
+//
+//func (AllowPolicy) IsAllowed(ctx context.Context, req *AccessRequest) (*AccessResponse, error) {
+//	return &AccessResponse{IsAllowed: true, Filter: func(action string, v any) any { return v }}, nil
+//}
+//
+//type DenyPolicy struct{}
+//
+//func (DenyPolicy) IsAllowed(ctx context.Context, req *AccessRequest) (*AccessResponse, error) {
+//	return nil, ErrAccessDenied
+//}
diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go
new file mode 100644
index 0000000000000000000000000000000000000000..8832b06d1881b3a6df3cc986996aac9e81012a7e
--- /dev/null
+++ b/pkg/auth/auth.go
@@ -0,0 +1 @@
+package auth
diff --git a/pkg/roles/role.go b/pkg/roles/role.go
index 76520f8fbf353c027ae782efb1bf79a1bfa453f5..1557de77823f9e5aa40631234d195ee5f8dd99ea 100644
--- a/pkg/roles/role.go
+++ b/pkg/roles/role.go
@@ -52,7 +52,7 @@ func (r Role) Clone() *Role {
 }
 
 func (r Role) CanAccessEnvironment(ctx context.Context, env *environments.Environment, service environments.Environments) bool {
-	if env.SpaceID == "" || env.ID == "" {
+	if env.ID == "" {
 		return false
 	}
 
@@ -66,13 +66,13 @@ func (r Role) CanAccessEnvironment(ctx context.Context, env *environments.Enviro
 	}
 
 	// Если окружение передано не полное, это означает, что надо его перезапросить
-	if env.Description == "" && env.Aliases == nil && env.StateInfo == nil {
+	if service != nil && env.Description == "" && env.Aliases == nil && env.StateInfo == nil {
 		if data.GlobMatch(env.ID, r.Environments...) {
 			return true
 		}
 
 		var err error
-		env, err = service.Get(ctx, env.SpaceID, env.ID)
+		env, err = service.Get(ctx, r.SpaceID, env.ID)
 		if err != nil || env == nil {
 			return false
 		}