Repository: mrparkers/terraform-provider-keycloak
Pull request #215 (2020-02-11 13:49:51)

Support for flows, subflows and executions
support for flows, subflows and executions
(+ added missing fields in data_source_keycloak_realm)

This are the changes form PR [#138](https://github.com/mrparkers/terraform-provider-keycloak/pull/138) + adaptations needed for keycloak 8.0.x 

See comments in #138 for more info

mrparkers: Thanks for re-opening this, I'll have some time later this week to review this again :smile:

Patch:
diff --git a/example/main.tf b/example/main.tf
index 0284e68..9101763 100644
--- a/example/main.tf
+++ b/example/main.tf
@@ -643,3 +643,55 @@ resource "keycloak_openid_client_service_account_role" "read_token" {
   service_account_user_id = "${keycloak_openid_client.test_client_auth.service_account_user_id}"
   role                    = "read-token"
 }
+
+resource "keycloak_authentication_flow" "browser-copy-flow" {
+  alias    = "browserCopyFlow"
+  realm_id = "${keycloak_realm.test.id}"
+  description = "browser based authentication"
+}
+
+resource "keycloak_authentication_execution" "browser-copy-cookie" {
+  realm_id = "${keycloak_realm.test.id}"
+  parent_flow_alias = "${keycloak_authentication_flow.browser-copy-flow.alias}"
+  authenticator = "auth-cookie"
+  requirement = "ALTERNATIVE"
+  depends_on = ["keycloak_authentication_execution.browser-copy-kerberos"]
+}
+
+resource "keycloak_authentication_execution" "browser-copy-kerberos" {
+  realm_id = "${keycloak_realm.test.id}"
+  parent_flow_alias = "${keycloak_authentication_flow.browser-copy-flow.alias}"
+  authenticator = "auth-spnego"
+  requirement = "DISABLED"
+}
+
+resource "keycloak_authentication_execution" "browser-copy-idp-redirect" {
+  realm_id = "${keycloak_realm.test.id}"
+  parent_flow_alias = "${keycloak_authentication_flow.browser-copy-flow.alias}"
+  authenticator = "identity-provider-redirector"
+  requirement = "ALTERNATIVE"
+  depends_on = ["keycloak_authentication_execution.browser-copy-cookie"]
+}
+
+resource "keycloak_authentication_subflow" "browser-copy-flow-forms" {
+  realm_id = "${keycloak_realm.test.id}"
+  parent_flow_alias = "${keycloak_authentication_flow.browser-copy-flow.alias}"
+  alias    = "browser-copy-flow-forms"
+  requirement = "ALTERNATIVE"
+  depends_on = ["keycloak_authentication_execution.browser-copy-idp-redirect"]
+}
+
+resource "keycloak_authentication_execution" "browser-copy-auth-username-password-form" {
+  realm_id = "${keycloak_realm.test.id}"
+  parent_flow_alias = "${keycloak_authentication_subflow.browser-copy-flow-forms.alias}"
+  authenticator = "auth-username-password-form"
+  requirement = "REQUIRED"
+}
+
+resource "keycloak_authentication_execution" "browser-copy-otp" {
+  realm_id = "${keycloak_realm.test.id}"
+  parent_flow_alias = "${keycloak_authentication_subflow.browser-copy-flow-forms.alias}"
+  authenticator = "auth-otp-form"
+  requirement = "REQUIRED"
+  depends_on = ["keycloak_authentication_execution.browser-copy-auth-username-password-form"]
+}
diff --git a/keycloak/authentication_execution.go b/keycloak/authentication_execution.go
new file mode 100644
index 0000000..b8b2d6b
--- /dev/null
+++ b/keycloak/authentication_execution.go
@@ -0,0 +1,144 @@
+package keycloak
+
+import (
+	"fmt"
+)
+
+// this is only used when creating an execution on a flow.
+// other fields can be provided to the API but they are ignored
+// POST /realms/${realmId}/authentication/flows/${flowAlias}/executions/execution
+type authenticationExecutionCreate struct {
+	Provider string `json:"provider"` //authenticator of the execution
+}
+
+type authenticationExecutionRequirementUpdate struct {
+	RealmId         string `json:"-"`
+	ParentFlowAlias string `json:"-"`
+	Id              string `json:"id"`
+	Requirement     string `json:"requirement"`
+}
+
+// this type is returned by GET /realms/${realmId}/authentication/flows/${flowAlias}/executions
+type AuthenticationExecution struct {
+	Id                   string `json:"id"`
+	RealmId              string `json:"-"`
+	ParentFlowAlias      string `json:"-"`
+	Authenticator        string `json:"authenticator"` //can be any authenticator from GET realms/{realm}/authentication/authenticator-providers OR GET realms/{realm}/authentication/client-authenticator-providers OR GET realms/{realm}/authentication/form-action-providers
+	AuthenticationConfig string `json:"authenticationConfig"`
+	AuthenticationFlow   bool   `json:"authenticationFlow"`
+	FlowId               string `json:"flowId"`
+	ParentFlowId         string `json:"parentFlow"`
+	Priority             int    `json:"priority"`
+	Requirement          string `json:"requirement"`
+}
+
+// another model is used for GET /realms/${realmId}/authentication/executions/${executionId}, but I am going to try to avoid using this API
+type AuthenticationExecutionInfo struct {
+	Id                   string `json:"id"`
+	RealmId              string `json:"-"`
+	ParentFlowAlias      string `json:"-"`
+	Alias                string `json:"alias"`
+	AuthenticationConfig string `json:"authenticationConfig"`
+	AuthenticationFlow   bool   `json:"authenticationFlow"`
+	Configurable         bool   `json:"configurable"`
+	FlowId               string `json:"flowId"`
+	Index                int    `json:"index"`
+	Level                int    `json:"level"`
+	ProviderId           string `json:"providerId"`
+	Requirement          string `json:"requirement"`
+}
+
+type AuthenticationExecutionList []*AuthenticationExecutionInfo
+
+func (list AuthenticationExecutionList) Len() int {
+	return len(list)
+}
+
+func (list AuthenticationExecutionList) Less(i, j int) bool {
+	return list[i].Index < list[j].Index
+}
+
+func (list AuthenticationExecutionList) Swap(i, j int) {
+	list[i], list[j] = list[j], list[i]
+}
+
+func (keycloakClient *KeycloakClient) ListAuthenticationExecutions(realmId, parentFlowAlias string) (AuthenticationExecutionList, error) {
+	var authenticationExecutions []*AuthenticationExecutionInfo
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/authentication/flows/%s/executions", realmId, parentFlowAlias), &authenticationExecutions, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return authenticationExecutions, err
+}
+
+func (keycloakClient *KeycloakClient) NewAuthenticationExecution(execution *AuthenticationExecution) error {
+	_, location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/authentication/flows/%s/executions/execution", execution.RealmId, execution.ParentFlowAlias), &authenticationExecutionCreate{Provider: execution.Authenticator})
+	if err != nil {
+		return err
+	}
+
+	execution.Id = getIdFromLocationHeader(location)
+
+	err = keycloakClient.UpdateAuthenticationExecution(execution)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) GetAuthenticationExecution(realmId, parentFlowAlias, id string) (*AuthenticationExecution, error) {
+	var authenticationExecution AuthenticationExecution
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/authentication/executions/%s", realmId, id), &authenticationExecution, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	authenticationExecution.RealmId = realmId
+	authenticationExecution.ParentFlowAlias = parentFlowAlias
+
+	return &authenticationExecution, nil
+}
+
+func (keycloakClient *KeycloakClient) UpdateAuthenticationExecution(execution *AuthenticationExecution) error {
+	authenticationExecutionUpdateRequirement := &authenticationExecutionRequirementUpdate{
+		RealmId:         execution.RealmId,
+		ParentFlowAlias: execution.ParentFlowAlias,
+		Id:              execution.Id,
+		Requirement:     execution.Requirement,
+	}
+	return keycloakClient.UpdateAuthenticationExecutionRequirement(authenticationExecutionUpdateRequirement)
+}
+
+func (keycloakClient *KeycloakClient) UpdateAuthenticationExecutionRequirement(executionRequirementUpdate *authenticationExecutionRequirementUpdate) error {
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/authentication/flows/%s/executions", executionRequirementUpdate.RealmId, executionRequirementUpdate.ParentFlowAlias), executionRequirementUpdate)
+}
+
+func (keycloakClient *KeycloakClient) DeleteAuthenticationExecution(realmId, id string) error {
+	err := keycloakClient.delete(fmt.Sprintf("/realms/%s/authentication/executions/%s", realmId, id), nil)
+	if err != nil {
+		// For whatever reason, this fails sometimes with a 500 during acceptance tests. try again
+		return keycloakClient.delete(fmt.Sprintf("/realms/%s/authentication/executions/%s", realmId, id), nil)
+	}
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) RaiseAuthenticationExecutionPriority(realmId, id string) error {
+	_, _, err := keycloakClient.post(fmt.Sprintf("/realms/%s/authentication/executions/%s/raise-priority", realmId, id), nil)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) LowerAuthenticationExecutionPriority(realmId, id string) error {
+	_, _, err := keycloakClient.post(fmt.Sprintf("/realms/%s/authentication/executions/%s/lower-priority", realmId, id), nil)
+	if err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/keycloak/authentication_flow.go b/keycloak/authentication_flow.go
new file mode 100644
index 0000000..8ce3de3
--- /dev/null
+++ b/keycloak/authentication_flow.go
@@ -0,0 +1,55 @@
+package keycloak
+
+import (
+	"fmt"
+)
+
+type AuthenticationFlow struct {
+	Id          string `json:"id,omitempty"`
+	RealmId     string `json:"-"`
+	Alias       string `json:"alias"`
+	Description string `json:"description"`
+	ProviderId  string `json:"providerId"` // "basic-flow" or "client-flow"
+	TopLevel    bool   `json:"topLevel"`
+	BuiltIn     bool   `json:"builtIn"`
+}
+
+func (keycloakClient *KeycloakClient) NewAuthenticationFlow(authenticationFlow *AuthenticationFlow) error {
+	authenticationFlow.TopLevel = true
+	authenticationFlow.BuiltIn = false
+
+	_, location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/authentication/flows", authenticationFlow.RealmId), authenticationFlow)
+	if err != nil {
+		return err
+	}
+	authenticationFlow.Id = getIdFromLocationHeader(location)
+
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) GetAuthenticationFlow(realmId, id string) (*AuthenticationFlow, error) {
+	var authenticationFlow AuthenticationFlow
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/authentication/flows/%s", realmId, id), &authenticationFlow, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	authenticationFlow.RealmId = realmId
+	return &authenticationFlow, nil
+}
+
+func (keycloakClient *KeycloakClient) UpdateAuthenticationFlow(authenticationFlow *AuthenticationFlow) error {
+	authenticationFlow.TopLevel = true
+	authenticationFlow.BuiltIn = false
+
+	return keycloakClient.put(fmt.Sprintf("/realms/%s/authentication/flows/%s", authenticationFlow.RealmId, authenticationFlow.Id), authenticationFlow)
+}
+
+func (keycloakClient *KeycloakClient) DeleteAuthenticationFlow(realmId, id string) error {
+	err := keycloakClient.delete(fmt.Sprintf("/realms/%s/authentication/flows/%s", realmId, id), nil)
+	if err != nil {
+		// For whatever reason, this fails sometimes with a 500 during acceptance tests. try again
+		return keycloakClient.delete(fmt.Sprintf("/realms/%s/authentication/flows/%s", realmId, id), nil)
+	}
+	return nil
+}
diff --git a/keycloak/authentication_subflow.go b/keycloak/authentication_subflow.go
new file mode 100644
index 0000000..1a3305a
--- /dev/null
+++ b/keycloak/authentication_subflow.go
@@ -0,0 +1,157 @@
+package keycloak
+
+import (
+	"errors"
+	"fmt"
+)
+
+type AuthenticationSubFlow struct {
+	Id              string `json:"id,omitempty"`
+	Alias           string `json:"alias"`
+	RealmId         string `json:"-"`
+	ParentFlowAlias string `json:"-"`
+	ProviderId      string `json:"providerId"` // "basic-flow" or "client-flow" or form-flow see /keycloak/server-spi/src/main/java/org/keycloak/models/AuthenticationFlowModel.java
+	TopLevel        bool   `json:"topLevel"`   // should only be false if this is a subflow
+	BuiltIn         bool   `json:"builtIn"`    // this controls whether or not this flow can be edited from the console. it can be updated, but this provider will only set it to `true`
+	Description     string `json:"description"`
+	//execution part
+	Authenticator string `json:"-"` //can be any authenticator see /auth/admin/master/console/#/server-info/providers (not limited to the authenticator spi section) for example could also be part of the form-action spi
+	Priority      int    `json:"-"`
+	Requirement   string `json:"-"`
+}
+
+//each subflow creates a flow and an execution under the covers
+type authenticationSubFlowCreate struct {
+	Alias       string `json:"alias"`
+	Type        string `json:"type"`     //providerId of the flow
+	Provider    string `json:"provider"` //authenticator of the execution
+	Description string `json:"description"`
+}
+
+func (keycloakClient *KeycloakClient) NewAuthenticationSubFlow(authenticationSubFlow *AuthenticationSubFlow) error {
+	authenticationSubFlow.TopLevel = false
+	authenticationSubFlow.BuiltIn = false
+	authenticationSubFlowCreate := &authenticationSubFlowCreate{
+		Alias:       authenticationSubFlow.Alias,
+		Type:        authenticationSubFlow.ProviderId,    //providerId of the flow
+		Provider:    authenticationSubFlow.Authenticator, //seems this can be empty //authenticator of the execution
+		Description: authenticationSubFlow.Description,
+	}
+
+	_, location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/authentication/flows/%s/executions/flow", authenticationSubFlow.RealmId, authenticationSubFlow.ParentFlowAlias), authenticationSubFlowCreate)
+	if err != nil {
+		return err
+	}
+	authenticationSubFlow.Id = getIdFromLocationHeader(location)
+
+	if authenticationSubFlow.Requirement != "DISABLED" {
+		return keycloakClient.UpdateAuthenticationSubFlow(authenticationSubFlow)
+	}
+	return nil
+}
+
+func (keycloakClient *KeycloakClient) GetAuthenticationSubFlow(realmId, parentFlowAlias, id string) (*AuthenticationSubFlow, error) {
+	var authenticationSubFlow AuthenticationSubFlow
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/authentication/flows/%s", realmId, id), &authenticationSubFlow, nil)
+	if err != nil {
+		return nil, err
+	}
+	authenticationSubFlow.RealmId = realmId
+	authenticationSubFlow.ParentFlowAlias = parentFlowAlias
+
+	executionId, err := keycloakClient.getExecutionId(&authenticationSubFlow)
+	if err != nil {
+		return nil, err
+	}
+
+	subFlowExecution, err := keycloakClient.GetAuthenticationExecution(realmId, parentFlowAlias, executionId)
+	if err != nil {
+		return nil, err
+	}
+	authenticationSubFlow.Authenticator = subFlowExecution.Authenticator
+	authenticationSubFlow.Requirement = subFlowExecution.Requirement
+
+	return &authenticationSubFlow, nil
+}
+
+func (keycloakClient *KeycloakClient) getExecutionId(authenticationSubFlow *AuthenticationSubFlow) (string, error) {
+	list, err := keycloakClient.ListAuthenticationExecutions(authenticationSubFlow.RealmId, authenticationSubFlow.ParentFlowAlias)
+	if err != nil {
+		return "", err
+	}
+
+	for _, ex := range list {
+		if ex.FlowId == authenticationSubFlow.Id {
+			return ex.Id, nil
+		}
+	}
+	return "", errors.New("no execution id found for subflow")
+}
+
+func (keycloakClient *KeycloakClient) UpdateAuthenticationSubFlow(authenticationSubFlow *AuthenticationSubFlow) error {
+	authenticationSubFlow.TopLevel = false
+	authenticationSubFlow.BuiltIn = false
+
+	err := keycloakClient.put(fmt.Sprintf("/realms/%s/authentication/flows/%s", authenticationSubFlow.RealmId, authenticationSubFlow.Id), authenticationSubFlow)
+
+	if err != nil {
+		return err
+	}
+
+	executionId, err := keycloakClient.getExecutionId(authenticationSubFlow)
+	if err != nil {
+		return err
+	}
+
+	//update requirement
+	authenticationExecutionUpdateRequirement := &authenticationExecutionRequirementUpdate{
+		RealmId:         authenticationSubFlow.RealmId,
+		ParentFlowAlias: authenticationSubFlow.ParentFlowAlias,
+		Id:              executionId,
+		Requirement:     authenticationSubFlow.Requirement,
+	}
+	return keycloakClient.UpdateAuthenticationExecutionRequirement(authenticationExecutionUpdateRequirement)
+
+}
+
+func (keycloakClient *KeycloakClient) DeleteAuthenticationSubFlow(realmId, parentFlowAlias, id string) error {
+	authenticationSubFlow := AuthenticationSubFlow{
+		Id:              id,
+		ParentFlowAlias: parentFlowAlias,
+		RealmId:         realmId,
+	}
+	executionId, err := keycloakClient.getExecutionId(&authenticationSubFlow)
+	if err != nil {
+		return err
+	}
+
+	return keycloakClient.DeleteAuthenticationExecution(authenticationSubFlow.RealmId, executionId)
+}
+
+func (keycloakClient *KeycloakClient) RaiseAuthenticationSubFlowPriority(realmId, parentFlowAlias, id string) error {
+	authenticationSubFlow := AuthenticationSubFlow{
+		Id:              id,
+		ParentFlowAlias: parentFlowAlias,
+		RealmId:         realmId,
+	}
+	executionId, err := keycloakClient.getExecutionId(&authenticationSubFlow)
+	if err != nil {
+		return err
+	}
+
+	return keycloakClient.RaiseAuthenticationExecutionPriority(authenticationSubFlow.RealmId, executionId)
+}
+
+func (keycloakClient *KeycloakClient) LowerAuthenticationSubFlowPriority(realmId, parentFlowAlias, id string) error {
+	authenticationSubFlow := AuthenticationSubFlow{
+		Id:              id,
+		ParentFlowAlias: parentFlowAlias,
+		RealmId:         realmId,
+	}
+	executionId, err := keycloakClient.getExecutionId(&authenticationSubFlow)
+	if err != nil {
+		return err
+	}
+
+	return keycloakClient.LowerAuthenticationExecutionPriority(authenticationSubFlow.RealmId, executionId)
+}
diff --git a/provider/data_source_keycloak_realm.go b/provider/data_source_keycloak_realm.go
index 8317d07..8a10698 100644
--- a/provider/data_source_keycloak_realm.go
+++ b/provider/data_source_keycloak_realm.go
@@ -60,6 +60,10 @@ func dataSourceKeycloakRealm() *schema.Resource {
 				Type:     schema.TypeBool,
 				Computed: true,
 			},
+			"ssl_required": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
 
 			//Smtp server
 
@@ -262,6 +266,43 @@ func dataSourceKeycloakRealm() *schema.Resource {
 								},
 							},
 						},
+						"brute_force_detection": {
+							Type:     schema.TypeList,
+							Optional: true,
+							Computed: true,
+							Elem: &schema.Resource{
+								Schema: map[string]*schema.Schema{
+									"permanent_lockout": { //Permanent Lockout
+										Type:     schema.TypeBool,
+										Computed: true,
+									},
+									"max_login_failures": { //failureFactor
+										Type:     schema.TypeInt,
+										Computed: true,
+									},
+									"wait_increment_seconds": { //Wait Increment
+										Type:     schema.TypeInt,
+										Computed: true,
+									},
+									"quick_login_check_milli_seconds": { //Quick Login Check Milli Seconds
+										Type:     schema.TypeInt,
+										Computed: true,
+									},
+									"minimum_quick_login_wait_seconds": { //Minimum Quick Login Wait
+										Type:     schema.TypeInt,
+										Computed: true,
+									},
+									"max_failure_wait_seconds": { //Max Wait
+										Type:     schema.TypeInt,
+										Computed: true,
+									},
+									"failure_reset_time_seconds": { //maxDeltaTimeSeconds
+										Type:     schema.TypeInt,
+										Computed: true,
+									},
+								},
+							},
+						},
 					},
 				},
 			},
@@ -302,6 +343,11 @@ func dataSourceKeycloakRealm() *schema.Resource {
 				Description: "Which flow should be used for DockerAuthenticationFlow",
 				Computed:    true,
 			},
+			"attributes": {
+				Type:     schema.TypeMap,
+				Optional: true,
+				Computed: true,
+			},
 		},
 	}
 }
diff --git a/provider/provider.go b/provider/provider.go
index d46efe8..1ad5006 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -61,6 +61,9 @@ func KeycloakProvider() *schema.Provider {
 			"keycloak_openid_client_service_account_role":              resourceKeycloakOpenidClientServiceAccountRole(),
 			"keycloak_openid_client_service_account_realm_role":        resourceKeycloakOpenidClientServiceAccountRealmRole(),
 			"keycloak_role":                                            resourceKeycloakRole(),
+			"keycloak_authentication_flow":                             resourceKeycloakAuthenticationFlow(),
+			"keycloak_authentication_subflow":                          resourceKeycloakAuthenticationSubFlow(),
+			"keycloak_authentication_execution":                        resourceKeycloakAuthenticationExecution(),
 		},
 		Schema: map[string]*schema.Schema{
 			"client_id": {
diff --git a/provider/resource_keycloak_authentication_execution.go b/provider/resource_keycloak_authentication_execution.go
new file mode 100644
index 0000000..07d7043
--- /dev/null
+++ b/provider/resource_keycloak_authentication_execution.go
@@ -0,0 +1,135 @@
+package provider
+
+import (
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"strings"
+)
+
+func resourceKeycloakAuthenticationExecution() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakAuthenticationExecutionCreate,
+		Read:   resourceKeycloakAuthenticationExecutionRead,
+		Delete: resourceKeycloakAuthenticationExecutionDelete,
+		Update: resourceKeycloakAuthenticationExecutionUpdate,
+		Importer: &schema.ResourceImporter{
+			State: resourceKeycloakAuthenticationExecutionImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"realm_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"parent_flow_alias": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"authenticator": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"requirement": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				ValidateFunc: validation.StringInSlice([]string{"REQUIRED", "ALTERNATIVE", "OPTIONAL", "CONDITIONAL", "DISABLED"}, false), //OPTIONAL is removed from 8.0.0 onwards
+				Default:      "DISABLED",
+			},
+		},
+	}
+}
+
+func mapFromDataToAuthenticationExecution(data *schema.ResourceData) *keycloak.AuthenticationExecution {
+	authenticationExecution := &keycloak.AuthenticationExecution{
+		Id:              data.Id(),
+		RealmId:         data.Get("realm_id").(string),
+		ParentFlowAlias: data.Get("parent_flow_alias").(string),
+		Authenticator:   data.Get("authenticator").(string),
+		Requirement:     data.Get("requirement").(string),
+	}
+
+	return authenticationExecution
+}
+
+func mapFromAuthenticationExecutionToData(data *schema.ResourceData, authenticationExecution *keycloak.AuthenticationExecution) {
+	data.SetId(authenticationExecution.Id)
+
+	data.Set("realm_id", authenticationExecution.RealmId)
+	data.Set("parent_flow_alias", authenticationExecution.ParentFlowAlias)
+	data.Set("authenticator", authenticationExecution.Authenticator)
+	data.Set("requirement", authenticationExecution.Requirement)
+}
+
+func resourceKeycloakAuthenticationExecutionCreate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	authenticationExecution := mapFromDataToAuthenticationExecution(data)
+
+	err := keycloakClient.NewAuthenticationExecution(authenticationExecution)
+	if err != nil {
+		return err
+	}
+
+	mapFromAuthenticationExecutionToData(data, authenticationExecution)
+
+	return resourceKeycloakAuthenticationExecutionRead(data, meta)
+}
+
+func resourceKeycloakAuthenticationExecutionRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	parentFlowAlias := data.Get("parent_flow_alias").(string)
+	id := data.Id()
+
+	authenticationExecution, err := keycloakClient.GetAuthenticationExecution(realmId, parentFlowAlias, id)
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+
+	mapFromAuthenticationExecutionToData(data, authenticationExecution)
+
+	return nil
+}
+
+func resourceKeycloakAuthenticationExecutionUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	authenticationExecution := mapFromDataToAuthenticationExecution(data)
+
+	err := keycloakClient.UpdateAuthenticationExecution(authenticationExecution)
+	if err != nil {
+		return err
+	}
+
+	mapFromAuthenticationExecutionToData(data, authenticationExecution)
+
+	return nil
+}
+
+func resourceKeycloakAuthenticationExecutionDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	id := data.Id()
+
+	return keycloakClient.DeleteAuthenticationExecution(realmId, id)
+}
+
+func resourceKeycloakAuthenticationExecutionImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
+	parts := strings.Split(d.Id(), "/")
+
+	if len(parts) != 3 {
+		return nil, fmt.Errorf("Invalid import. Supported import formats: {{realmId}}/{{parentFlowAlias}}/{{authenticationExecutionId}}")
+	}
+
+	d.Set("realm_id", parts[0])
+	d.Set("parent_flow_alias", parts[1])
+	d.SetId(parts[2])
+
+	return []*schema.ResourceData{d}, nil
+}
diff --git a/provider/resource_keycloak_authentication_flow.go b/provider/resource_keycloak_authentication_flow.go
new file mode 100644
index 0000000..79f77ba
--- /dev/null
+++ b/provider/resource_keycloak_authentication_flow.go
@@ -0,0 +1,127 @@
+package provider
+
+import (
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"strings"
+)
+
+func resourceKeycloakAuthenticationFlow() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakAuthenticationFlowCreate,
+		Read:   resourceKeycloakAuthenticationFlowRead,
+		Delete: resourceKeycloakAuthenticationFlowDelete,
+		Update: resourceKeycloakAuthenticationFlowUpdate,
+		Importer: &schema.ResourceImporter{
+			State: resourceKeycloakAuthenticationFlowImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"realm_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"alias": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"provider_id": {
+				Type:         schema.TypeString,
+				Default:      "basic-flow",
+				ValidateFunc: validation.StringInSlice([]string{"basic-flow", "client-flow"}, false), //it seems toplevel can only one of these and not 'form-flow'
+				Optional:     true,
+			},
+			"description": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+		},
+	}
+}
+
+func mapFromDataToAuthenticationFlow(data *schema.ResourceData) *keycloak.AuthenticationFlow {
+	authenticationFlow := &keycloak.AuthenticationFlow{
+		Id:          data.Id(),
+		RealmId:     data.Get("realm_id").(string),
+		Alias:       data.Get("alias").(string),
+		ProviderId:  data.Get("provider_id").(string),
+		Description: data.Get("description").(string),
+	}
+
+	return authenticationFlow
+}
+
+func mapFromAuthenticationFlowToData(data *schema.ResourceData, authenticationFlow *keycloak.AuthenticationFlow) {
+	data.SetId(authenticationFlow.Id)
+	data.Set("realm_id", authenticationFlow.RealmId)
+	data.Set("alias", authenticationFlow.Alias)
+	data.Set("provider_id", authenticationFlow.ProviderId)
+	data.Set("description", authenticationFlow.Description)
+}
+
+func resourceKeycloakAuthenticationFlowCreate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	authenticationFlow := mapFromDataToAuthenticationFlow(data)
+
+	err := keycloakClient.NewAuthenticationFlow(authenticationFlow)
+	if err != nil {
+		return err
+	}
+
+	mapFromAuthenticationFlowToData(data, authenticationFlow)
+	return resourceKeycloakAuthenticationFlowRead(data, meta)
+}
+
+func resourceKeycloakAuthenticationFlowRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	id := data.Id()
+
+	authenticationFlow, err := keycloakClient.GetAuthenticationFlow(realmId, id)
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+
+	mapFromAuthenticationFlowToData(data, authenticationFlow)
+	return nil
+}
+
+func resourceKeycloakAuthenticationFlowUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	authenticationFlow := mapFromDataToAuthenticationFlow(data)
+
+	err := keycloakClient.UpdateAuthenticationFlow(authenticationFlow)
+	if err != nil {
+		return err
+	}
+
+	mapFromAuthenticationFlowToData(data, authenticationFlow)
+	return nil
+}
+
+func resourceKeycloakAuthenticationFlowDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	id := data.Id()
+
+	return keycloakClient.DeleteAuthenticationFlow(realmId, id)
+}
+
+func resourceKeycloakAuthenticationFlowImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
+	parts := strings.Split(d.Id(), "/")
+
+	if len(parts) != 2 {
+		return nil, fmt.Errorf("Invalid import. Supported import formats: {{realmId}}/{{authenticationFlowId}}")
+	}
+
+	d.Set("realm_id", parts[0])
+	d.SetId(parts[1])
+
+	return []*schema.ResourceData{d}, nil
+}
diff --git a/provider/resource_keycloak_authentication_subflow.go b/provider/resource_keycloak_authentication_subflow.go
new file mode 100644
index 0000000..d282ee6
--- /dev/null
+++ b/provider/resource_keycloak_authentication_subflow.go
@@ -0,0 +1,152 @@
+package provider
+
+import (
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"strings"
+)
+
+func resourceKeycloakAuthenticationSubFlow() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceKeycloakAuthenticationSubFlowCreate,
+		Read:   resourceKeycloakAuthenticationSubFlowRead,
+		Delete: resourceKeycloakAuthenticationSubFlowDelete,
+		Update: resourceKeycloakAuthenticationSubFlowUpdate,
+		Importer: &schema.ResourceImporter{
+			State: resourceKeycloakAuthenticationSubFlowImport,
+		},
+		Schema: map[string]*schema.Schema{
+			"realm_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"parent_flow_alias": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"alias": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"provider_id": {
+				Type:         schema.TypeString,
+				Default:      "basic-flow",
+				ValidateFunc: validation.StringInSlice([]string{"basic-flow", "form-flow", "client-flow"}, false),
+				Optional:     true,
+			},
+			"description": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+
+			//execution parts of the subflow
+			"authenticator": {
+				Type:        schema.TypeString,
+				Description: "Might be needed to be set with certain custom subflow with specific authenticator, in general this will remain empty",
+				Optional:    true,
+				ForceNew:    true,
+			},
+			"requirement": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				ValidateFunc: validation.StringInSlice([]string{"REQUIRED", "ALTERNATIVE", "OPTIONAL", "CONDITIONAL", "DISABLED"}, false), //OPTIONAL is removed from 8.0.0 onwards
+				Default:      "DISABLED",
+			},
+		},
+	}
+}
+
+func mapFromDataToAuthenticationSubFlow(data *schema.ResourceData) *keycloak.AuthenticationSubFlow {
+	authenticationSubFlow := &keycloak.AuthenticationSubFlow{
+		Id:              data.Id(),
+		RealmId:         data.Get("realm_id").(string),
+		ParentFlowAlias: data.Get("parent_flow_alias").(string),
+		Alias:           data.Get("alias").(string),
+		ProviderId:      data.Get("provider_id").(string),
+		Description:     data.Get("description").(string),
+		Authenticator:   data.Get("authenticator").(string),
+		Requirement:     data.Get("requirement").(string),
+	}
+
+	return authenticationSubFlow
+}
+
+func mapFromAuthenticationSubFlowToData(data *schema.ResourceData, authenticationSubFlow *keycloak.AuthenticationSubFlow) {
+	data.SetId(authenticationSubFlow.Id)
+	data.Set("realm_id", authenticationSubFlow.RealmId)
+	data.Set("parent_flow_alias", authenticationSubFlow.ParentFlowAlias)
+	data.Set("alias", authenticationSubFlow.Alias)
+	data.Set("provider_id", authenticationSubFlow.ProviderId)
+	data.Set("description", authenticationSubFlow.Description)
+	data.Set("authenticator", authenticationSubFlow.Authenticator)
+	data.Set("requirement", authenticationSubFlow.Requirement)
+}
+
+func resourceKeycloakAuthenticationSubFlowCreate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	authenticationFlow := mapFromDataToAuthenticationSubFlow(data)
+
+	err := keycloakClient.NewAuthenticationSubFlow(authenticationFlow)
+	if err != nil {
+		return err
+	}
+	mapFromAuthenticationSubFlowToData(data, authenticationFlow)
+	return resourceKeycloakAuthenticationSubFlowRead(data, meta)
+}
+
+func resourceKeycloakAuthenticationSubFlowRead(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	parentFlowAlias := data.Get("parent_flow_alias").(string)
+	id := data.Id()
+
+	authenticationFlow, err := keycloakClient.GetAuthenticationSubFlow(realmId, parentFlowAlias, id)
+	if err != nil {
+		return handleNotFoundError(err, data)
+	}
+	mapFromAuthenticationSubFlowToData(data, authenticationFlow)
+	return nil
+}
+
+func resourceKeycloakAuthenticationSubFlowUpdate(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	authenticationFlow := mapFromDataToAuthenticationSubFlow(data)
+
+	err := keycloakClient.UpdateAuthenticationSubFlow(authenticationFlow)
+	if err != nil {
+		return err
+	}
+	mapFromAuthenticationSubFlowToData(data, authenticationFlow)
+	return nil
+}
+
+func resourceKeycloakAuthenticationSubFlowDelete(data *schema.ResourceData, meta interface{}) error {
+	keycloakClient := meta.(*keycloak.KeycloakClient)
+
+	realmId := data.Get("realm_id").(string)
+	parentFlowAlias := data.Get("parent_flow_alias").(string)
+	id := data.Id()
+
+	return keycloakClient.DeleteAuthenticationSubFlow(realmId, parentFlowAlias, id)
+}
+
+func resourceKeycloakAuthenticationSubFlowImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
+	parts := strings.Split(d.Id(), "/")
+
+	if len(parts) != 3 {
+		return nil, fmt.Errorf("Invalid import. Supported import formats: {{realmId}}/{{parentFlowAlias}}/{{authenticationSubFlowId}}")
+	}
+
+	d.Set("realm_id", parts[0])
+	d.Set("parent_flow_alias", parts[1])
+	d.SetId(parts[2])
+
+	return []*schema.ResourceData{d}, nil
+}


Test patch:
diff --git a/provider/resource_keycloak_authentication_execution_test.go b/provider/resource_keycloak_authentication_execution_test.go
new file mode 100644
index 0000000..af2f9e9
--- /dev/null
+++ b/provider/resource_keycloak_authentication_execution_test.go
@@ -0,0 +1,225 @@
+package provider
+
+import (
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/terraform"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"testing"
+)
+
+func TestAccKeycloakAuthenticationExecution_basic(t *testing.T) {
+	realmName := "terraform-r-" + acctest.RandString(10)
+	parentAuthFlowAlias := "terraform-parent-flow-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationExecutionDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationExecution_basic(realmName, parentAuthFlowAlias),
+				Check:  testAccCheckKeycloakAuthenticationExecutionExists("keycloak_authentication_execution.execution"),
+			},
+			{
+				ResourceName:      "keycloak_authentication_execution.execution",
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateIdFunc: getExecutionImportId("keycloak_authentication_execution.execution"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakAuthenticationExecution_createAfterManualDestroy(t *testing.T) {
+	var authenticationExecution = &keycloak.AuthenticationExecution{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	authParentFlowAlias := "terraform-parent-flow-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationExecutionDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationExecution_basic(realmName, authParentFlowAlias),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationExecutionExists("keycloak_authentication_execution.execution"),
+					testAccCheckKeycloakAuthenticationExecutionFetch("keycloak_authentication_execution.execution", authenticationExecution),
+				),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteAuthenticationExecution(authenticationExecution.RealmId, authenticationExecution.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakAuthenticationExecution_basic(realmName, authParentFlowAlias),
+				Check:  testAccCheckKeycloakAuthenticationExecutionExists("keycloak_authentication_execution.execution"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakAuthenticationExecution_updateAuthenticationExecutionRequirement(t *testing.T) {
+	realmName := "terraform-r-" + acctest.RandString(10)
+	authParentFlowAlias := "terraform-parent-flow-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationSubFlowDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationExecution_basic(realmName, authParentFlowAlias),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationExecutionExists("keycloak_authentication_execution.execution"),
+					resource.TestCheckResourceAttr("keycloak_authentication_execution.execution", "requirement", "DISABLED"),
+				),
+			},
+			{
+				Config: testKeycloakAuthenticationExecution_basicWithRequirement(realmName, authParentFlowAlias, "REQUIRED"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationExecutionExists("keycloak_authentication_execution.execution"),
+					resource.TestCheckResourceAttr("keycloak_authentication_execution.execution", "requirement", "REQUIRED"),
+				),
+			},
+			{
+				Config: testKeycloakAuthenticationExecution_basicWithRequirement(realmName, authParentFlowAlias, "DISABLED"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationExecutionExists("keycloak_authentication_execution.execution"),
+					resource.TestCheckResourceAttr("keycloak_authentication_execution.execution", "requirement", "DISABLED"),
+				),
+			},
+		},
+	})
+}
+
+func testAccCheckKeycloakAuthenticationExecutionExists(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		_, err := getAuthenticationExecutionFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakAuthenticationExecutionFetch(resourceName string, authenticationExecution *keycloak.AuthenticationExecution) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		fetchedAuthenticationExecution, err := getAuthenticationExecutionFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		authenticationExecution.Id = fetchedAuthenticationExecution.Id
+		authenticationExecution.ParentFlowAlias = fetchedAuthenticationExecution.ParentFlowAlias
+		authenticationExecution.RealmId = fetchedAuthenticationExecution.RealmId
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakAuthenticationExecutionDestroy() resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		for _, rs := range s.RootModule().Resources {
+			if rs.Type != "keycloak_authentication_execution" {
+				continue
+			}
+
+			id := rs.Primary.ID
+			realm := rs.Primary.Attributes["realm_id"]
+			parentFlowAlias := rs.Primary.Attributes["parent_flow_alias"]
+
+			keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+			authenticationExecution, _ := keycloakClient.GetAuthenticationExecution(realm, parentFlowAlias, id)
+			if authenticationExecution != nil {
+				return fmt.Errorf("authentication flow with id %s still exists", id)
+			}
+		}
+
+		return nil
+	}
+}
+
+func getAuthenticationExecutionFromState(s *terraform.State, resourceName string) (*keycloak.AuthenticationExecution, error) {
+	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+	rs, ok := s.RootModule().Resources[resourceName]
+	if !ok {
+		return nil, fmt.Errorf("resource not found: %s", resourceName)
+	}
+
+	id := rs.Primary.ID
+	realm := rs.Primary.Attributes["realm_id"]
+	parentFlowAlias := rs.Primary.Attributes["parent_flow_alias"]
+
+	authenticationExecution, err := keycloakClient.GetAuthenticationExecution(realm, parentFlowAlias, id)
+
+	if err != nil {
+		return nil, fmt.Errorf("error getting authentication execution with id %s: %s", id, err)
+	}
+
+	return authenticationExecution, nil
+}
+
+func getExecutionImportId(resourceName string) resource.ImportStateIdFunc {
+	return func(s *terraform.State) (string, error) {
+		rs, ok := s.RootModule().Resources[resourceName]
+		if !ok {
+			return "", fmt.Errorf("resource not found: %s", resourceName)
+		}
+
+		id := rs.Primary.ID
+		parentFlowAlias := rs.Primary.Attributes["parent_flow_alias"]
+		realmId := rs.Primary.Attributes["realm_id"]
+
+		return fmt.Sprintf("%s/%s/%s", realmId, parentFlowAlias, id), nil
+	}
+}
+
+func testKeycloakAuthenticationExecution_basic(realm, parentAlias string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_authentication_flow" "flow" {
+	realm_id = "${keycloak_realm.realm.id}"
+	alias    = "%s"
+}
+
+resource "keycloak_authentication_execution" "execution" {
+	realm_id = "${keycloak_realm.realm.id}"
+	parent_flow_alias = "${keycloak_authentication_flow.flow.alias}"
+	authenticator = "auth-cookie"
+}
+	`, realm, parentAlias)
+}
+
+func testKeycloakAuthenticationExecution_basicWithRequirement(realm, parentAlias, requirement string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_authentication_flow" "flow" {
+	realm_id = "${keycloak_realm.realm.id}"
+	alias    = "%s"
+}
+
+resource "keycloak_authentication_execution" "execution" {
+	realm_id = "${keycloak_realm.realm.id}"
+	parent_flow_alias = "${keycloak_authentication_flow.flow.alias}"
+	authenticator = "auth-cookie"
+	requirement = "%s"
+}
+	`, realm, parentAlias, requirement)
+}
diff --git a/provider/resource_keycloak_authentication_flow_test.go b/provider/resource_keycloak_authentication_flow_test.go
new file mode 100644
index 0000000..80ad313
--- /dev/null
+++ b/provider/resource_keycloak_authentication_flow_test.go
@@ -0,0 +1,253 @@
+package provider
+
+import (
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/terraform"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"testing"
+)
+
+func TestAccKeycloakAuthenticationFlow_basic(t *testing.T) {
+	realmName := "terraform-r-" + acctest.RandString(10)
+	authFlowAlias := "terraform-flow-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationFlowDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationFlow_basic(realmName, authFlowAlias),
+				Check:  testAccCheckKeycloakAuthenticationFlowExists("keycloak_authentication_flow.flow"),
+			},
+			{
+				ResourceName:        "keycloak_authentication_flow.flow",
+				ImportState:         true,
+				ImportStateVerify:   true,
+				ImportStateIdPrefix: realmName + "/",
+			},
+		},
+	})
+}
+
+func TestAccKeycloakAuthenticationFlow_createAfterManualDestroy(t *testing.T) {
+	var authenticationFlow = &keycloak.AuthenticationFlow{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	authFlowAlias := "terraform-flow-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationFlowDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationFlow_basic(realmName, authFlowAlias),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationFlowExists("keycloak_authentication_flow.flow"),
+					testAccCheckKeycloakAuthenticationFlowFetch("keycloak_authentication_flow.flow", authenticationFlow),
+				),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteAuthenticationFlow(authenticationFlow.RealmId, authenticationFlow.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakAuthenticationFlow_basic(realmName, authFlowAlias),
+				Check:  testAccCheckKeycloakAuthenticationFlowExists("keycloak_authentication_flow.flow"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakAuthenticationFlow_updateAuthenticationFlow(t *testing.T) {
+	realmName := "terraform-r-" + acctest.RandString(10)
+
+	authFlowAliasBefore := "terraform-flow-before-" + acctest.RandString(10)
+	authFlowAliasAfter := "terraform-flow-after-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationFlowDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationFlow_basic(realmName, authFlowAliasBefore),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationFlowExists("keycloak_authentication_flow.flow"),
+					resource.TestCheckResourceAttr("keycloak_authentication_flow.flow", "alias", authFlowAliasBefore),
+				),
+			},
+			{
+				Config: testKeycloakAuthenticationFlow_basic(realmName, authFlowAliasAfter),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationFlowExists("keycloak_authentication_flow.flow"),
+					resource.TestCheckResourceAttr("keycloak_authentication_flow.flow", "alias", authFlowAliasAfter),
+				),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakAuthenticationFlow_updateRealm(t *testing.T) {
+	realmOne := "terraform-" + acctest.RandString(10)
+	realmTwo := "terraform-" + acctest.RandString(10)
+
+	authFlowAlias := "terraform-flow-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationFlowDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationFlow_updateRealmBefore(realmOne, realmTwo, authFlowAlias),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationFlowExists("keycloak_authentication_flow.flow"),
+					testAccCheckKeycloakAuthenticationFlowBelongsToRealm("keycloak_authentication_flow.flow", realmOne),
+				),
+			},
+			{
+				Config: testKeycloakAuthenticationFlow_updateRealmAfter(realmOne, realmTwo, authFlowAlias),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationFlowExists("keycloak_authentication_flow.flow"),
+					testAccCheckKeycloakAuthenticationFlowBelongsToRealm("keycloak_authentication_flow.flow", realmTwo),
+				),
+			},
+		},
+	})
+}
+
+func testAccCheckKeycloakAuthenticationFlowExists(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		_, err := getAuthenticationFlowFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakAuthenticationFlowFetch(resourceName string, authenticationFlow *keycloak.AuthenticationFlow) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		fetchedAuthenticationFlow, err := getAuthenticationFlowFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		authenticationFlow.Id = fetchedAuthenticationFlow.Id
+		authenticationFlow.RealmId = fetchedAuthenticationFlow.RealmId
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakAuthenticationFlowBelongsToRealm(resourceName, realm string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		authenticationFlow, err := getAuthenticationFlowFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		if authenticationFlow.RealmId != realm {
+			return fmt.Errorf("expected authentication flow with id %s to have realm_id of %s, but got %s", authenticationFlow.Id, realm, authenticationFlow.RealmId)
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakAuthenticationFlowDestroy() resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		for _, rs := range s.RootModule().Resources {
+			if rs.Type != "keycloak_authentication_flow" {
+				continue
+			}
+
+			id := rs.Primary.ID
+			realm := rs.Primary.Attributes["realm_id"]
+
+			keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+			authenticationFlow, _ := keycloakClient.GetAuthenticationFlow(realm, id)
+			if authenticationFlow != nil {
+				return fmt.Errorf("authentication flow with id %s still exists", id)
+			}
+		}
+
+		return nil
+	}
+}
+
+func getAuthenticationFlowFromState(s *terraform.State, resourceName string) (*keycloak.AuthenticationFlow, error) {
+	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+	rs, ok := s.RootModule().Resources[resourceName]
+	if !ok {
+		return nil, fmt.Errorf("resource not found: %s", resourceName)
+	}
+
+	id := rs.Primary.ID
+	realm := rs.Primary.Attributes["realm_id"]
+
+	authenticationFlow, err := keycloakClient.GetAuthenticationFlow(realm, id)
+	if err != nil {
+		return nil, fmt.Errorf("error getting authentication flow with id %s: %s", id, err)
+	}
+
+	return authenticationFlow, nil
+}
+
+func testKeycloakAuthenticationFlow_basic(realm, alias string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_authentication_flow" "flow" {
+	realm_id = "${keycloak_realm.realm.id}"
+	alias    = "%s"
+}
+	`, realm, alias)
+}
+
+func testKeycloakAuthenticationFlow_updateRealmBefore(realmOne, realmTwo, alias string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm_1" {
+	realm = "%s"
+}
+
+resource "keycloak_realm" "realm_2" {
+	realm = "%s"
+}
+
+resource "keycloak_authentication_flow" "flow" {
+	alias    = "%s"
+	realm_id = "${keycloak_realm.realm_1.id}"
+}
+	`, realmOne, realmTwo, alias)
+}
+
+func testKeycloakAuthenticationFlow_updateRealmAfter(realmOne, realmTwo, alias string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm_1" {
+	realm = "%s"
+}
+
+resource "keycloak_realm" "realm_2" {
+	realm = "%s"
+}
+
+resource "keycloak_authentication_flow" "flow" {
+	alias    = "%s"
+	realm_id = "${keycloak_realm.realm_2.id}"
+}
+	`, realmOne, realmTwo, alias)
+}
diff --git a/provider/resource_keycloak_authentication_subflow_test.go b/provider/resource_keycloak_authentication_subflow_test.go
new file mode 100644
index 0000000..141aebc
--- /dev/null
+++ b/provider/resource_keycloak_authentication_subflow_test.go
@@ -0,0 +1,260 @@
+package provider
+
+import (
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/terraform"
+	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
+	"testing"
+)
+
+func TestAccKeycloakAuthenticationSubFlow_basic(t *testing.T) {
+	realmName := "terraform-r-" + acctest.RandString(10)
+	parentAuthFlowAlias := "terraform-parent-flow-" + acctest.RandString(10)
+	authFlowAlias := "terraform-flow-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationSubFlowDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationSubFlow_basic(realmName, parentAuthFlowAlias, authFlowAlias),
+				Check:  testAccCheckKeycloakAuthenticationSubFlowExists("keycloak_authentication_subflow.subflow"),
+			},
+			{
+				ResourceName:      "keycloak_authentication_subflow.subflow",
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateIdFunc: getSubFlowImportId("keycloak_authentication_subflow.subflow"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakAuthenticationSubFlow_createAfterManualDestroy(t *testing.T) {
+	var authenticationSubFlow = &keycloak.AuthenticationSubFlow{}
+
+	realmName := "terraform-" + acctest.RandString(10)
+	authParentFlowAlias := "terraform-parent-flow-" + acctest.RandString(10)
+	authFlowAlias := "terraform-flow-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationSubFlowDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationSubFlow_basic(realmName, authParentFlowAlias, authFlowAlias),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationSubFlowExists("keycloak_authentication_subflow.subflow"),
+					testAccCheckKeycloakAuthenticationSubFlowFetch("keycloak_authentication_subflow.subflow", authenticationSubFlow),
+				),
+			},
+			{
+				PreConfig: func() {
+					keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+					err := keycloakClient.DeleteAuthenticationSubFlow(authenticationSubFlow.RealmId, authenticationSubFlow.ParentFlowAlias, authenticationSubFlow.Id)
+					if err != nil {
+						t.Fatal(err)
+					}
+				},
+				Config: testKeycloakAuthenticationSubFlow_basic(realmName, authParentFlowAlias, authFlowAlias),
+				Check:  testAccCheckKeycloakAuthenticationSubFlowExists("keycloak_authentication_subflow.subflow"),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakAuthenticationSubFlow_updateAuthenticationSubFlow(t *testing.T) {
+	realmName := "terraform-r-" + acctest.RandString(10)
+	authParentFlowAlias := "terraform-parent-flow-" + acctest.RandString(10)
+	authFlowAliasBefore := "terraform-flow-before-" + acctest.RandString(10)
+	authFlowAliasAfter := "terraform-flow-after-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationSubFlowDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationSubFlow_basic(realmName, authParentFlowAlias, authFlowAliasBefore),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationSubFlowExists("keycloak_authentication_subflow.subflow"),
+					resource.TestCheckResourceAttr("keycloak_authentication_subflow.subflow", "alias", authFlowAliasBefore),
+				),
+			},
+			{
+				Config: testKeycloakAuthenticationSubFlow_basic(realmName, authParentFlowAlias, authFlowAliasAfter),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationSubFlowExists("keycloak_authentication_subflow.subflow"),
+					resource.TestCheckResourceAttr("keycloak_authentication_subflow.subflow", "alias", authFlowAliasAfter),
+				),
+			},
+		},
+	})
+}
+
+func TestAccKeycloakAuthenticationSubFlow_updateAuthenticationSubFlowRequirement(t *testing.T) {
+	realmName := "terraform-r-" + acctest.RandString(10)
+	authParentFlowAlias := "terraform-parent-flow-" + acctest.RandString(10)
+	authFlowAlias := "terraform-flow-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakAuthenticationSubFlowDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakAuthenticationSubFlow_basic(realmName, authParentFlowAlias, authFlowAlias),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationSubFlowExists("keycloak_authentication_subflow.subflow"),
+					resource.TestCheckResourceAttr("keycloak_authentication_subflow.subflow", "requirement", "DISABLED"),
+				),
+			},
+			{
+				Config: testKeycloakAuthenticationSubFlow_basicWithRequirement(realmName, authParentFlowAlias, authFlowAlias, "REQUIRED"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationSubFlowExists("keycloak_authentication_subflow.subflow"),
+					resource.TestCheckResourceAttr("keycloak_authentication_subflow.subflow", "requirement", "REQUIRED"),
+				),
+			},
+			{
+				Config: testKeycloakAuthenticationSubFlow_basicWithRequirement(realmName, authParentFlowAlias, authFlowAlias, "DISABLED"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckKeycloakAuthenticationSubFlowExists("keycloak_authentication_subflow.subflow"),
+					resource.TestCheckResourceAttr("keycloak_authentication_subflow.subflow", "requirement", "DISABLED"),
+				),
+			},
+		},
+	})
+}
+
+func testAccCheckKeycloakAuthenticationSubFlowExists(resourceName string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		_, err := getAuthenticationSubFlowFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakAuthenticationSubFlowFetch(resourceName string, authenticationSubFlow *keycloak.AuthenticationSubFlow) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		fetchedAuthenticationSubFlow, err := getAuthenticationSubFlowFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		authenticationSubFlow.Id = fetchedAuthenticationSubFlow.Id
+		authenticationSubFlow.ParentFlowAlias = fetchedAuthenticationSubFlow.ParentFlowAlias
+		authenticationSubFlow.RealmId = fetchedAuthenticationSubFlow.RealmId
+		authenticationSubFlow.Alias = fetchedAuthenticationSubFlow.Alias
+
+		return nil
+	}
+}
+
+func testAccCheckKeycloakAuthenticationSubFlowDestroy() resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		for _, rs := range s.RootModule().Resources {
+			if rs.Type != "keycloak_authentication_subflow" {
+				continue
+			}
+
+			id := rs.Primary.ID
+			realm := rs.Primary.Attributes["realm_id"]
+			parentFlowAlias := rs.Primary.Attributes["parent_flow_alias"]
+
+			keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+			authenticationSubFlow, _ := keycloakClient.GetAuthenticationSubFlow(realm, parentFlowAlias, id)
+			if authenticationSubFlow != nil {
+				return fmt.Errorf("authentication flow with id %s still exists", id)
+			}
+		}
+
+		return nil
+	}
+}
+
+func getAuthenticationSubFlowFromState(s *terraform.State, resourceName string) (*keycloak.AuthenticationSubFlow, error) {
+	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
+
+	rs, ok := s.RootModule().Resources[resourceName]
+	if !ok {
+		return nil, fmt.Errorf("resource not found: %s", resourceName)
+	}
+
+	id := rs.Primary.ID
+	realm := rs.Primary.Attributes["realm_id"]
+	parentFlowAlias := rs.Primary.Attributes["parent_flow_alias"]
+
+	authenticationSubFlow, err := keycloakClient.GetAuthenticationSubFlow(realm, parentFlowAlias, id)
+
+	if err != nil {
+		return nil, fmt.Errorf("error getting authentication subflow with id %s: %s", id, err)
+	}
+
+	return authenticationSubFlow, nil
+}
+
+func getSubFlowImportId(resourceName string) resource.ImportStateIdFunc {
+	return func(s *terraform.State) (string, error) {
+		rs, ok := s.RootModule().Resources[resourceName]
+		if !ok {
+			return "", fmt.Errorf("resource not found: %s", resourceName)
+		}
+
+		id := rs.Primary.ID
+		parentFlowAlias := rs.Primary.Attributes["parent_flow_alias"]
+		realmId := rs.Primary.Attributes["realm_id"]
+
+		return fmt.Sprintf("%s/%s/%s", realmId, parentFlowAlias, id), nil
+	}
+}
+
+func testKeycloakAuthenticationSubFlow_basic(realm, parentAlias, alias string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_authentication_flow" "flow" {
+	realm_id = "${keycloak_realm.realm.id}"
+	alias    = "%s"
+}
+
+resource "keycloak_authentication_subflow" "subflow" {
+	realm_id = "${keycloak_realm.realm.id}"
+	parent_flow_alias = "${keycloak_authentication_flow.flow.alias}"
+	alias    = "%s"
+	provider_id = "basic-flow"
+}
+	`, realm, parentAlias, alias)
+}
+
+func testKeycloakAuthenticationSubFlow_basicWithRequirement(realm, parentAlias, alias, requirement string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_authentication_flow" "flow" {
+	realm_id = "${keycloak_realm.realm.id}"
+	alias    = "%s"
+}
+
+resource "keycloak_authentication_subflow" "subflow" {
+	realm_id = "${keycloak_realm.realm.id}"
+	parent_flow_alias = "${keycloak_authentication_flow.flow.alias}"
+	alias    = "%s"
+	provider_id = "basic-flow"
+	requirement = "%s"
+}
+	`, realm, parentAlias, alias, requirement)
+}