Repository: Azure/ARO-RP
Pull request #2667 (2023-01-27 16:36:46)

Migrate RP from adal to azidentity package
### Which issue this PR addresses:

[[ARO-1917] AAD: Migrate ADAL -> MSAL](https://issues.redhat.com/browse/ARO-1917)

### What this PR does / why we need it:

Microsoft has [announced](https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/azure-ad-change-management-simplified/ba-p/2967456) the impending end-of-support for Azure Active Directory Authentication Library (ADAL):

> **Update December 15th, 2022**: ADAL end of support is now extended to June 30th, 2023. We will retire AAD Graph API any time after June 30th, 2023. Through the next six months (January 2023 – June 2023) we will continue informing customers about the upcoming end of support along with providing guidance on migration.

We need to move the entire RP code to Microsoft Authentication Library (MSAL) which for us means using the [Azure Identity Client Module for Go or "azidentity"](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity) in place of [Azure Active Directory authentication for Go or "adal"](https://pkg.go.dev/github.com/Azure/go-autorest/autorest/adal).

(Note: "azidentity", despite its slightly outdated [README](https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/azidentity/README.md), [does now use MSAL](https://github.com/Azure/azure-sdk-for-go/issues/14920).)

### Test plan for issue:

Primary method of testing was [deploying a full RP development service](https://github.com/Azure/ARO-RP/blob/master/docs/deploy-full-rp-service-in-dev.md) (with help from https://github.com/Azure/ARO-RP/pull/2621), and verifying all ARO services on the RP and Gateway VMs start up and function correctly.

Also, unit tests were updated where needed and verified to pass, along with running end-to-end tests.

### Is there any documentation that needs to be updated for this PR?

None that I'm aware of.  This is an internal RP change and should not alter its behavior.

### Additional notes:

- This does bump the Go requirement to 1.18 since [azidentity requires Go 1.18 as of v1.0](https://github.com/Azure/azure-sdk-for-go/commit/9514e5f7779d3406fa8320777da06867b79dfcc1#diff-5f1a0df6b303a95cf03dc9fb99c9bef2ddaddbcb963ffe6a95900ae263169791).

- This is a large change, so I've tried to break most of the commits up by source directory for less overwhelming review.

- azidentity [handles token refreshing automatically](https://github.com/Azure/azure-sdk-for-go/issues/10567#issuecomment-921376485), so we should no longer have to worry about this in the RP.  Therefore, this Pull Request eliminates [AuthorizationRefreshingAction](https://github.com/Azure/ARO-RP/blob/master/pkg/util/steps/refreshing.go) and [refreshable.Authorizer](https://github.com/Azure/ARO-RP/blob/master/pkg/util/refreshable/refreshable.go).

- There is one remaining import of [adal](https://pkg.go.dev/github.com/Azure/go-autorest/autorest/adal) in [pkg/portal/middleware/aad.go](https://github.com/Azure/ARO-RP/blob/master/pkg/portal/middleware/aad.go), however the RP code is just using offline utility functions from [adal](https://pkg.go.dev/github.com/Azure/go-autorest/autorest/adal) that currently have no public equivalent in [Microsoft Authentication Library (MSAL) for Go](https://github.com/AzureAD/microsoft-authentication-library-for-go) (as far as I can see).  So I left that code alone for now.

- **Followup work:** Per the [azidentity Migration Guide](https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/azidentity/MIGRATION.md#use-azidentity-credentials-with-older-packages), this PR vendors in an unofficial module [github.com/jongio/azidext](https://github.com/jongio/azidext) to serve as a temporary shim between azidentity's TokenCredential APIs and the older Azure SDK client packages under [github.com/Azure/go-autorest](https://github.com/Azure/go-autorest).<BR>
 We should endeavor to further migrate the RP code to use the newer Azure SDK client packages under [github.com/Azure/azure-sdk-for-go/sdk/resourcemanager](https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/resourcemanager), and remove the need for [github.com/jongio/azidext](https://github.com/jongio/azidext).

mbarnes: CI checks will be failing until we have Go 1.18 support.
mbarnes: https://github.com/Azure/ARO-RP/pull/2691 (Go 1.18) merged so I rebased this branch and squashed the "fixup" commits.
mbarnes: Tweaked some commits and force-pushed again to get more checks passing.
github-actions[bot]: Please rebase pull request.
mbarnes: E2E tests pass for me when run locally but are consistently failing here on Azure Pipelines.

Going to add some temporary debug commits to help figure out where the failure's coming from.  If you're reviewing code, disregard these.
mbarnes: > The last question I'll post is: this PR introduces a lot of + "/.default" to endpoints, is there ever a case in the code now where we don't use that /.default uri? Can we safely migrate that into the Environment instead, in one place?

The whole "/.default" scope thing is ugly and I wish azidentity just handled it transparently.

There is only one place where we don't use "/.default", which is the MSI authorizer in `cmd/aro/gateway.go` for DB token.

I considered adding getter methods in `Environment` for the few scopes we use, but decided against it since this is temporary ugliness.  I believe the newer Azure clients I suggested migrating to as followup work hide all this scope stuff.  I've only looked briefly at the APIs but I think it sets the appropriate scope(s) automatically for the type of Azure resource.
github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
mbarnes: Thanks for the review @cadenmarchese!
mbarnes: Looks like I lost @cadenmarchese's approval after I force-pushed today to resolve merge conflicts.
github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
mbarnes: In order to not lose approvals, I'll resolve any further merge conflicts once I'm told we're ready to merge this.

Patch:
diff --git a/cmd/aro/dbtoken.go b/cmd/aro/dbtoken.go
index 3acfc9897..48759470a 100644
--- a/cmd/aro/dbtoken.go
+++ b/cmd/aro/dbtoken.go
@@ -47,12 +47,12 @@ func dbtoken(ctx context.Context, log *logrus.Entry) error {
 		}
 	}
 
-	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerEndpoint)
+	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerScope)
 	if err != nil {
 		return err
 	}
 
-	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceIdentifiers.KeyVault)
+	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().KeyVaultScope)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/aro/gateway.go b/cmd/aro/gateway.go
index eb01881e6..34257b4bc 100644
--- a/cmd/aro/gateway.go
+++ b/cmd/aro/gateway.go
@@ -51,8 +51,13 @@ func gateway(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	resource := os.Getenv("AZURE_DBTOKEN_CLIENT_ID")
-	msiRefresherAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextGateway, resource)
+	// Access token GET request needs to be:
+	// http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=$AZURE_DBTOKEN_CLIENT_ID
+	//
+	// In this context, the "resource" parameter is passed to azidentity as a
+	// "scope" argument even though a scope normally consists of an endpoint URL.
+	scope := os.Getenv("AZURE_DBTOKEN_CLIENT_ID")
+	msiRefresherAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextGateway, scope)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/aro/monitor.go b/cmd/aro/monitor.go
index 489cc8bc8..08cf3a4de 100644
--- a/cmd/aro/monitor.go
+++ b/cmd/aro/monitor.go
@@ -61,12 +61,12 @@ func monitor(ctx context.Context, log *logrus.Entry) error {
 
 	clusterm := statsd.New(ctx, log.WithField("component", "metrics"), _env, os.Getenv("CLUSTER_MDM_ACCOUNT"), os.Getenv("CLUSTER_MDM_NAMESPACE"), os.Getenv("MDM_STATSD_SOCKET"))
 
-	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerEndpoint)
+	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerScope)
 	if err != nil {
 		return err
 	}
 
-	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceIdentifiers.KeyVault)
+	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().KeyVaultScope)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/aro/portal.go b/cmd/aro/portal.go
index 864a082e1..7acb13fd6 100644
--- a/cmd/aro/portal.go
+++ b/cmd/aro/portal.go
@@ -63,12 +63,12 @@ func portal(ctx context.Context, log *logrus.Entry, audit *logrus.Entry) error {
 		return err
 	}
 
-	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerEndpoint)
+	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerScope)
 	if err != nil {
 		return err
 	}
 
-	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceIdentifiers.KeyVault)
+	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().KeyVaultScope)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/aro/rp.go b/cmd/aro/rp.go
index 80eafaa46..95f4d3af0 100644
--- a/cmd/aro/rp.go
+++ b/cmd/aro/rp.go
@@ -87,7 +87,7 @@ func rp(ctx context.Context, log, audit *logrus.Entry) error {
 		RequestLatency: k8s.NewLatency(m),
 	})
 
-	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerEndpoint)
+	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerScope)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/aro/update_ocp_versions.go b/cmd/aro/update_ocp_versions.go
index 6208e1e05..32a22d111 100644
--- a/cmd/aro/update_ocp_versions.go
+++ b/cmd/aro/update_ocp_versions.go
@@ -69,12 +69,12 @@ func getVersionsDatabase(ctx context.Context, log *logrus.Entry) (database.OpenS
 		}
 	}
 
-	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerEndpoint)
+	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerScope)
 	if err != nil {
 		return nil, fmt.Errorf("MSI Authorizer failed with: %s", err.Error())
 	}
 
-	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceIdentifiers.KeyVault)
+	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().KeyVaultScope)
 	if err != nil {
 		return nil, fmt.Errorf("MSI KeyVault Authorizer failed with: %s", err.Error())
 	}
diff --git a/docs/deploy-development-rp.md b/docs/deploy-development-rp.md
index 961b46317..0c1fdc95b 100644
--- a/docs/deploy-development-rp.md
+++ b/docs/deploy-development-rp.md
@@ -147,9 +147,9 @@ If you are already familiar with running the ARO RP locally, you can speed up th
 
 ## Connect ARO-RP with a Hive development cluster
 The env variables names defined in pkg/util/liveconfig/manager.go control the communication of the ARO-RP with Hive.
-- If you want to use ARO-RP + Hive, set *HIVE_KUBE_CONFIG_PATH* to the path of the kubeconfig of the AKS Dev cluster. [Info](https://github.com/Azure/ARO-RP/blob/master/docs/deploy-development-rp.md#debugging-openshift-cluster) about creating that kubeconfig (Step *Get an admin kubeconfig:*).
-- If you want to create clusters using the local ARO-RP + Hive instead of doing the standard cluster creation process (which doesn't use Hive), set *ARO_INSTALL_VIA_HIVE* to *true*.
-- If you want to enable the Hive adoption feature (which is performed during adminUpdate()), set *ARO_ADOPT_BY_HIVE* to *true*.
+- If you want to use ARO-RP + Hive, set `HIVE_KUBE_CONFIG_PATH` to the path of the kubeconfig of the AKS Dev cluster. [Info](https://github.com/Azure/ARO-RP/blob/master/docs/deploy-development-rp.md#debugging-aks-cluster) about creating that kubeconfig (Step *Access the cluster via API*).
+- If you want to create clusters using the local ARO-RP + Hive instead of doing the standard cluster creation process (which doesn't use Hive), set `ARO_INSTALL_VIA_HIVE` to *true*.
+- If you want to enable the Hive adoption feature (which is performed during adminUpdate()), set `ARO_ADOPT_BY_HIVE` to *true*.
 
 After setting the above environment variables (using *export* direclty in the terminal or including them in the *env* file), connect to the [VPN](https://github.com/Azure/ARO-RP/blob/master/docs/deploy-development-rp.md#debugging-aks-cluster) (*Connect to the VPN* section).
 
@@ -195,10 +195,10 @@ After that, when you [create](https://github.com/Azure/ARO-RP/blob/master/docs/d
   curl -X POST -k "https://localhost:8443/admin/subscriptions/$AZURE_SUBSCRIPTION_ID/resourceGroups/$RESOURCEGROUP/providers/Microsoft.RedHatOpenShift/openShiftClusters/$CLUSTER/stopvm?vmName=$VMNAME" --header "Content-Type: application/json" -d "{}"
   ```
 
-* Stop and deallocate a VM in a dev cluster
+* Stop and [deallocate a VM](https://learn.microsoft.com/en-us/azure/virtual-machines/states-billing) in a dev cluster
   ```bash
   VMNAME="aro-cluster-qplnw-master-0"
-  curl -X POST -k "https://localhost:8443/admin/subscriptions/$AZURE_SUBSCRIPTION_ID/resourceGroups/$RESOURCEGROUP/providers/Microsoft.RedHatOpenShift/openShiftClusters/$CLUSTER/stopvm?vmName=$VMNAME" --header "Content-Type: application/json" -d "{}"
+  curl -X POST -k "https://localhost:8443/admin/subscriptions/$AZURE_SUBSCRIPTION_ID/resourceGroups/$RESOURCEGROUP/providers/Microsoft.RedHatOpenShift/openShiftClusters/$CLUSTER/stopvm?vmName=$VMNAME&deallocateVM=True" --header "Content-Type: application/json" -d "{}"
   ```
 
 * Start a VM in a dev cluster
diff --git a/go.mod b/go.mod
index 68c886913..c8dc3e41c 100644
--- a/go.mod
+++ b/go.mod
@@ -6,9 +6,8 @@ require (
 	github.com/Azure/azure-sdk-for-go v63.1.0+incompatible
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2
-	github.com/Azure/go-autorest/autorest v0.11.25
-	github.com/Azure/go-autorest/autorest/adal v0.9.18
-	github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
+	github.com/Azure/go-autorest/autorest v0.11.27
+	github.com/Azure/go-autorest/autorest/adal v0.9.20
 	github.com/Azure/go-autorest/autorest/date v0.3.0
 	github.com/Azure/go-autorest/autorest/to v0.4.0
 	github.com/Azure/go-autorest/autorest/validation v0.3.1
@@ -29,6 +28,7 @@ require (
 	github.com/go-logr/logr v1.2.3
 	github.com/go-test/deep v1.0.8
 	github.com/gofrs/uuid v4.2.0+incompatible
+	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/golang/mock v1.6.0
 	github.com/google/go-cmp v0.5.8
 	github.com/googleapis/gnostic v0.6.8
@@ -37,6 +37,7 @@ require (
 	github.com/gorilla/securecookie v1.1.1
 	github.com/gorilla/sessions v1.2.1
 	github.com/jewzaam/go-cosmosdb v0.0.0-20220315232836-282b67c5b234
+	github.com/jongio/azidext/go/azidext v0.4.0
 	github.com/onsi/ginkgo/v2 v2.3.1
 	github.com/onsi/gomega v1.22.0
 	github.com/openshift/api v3.9.1-0.20191111211345-a27ff30ebf09+incompatible
@@ -84,6 +85,7 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
+	github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
 	github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
 	github.com/Azure/go-autorest/logger v0.2.1 // indirect
 	github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 // indirect
@@ -119,7 +121,7 @@ require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
 	github.com/docker/distribution v2.8.1+incompatible // indirect
-	github.com/docker/docker v20.10.14+incompatible // indirect
+	github.com/docker/docker v20.10.24+incompatible // indirect
 	github.com/docker/docker-credential-helpers v0.6.4 // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-metrics v0.0.1 // indirect
@@ -141,7 +143,6 @@ require (
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/gobuffalo/flect v0.2.5 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
-	github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/btree v1.0.1 // indirect
diff --git a/go.sum b/go.sum
index 1e0988b65..655eb47a2 100644
--- a/go.sum
+++ b/go.sum
@@ -88,13 +88,14 @@ github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+B
 github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
 github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
 github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
-github.com/Azure/go-autorest/autorest v0.11.25 h1:yp+V8DGur2aIUE87ebP8twPLz6k68jtJTlg61mEoByA=
-github.com/Azure/go-autorest/autorest v0.11.25/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U=
+github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A=
+github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U=
 github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
 github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
 github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=
 github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg=
+github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
 github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
 github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
 github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4=
@@ -487,8 +488,9 @@ github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompati
 github.com/docker/docker v17.12.1-ce+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/docker v20.10.14+incompatible h1:+T9/PRYWNDo5SZl5qS1r9Mo/0Q8AwxKKPtu9S1yxM0w=
 github.com/docker/docker v20.10.14+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE=
+github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
 github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o=
 github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
@@ -954,6 +956,8 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
+github.com/jongio/azidext/go/azidext v0.4.0 h1:TOYyVFMeWGgXNhURSgrEtUCu7JAAKgsy+5C4+AEfYlw=
+github.com/jongio/azidext/go/azidext v0.4.0/go.mod h1:VrlpGde5B+pPbTUxnThE5UIQQkcebdr3jrC2MmlMVSI=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
diff --git a/hack/aead/aead.go b/hack/aead/aead.go
index e0af7e5c5..66b1d97b9 100644
--- a/hack/aead/aead.go
+++ b/hack/aead/aead.go
@@ -51,7 +51,7 @@ func run(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceIdentifiers.KeyVault)
+	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().KeyVaultScope)
 	if err != nil {
 		return err
 	}
diff --git a/hack/db/db.go b/hack/db/db.go
index 6eef94a71..85823bbc7 100644
--- a/hack/db/db.go
+++ b/hack/db/db.go
@@ -10,7 +10,8 @@ import (
 	"os"
 	"strings"
 
-	"github.com/Azure/go-autorest/autorest/azure/auth"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/database"
@@ -31,12 +32,15 @@ func run(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	authorizer, err := auth.NewAuthorizerFromCLIWithResource(_env.Environment().ResourceManagerEndpoint)
+	tokenCredential, err := azidentity.NewAzureCLICredential(nil)
 	if err != nil {
 		return err
 	}
 
-	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceIdentifiers.KeyVault)
+	scopes := []string{_env.Environment().ResourceManagerScope}
+	authorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
+
+	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().KeyVaultScope)
 	if err != nil {
 		return err
 	}
diff --git a/hack/portalauth/portalauth.go b/hack/portalauth/portalauth.go
index b74dbf48a..d32b2b327 100644
--- a/hack/portalauth/portalauth.go
+++ b/hack/portalauth/portalauth.go
@@ -38,7 +38,7 @@ func run(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceIdentifiers.KeyVault)
+	msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().KeyVaultScope)
 	if err != nil {
 		return err
 	}
diff --git a/hack/rhcos-sas/rhcos-sas.go b/hack/rhcos-sas/rhcos-sas.go
index 03a9b3feb..5c5e18ba4 100644
--- a/hack/rhcos-sas/rhcos-sas.go
+++ b/hack/rhcos-sas/rhcos-sas.go
@@ -10,8 +10,9 @@ import (
 	"strings"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	azstorage "github.com/Azure/azure-sdk-for-go/storage"
-	"github.com/Azure/go-autorest/autorest/azure/auth"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/openshift/installer/pkg/types"
 	"github.com/sirupsen/logrus"
 
@@ -32,11 +33,14 @@ func run(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	authorizer, err := auth.NewAuthorizerFromCLIWithResource(_env.Environment().ResourceManagerEndpoint)
+	tokenCredential, err := azidentity.NewAzureCLICredential(nil)
 	if err != nil {
 		return err
 	}
 
+	scopes := []string{_env.Environment().ResourceManagerScope}
+	authorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
+
 	accounts := storage.NewAccountsClient(_env.Environment(), subscriptionID, authorizer)
 
 	keys, err := accounts.ListKeys(ctx, resourceGroup, accountName, "")
diff --git a/pkg/api/validate/dynamic/dynamic.go b/pkg/api/validate/dynamic/dynamic.go
index bc3930260..ccb3e331c 100644
--- a/pkg/api/validate/dynamic/dynamic.go
+++ b/pkg/api/validate/dynamic/dynamic.go
@@ -11,6 +11,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network"
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/azure"
@@ -20,14 +21,11 @@ import (
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/env"
-	"github.com/Azure/ARO-RP/pkg/util/aad"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/authorization"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/compute"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network"
 	"github.com/Azure/ARO-RP/pkg/util/permissions"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
-	"github.com/Azure/ARO-RP/pkg/util/steps"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
 )
 
@@ -40,7 +38,7 @@ type Subnet struct {
 }
 
 type ServicePrincipalValidator interface {
-	ValidateServicePrincipal(ctx context.Context, clientID, clientSecret, tenantID string) error
+	ValidateServicePrincipal(ctx context.Context, tokenCredential azcore.TokenCredential) error
 }
 
 // Dynamic validate in the operator context.
@@ -65,7 +63,6 @@ type dynamic struct {
 	resourceSkusClient compute.ResourceSkusClient
 	spComputeUsage     compute.UsageClient
 	spNetworkUsage     network.UsageClient
-	tokenClient        aad.TokenClient
 }
 
 type AuthorizerType string
@@ -75,7 +72,7 @@ const (
 	AuthorizerClusterServicePrincipal AuthorizerType = "cluster"
 )
 
-func NewValidator(log *logrus.Entry, env env.Interface, azEnv *azureclient.AROEnvironment, subscriptionID string, authorizer refreshable.Authorizer, authorizerType AuthorizerType, tokenClient aad.TokenClient) (Dynamic, error) {
+func NewValidator(log *logrus.Entry, env env.Interface, azEnv *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer, authorizerType AuthorizerType) (Dynamic, error) {
 	return &dynamic{
 		log:            log,
 		authorizerType: authorizerType,
@@ -88,16 +85,14 @@ func NewValidator(log *logrus.Entry, env env.Interface, azEnv *azureclient.AROEn
 		virtualNetworks:    newVirtualNetworksCache(network.NewVirtualNetworksClient(azEnv, subscriptionID, authorizer)),
 		diskEncryptionSets: compute.NewDiskEncryptionSetsClient(azEnv, subscriptionID, authorizer),
 		resourceSkusClient: compute.NewResourceSkusClient(azEnv, subscriptionID, authorizer),
-		tokenClient:        tokenClient,
 	}, nil
 }
 
-func NewServicePrincipalValidator(log *logrus.Entry, azEnv *azureclient.AROEnvironment, authorizerType AuthorizerType, tokenClient aad.TokenClient) (ServicePrincipalValidator, error) {
+func NewServicePrincipalValidator(log *logrus.Entry, azEnv *azureclient.AROEnvironment, authorizerType AuthorizerType) (ServicePrincipalValidator, error) {
 	return &dynamic{
 		log:            log,
 		authorizerType: authorizerType,
 		azEnv:          azEnv,
-		tokenClient:    tokenClient,
 	}, nil
 }
 
@@ -293,11 +288,6 @@ func (dv *dynamic) validateActions(ctx context.Context, r *azure.Resource, actio
 	return wait.PollImmediateUntil(20*time.Second, func() (bool, error) {
 		dv.log.Debug("retry validateActions")
 		perms, err := dv.permissions.ListForResource(ctx, r.ResourceGroup, r.Provider, "", r.ResourceType, r.ResourceName)
-
-		if detailedErr, ok := err.(autorest.DetailedError); ok &&
-			detailedErr.StatusCode == http.StatusForbidden {
-			return false, steps.ErrWantRefresh
-		}
 		if err != nil {
 			return false, err
 		}
diff --git a/pkg/api/validate/dynamic/generate.go b/pkg/api/validate/dynamic/generate.go
index 59e81172c..88c2cca4b 100644
--- a/pkg/api/validate/dynamic/generate.go
+++ b/pkg/api/validate/dynamic/generate.go
@@ -3,6 +3,10 @@ package dynamic
 // Copyright (c) Microsoft Corporation.
 // Licensed under the Apache License 2.0.
 
+// XXX Using mockgen in source mode here to prevent mockgen from following
+//     type alias azcore.TokenCredential to an internal azcore subpackage.
+//     See https://github.com/golang/mock/issues/244
+
 //go:generate rm -rf ../../../../pkg/util/mocks/$GOPACKAGE
-//go:generate go run ../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../pkg/util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/api/validate/$GOPACKAGE Dynamic
+//go:generate go run ../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../pkg/util/mocks/$GOPACKAGE/$GOPACKAGE.go -source=dynamic.go
 //go:generate go run ../../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../../../pkg/util/mocks/$GOPACKAGE/$GOPACKAGE.go
diff --git a/pkg/api/validate/dynamic/serviceprincipal.go b/pkg/api/validate/dynamic/serviceprincipal.go
index ecdfa4146..9e4265e12 100644
--- a/pkg/api/validate/dynamic/serviceprincipal.go
+++ b/pkg/api/validate/dynamic/serviceprincipal.go
@@ -7,23 +7,28 @@ import (
 	"context"
 	"net/http"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
 	"github.com/form3tech-oss/jwt-go"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/util/azureclaim"
 )
 
-func (dv *dynamic) ValidateServicePrincipal(ctx context.Context, clientID, clientSecret, tenantID string) error {
+func (dv *dynamic) ValidateServicePrincipal(ctx context.Context, tokenCredential azcore.TokenCredential) error {
 	dv.log.Print("ValidateServicePrincipal")
 
-	token, err := dv.tokenClient.GetToken(ctx, dv.log, clientID, clientSecret, tenantID, dv.azEnv.ActiveDirectoryEndpoint, dv.azEnv.GraphEndpoint)
+	tokenRequestOptions := policy.TokenRequestOptions{
+		Scopes: []string{dv.azEnv.ActiveDirectoryGraphScope},
+	}
+	token, err := tokenCredential.GetToken(ctx, tokenRequestOptions)
 	if err != nil {
 		return err
 	}
 
 	p := &jwt.Parser{}
 	c := &azureclaim.AzureClaim{}
-	_, _, err = p.ParseUnverified(token.OAuthToken(), c)
+	_, _, err = p.ParseUnverified(token.Token, c)
 	if err != nil {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidServicePrincipalCredentials, "properties.servicePrincipalProfile", err.Error())
 	}
diff --git a/pkg/api/validate/openshiftcluster_validatedynamic.go b/pkg/api/validate/openshiftcluster_validatedynamic.go
index 5e8cb80f1..6f7214160 100644
--- a/pkg/api/validate/openshiftcluster_validatedynamic.go
+++ b/pkg/api/validate/openshiftcluster_validatedynamic.go
@@ -6,14 +6,20 @@ package validate
 import (
 	"context"
 	"fmt"
-
+	"net/http"
+	"time"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	"github.com/Azure/go-autorest/autorest"
+	"github.com/form3tech-oss/jwt-go"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/sirupsen/logrus"
+	"k8s.io/apimachinery/pkg/util/wait"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/api/validate/dynamic"
 	"github.com/Azure/ARO-RP/pkg/env"
-	"github.com/Azure/ARO-RP/pkg/util/aad"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 )
 
 // OpenShiftClusterDynamicValidator is the dynamic validator interface
@@ -22,7 +28,7 @@ type OpenShiftClusterDynamicValidator interface {
 }
 
 // NewOpenShiftClusterDynamicValidator creates a new OpenShiftClusterDynamicValidator
-func NewOpenShiftClusterDynamicValidator(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster, subscriptionDoc *api.SubscriptionDocument, fpAuthorizer refreshable.Authorizer) OpenShiftClusterDynamicValidator {
+func NewOpenShiftClusterDynamicValidator(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster, subscriptionDoc *api.SubscriptionDocument, fpAuthorizer autorest.Authorizer) OpenShiftClusterDynamicValidator {
 	return &openShiftClusterDynamicValidator{
 		log: log,
 		env: env,
@@ -39,7 +45,65 @@ type openShiftClusterDynamicValidator struct {
 
 	oc              *api.OpenShiftCluster
 	subscriptionDoc *api.SubscriptionDocument
-	fpAuthorizer    refreshable.Authorizer
+	fpAuthorizer    autorest.Authorizer
+}
+
+func ensureAccessTokenClaims(ctx context.Context, tokenCredential *azidentity.ClientSecretCredential, scopes []string) error {
+	var err error
+
+	timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
+	defer cancel()
+
+	// NOTE: Do not override err with the error returned by
+	// wait.PollImmediateUntil. Doing this will not propagate the
+	// latest error to the user in case the wait exceeds the timeout.
+	_ = wait.PollImmediateUntil(10*time.Second, func() (bool, error) {
+		options := policy.TokenRequestOptions{Scopes: scopes}
+		token, err := tokenCredential.GetToken(ctx, options)
+		if err != nil {
+			return false, err
+		}
+
+		var claims jwt.MapClaims
+		parser := &jwt.Parser{UseJSONNumber: true}
+		_, _, err = parser.ParseUnverified(token.Token, &claims)
+		if err != nil {
+			err = api.NewCloudError(
+				http.StatusBadRequest,
+				api.CloudErrorCodeInvalidServicePrincipalToken,
+				"properties.servicePrincipalProfile",
+				"The provided service principal generated an invalid token.")
+			return false, err
+		}
+
+		// XXX Unclear if this check is still required, as it was originally
+		//     implemented for ADAL authentication with the comment:
+		//
+		//     Lack of an altsecid, puid or oid claim in the token. Continuing would
+		//     subsequently cause the ARM error `Code="InvalidAuthenticationToken"
+		//     Message="The received access token is not valid: at least one of the
+		//     claims 'puid' or 'altsecid' or 'oid' should be present. If you are
+		//     accessing as an application please make sure service principal is
+		//     properly created in the tenant."`.  I think this can be returned when
+		//     the service principal associated with the application hasn't yet
+		//     caught up with the application itself.
+		//
+		//     (source: commit id 52dff30f31bad63cc4e46bbf701437756a6da83a)
+		for _, claim := range []string{"altsecid", "oid", "puid"} {
+			if _, found := claims[claim]; found {
+				return true, nil
+			}
+		}
+
+		err = api.NewCloudError(
+			http.StatusBadRequest,
+			api.CloudErrorCodeInvalidServicePrincipalClaims,
+			"properties.servicePrincipleProfile",
+			"The provided service principal does not give an access token with at least one of the claims 'altsecid', 'oid' or 'puid'.")
+		return false, err
+	}, timeoutCtx.Done())
+
+	return err
 }
 
 // Dynamic validates an OpenShift cluster
@@ -57,7 +121,7 @@ func (dv *openShiftClusterDynamicValidator) Dynamic(ctx context.Context) error {
 	}
 
 	// FP validation
-	fpDynamic, err := dynamic.NewValidator(dv.log, dv.env, dv.env.Environment(), dv.subscriptionDoc.ID, dv.fpAuthorizer, dynamic.AuthorizerFirstParty, aad.NewTokenClient())
+	fpDynamic, err := dynamic.NewValidator(dv.log, dv.env, dv.env.Environment(), dv.subscriptionDoc.ID, dv.fpAuthorizer, dynamic.AuthorizerFirstParty)
 	if err != nil {
 		return err
 	}
@@ -73,21 +137,28 @@ func (dv *openShiftClusterDynamicValidator) Dynamic(ctx context.Context) error {
 	}
 
 	spp := dv.oc.Properties.ServicePrincipalProfile
-	tokenClient := aad.NewTokenClient()
-	token, err := tokenClient.GetToken(ctx, dv.log, spp.ClientID, string(spp.ClientSecret), dv.subscriptionDoc.Subscription.Properties.TenantID, dv.env.Environment().ActiveDirectoryEndpoint, dv.env.Environment().ResourceManagerEndpoint)
+	tenantID := dv.subscriptionDoc.Subscription.Properties.TenantID
+	options := dv.env.Environment().ClientSecretCredentialOptions()
+	tokenCredential, err := azidentity.NewClientSecretCredential(
+		tenantID, spp.ClientID, string(spp.ClientSecret), options)
 	if err != nil {
 		return err
 	}
 
-	spAuthorizer := refreshable.NewAuthorizer(token)
+	scopes := []string{dv.env.Environment().ResourceManagerScope}
+	err = ensureAccessTokenClaims(ctx, tokenCredential, scopes)
+	if err != nil {
+		return err
+	}
+	spAuthorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
 
-	spDynamic, err := dynamic.NewValidator(dv.log, dv.env, dv.env.Environment(), dv.subscriptionDoc.ID, spAuthorizer, dynamic.AuthorizerClusterServicePrincipal, aad.NewTokenClient())
+	spDynamic, err := dynamic.NewValidator(dv.log, dv.env, dv.env.Environment(), dv.subscriptionDoc.ID, spAuthorizer, dynamic.AuthorizerClusterServicePrincipal)
 	if err != nil {
 		return err
 	}
 
 	// SP validation
-	err = spDynamic.ValidateServicePrincipal(ctx, spp.ClientID, string(spp.ClientSecret), dv.subscriptionDoc.Subscription.Properties.TenantID)
+	err = spDynamic.ValidateServicePrincipal(ctx, tokenCredential)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go
index 36b1ee8e2..77a05a1f2 100644
--- a/pkg/cluster/cluster.go
+++ b/pkg/cluster/cluster.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"time"
 
+	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/azure"
 	configclient "github.com/openshift/client-go/config/clientset/versioned"
 	imageregistryclient "github.com/openshift/client-go/imageregistry/clientset/versioned"
@@ -36,7 +37,6 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/billing"
 	"github.com/Azure/ARO-RP/pkg/util/dns"
 	"github.com/Azure/ARO-RP/pkg/util/encryption"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 	"github.com/Azure/ARO-RP/pkg/util/storage"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
 )
@@ -59,8 +59,8 @@ type manager struct {
 	billing           billing.Manager
 	doc               *api.OpenShiftClusterDocument
 	subscriptionDoc   *api.SubscriptionDocument
-	fpAuthorizer      refreshable.Authorizer
-	localFpAuthorizer refreshable.Authorizer
+	fpAuthorizer      autorest.Authorizer
+	localFpAuthorizer autorest.Authorizer
 	metricsEmitter    metrics.Emitter
 
 	spApplications        graphrbac.ApplicationsClient
@@ -117,17 +117,17 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database
 		return nil, err
 	}
 
-	localFPAuthorizer, err := _env.FPAuthorizer(_env.TenantID(), _env.Environment().ResourceManagerEndpoint)
+	localFPAuthorizer, err := _env.FPAuthorizer(_env.TenantID(), _env.Environment().ResourceManagerScope)
 	if err != nil {
 		return nil, err
 	}
 
-	fpAuthorizer, err := _env.FPAuthorizer(subscriptionDoc.Subscription.Properties.TenantID, _env.Environment().ResourceManagerEndpoint)
+	fpAuthorizer, err := _env.FPAuthorizer(subscriptionDoc.Subscription.Properties.TenantID, _env.Environment().ResourceManagerScope)
 	if err != nil {
 		return nil, err
 	}
 
-	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerEndpoint)
+	msiAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().ResourceManagerScope)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/cluster/install.go b/pkg/cluster/install.go
index 26dc5bdb6..a0376abce 100644
--- a/pkg/cluster/install.go
+++ b/pkg/cluster/install.go
@@ -52,9 +52,9 @@ func (m *manager) adminUpdate() []steps.Step {
 
 	if isEverything {
 		toRun = append(toRun,
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.ensureResourceGroup)), // re-create RP RBAC if needed after tenant migration
+			steps.Action(m.ensureResourceGroup), // re-create RP RBAC if needed after tenant migration
 			steps.Action(m.createOrUpdateDenyAssignment),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.enableServiceEndpoints)),
+			steps.Action(m.enableServiceEndpoints),
 			steps.Action(m.populateRegistryStorageAccountName), // must go before migrateStorageAccounts
 			steps.Action(m.migrateStorageAccounts),
 			steps.Action(m.fixSSH),
@@ -161,7 +161,7 @@ func (m *manager) clusterWasCreatedByHive() bool {
 
 func (m *manager) Update(ctx context.Context) error {
 	s := []steps.Step{
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.validateResources)),
+		steps.Action(m.validateResources),
 		steps.Action(m.initializeKubernetesClients), // All init steps are first
 		steps.Action(m.initializeOperatorDeployer),  // depends on kube clients
 		steps.Action(m.initializeClusterSPClients),
@@ -229,7 +229,7 @@ func setFieldCreatedByHive(createdByHive bool) database.OpenShiftClusterDocument
 
 func (m *manager) bootstrap() []steps.Step {
 	s := []steps.Step{
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.validateResources)),
+		steps.Action(m.validateResources),
 		steps.Action(m.ensureACRToken),
 		steps.Action(m.ensureInfraID),
 		steps.Action(m.ensureSSHKey),
@@ -240,14 +240,14 @@ func (m *manager) bootstrap() []steps.Step {
 		steps.Action(m.createDNS),
 		steps.Action(m.initializeClusterSPClients), // must run before clusterSPObjectID
 		steps.Action(m.clusterSPObjectID),
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.ensureResourceGroup)),
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.enableServiceEndpoints)),
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.setMasterSubnetPolicies)),
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.deployBaseResourceTemplate)),
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.attachNSGs)),
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.updateAPIIPEarly)),
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.createOrUpdateRouterIPEarly)),
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.ensureGatewayCreate)),
+		steps.Action(m.ensureResourceGroup),
+		steps.Action(m.enableServiceEndpoints),
+		steps.Action(m.setMasterSubnetPolicies),
+		steps.Action(m.deployBaseResourceTemplate),
+		steps.Action(m.attachNSGs),
+		steps.Action(m.updateAPIIPEarly),
+		steps.Action(m.createOrUpdateRouterIPEarly),
+		steps.Action(m.ensureGatewayCreate),
 		steps.Action(m.createAPIServerPrivateEndpoint),
 		steps.Action(m.createCertificates),
 	}
@@ -265,12 +265,12 @@ func (m *manager) bootstrap() []steps.Step {
 			// all of bootstrapping being complete
 			steps.Condition(m.hiveClusterInstallationComplete, 60*time.Minute, true),
 			steps.Condition(m.hiveClusterDeploymentReady, 5*time.Minute, true),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.generateKubeconfigs)),
+			steps.Action(m.generateKubeconfigs),
 		)
 	} else {
 		s = append(s,
 			steps.Action(m.runIntegratedInstaller),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.generateKubeconfigs)),
+			steps.Action(m.generateKubeconfigs),
 		)
 
 		if m.adoptViaHive {
diff --git a/pkg/cluster/serviceprincipal.go b/pkg/cluster/serviceprincipal.go
index 0833f4fcc..0b6297861 100644
--- a/pkg/cluster/serviceprincipal.go
+++ b/pkg/cluster/serviceprincipal.go
@@ -8,25 +8,28 @@ import (
 	"strings"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	azgraphrbac "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
-	"github.com/Azure/go-autorest/autorest"
+	"github.com/jongio/azidext/go/azidext"
 	"k8s.io/apimachinery/pkg/util/wait"
 
 	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/util/aad"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/graphrbac"
 )
 
 // initializeClusterSPClients initialized clients, based on cluster service principal
 func (m *manager) initializeClusterSPClients(ctx context.Context) error {
 	spp := m.doc.OpenShiftCluster.Properties.ServicePrincipalProfile
-	tokenClient := aad.NewTokenClient()
-	token, err := tokenClient.GetToken(ctx, m.log, spp.ClientID, string(spp.ClientSecret), m.subscriptionDoc.Subscription.Properties.TenantID, m.env.Environment().ActiveDirectoryEndpoint, m.env.Environment().GraphEndpoint)
+	options := m.env.Environment().ClientSecretCredentialOptions()
+	credential, err := azidentity.NewClientSecretCredential(
+		m.subscriptionDoc.Subscription.Properties.TenantID,
+		spp.ClientID, string(spp.ClientSecret), options)
 	if err != nil {
 		return err
 	}
 
-	spGraphAuthorizer := autorest.NewBearerAuthorizer(token)
+	scopes := []string{m.env.Environment().ActiveDirectoryGraphScope}
+	spGraphAuthorizer := azidext.NewTokenCredentialAdapter(credential, scopes)
 
 	m.spApplications = graphrbac.NewApplicationsClient(m.env.Environment(), m.subscriptionDoc.Subscription.Properties.TenantID, spGraphAuthorizer)
 	return nil
diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go
index 0dadf0069..d751c8ab4 100644
--- a/pkg/deploy/deploy.go
+++ b/pkg/deploy/deploy.go
@@ -8,9 +8,10 @@ import (
 	"reflect"
 	"strings"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features"
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/Azure/go-autorest/autorest/azure/auth"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/deploy/vmsscleaner"
@@ -73,15 +74,17 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Core, config *RPConfig
 		return nil, err
 	}
 
-	authorizer, err := auth.NewAuthorizerFromEnvironment()
+	options := _env.Environment().EnvironmentCredentialOptions()
+	tokenCredential, err := azidentity.NewEnvironmentCredential(options)
 	if err != nil {
 		return nil, err
 	}
 
-	kvAuthorizer, err := auth.NewAuthorizerFromEnvironmentWithResource(_env.Environment().ResourceIdentifiers.KeyVault)
-	if err != nil {
-		return nil, err
-	}
+	scopes := []string{_env.Environment().ResourceManagerScope}
+	authorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
+
+	scopes = []string{_env.Environment().KeyVaultScope}
+	kvAuthorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
 
 	vmssClient := compute.NewVirtualMachineScaleSetsClient(_env.Environment(), config.SubscriptionID, authorizer)
 
diff --git a/pkg/env/armhelper.go b/pkg/env/armhelper.go
index 2ff006206..4bd5cb628 100644
--- a/pkg/env/armhelper.go
+++ b/pkg/env/armhelper.go
@@ -7,18 +7,18 @@ import (
 	"context"
 	"os"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization"
 	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/adal"
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/Azure/go-autorest/autorest/azure/auth"
 	"github.com/Azure/go-autorest/autorest/to"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/graphrbac"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/authorization"
 	"github.com/Azure/ARO-RP/pkg/util/rbac"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
@@ -47,13 +47,13 @@ import (
 // In INT/PROD I believe it is invisible.
 
 type ARMHelper interface {
-	EnsureARMResourceGroupRoleAssignment(context.Context, refreshable.Authorizer, string) error
+	EnsureARMResourceGroupRoleAssignment(context.Context, autorest.Authorizer, string) error
 }
 
 // noopARMHelper is used in INT and PROD.  It does nothing.
 type noopARMHelper struct{}
 
-func (*noopARMHelper) EnsureARMResourceGroupRoleAssignment(context.Context, refreshable.Authorizer, string) error {
+func (*noopARMHelper) EnsureARMResourceGroupRoleAssignment(context.Context, autorest.Authorizer, string) error {
 	return nil
 }
 
@@ -72,21 +72,18 @@ func newARMHelper(ctx context.Context, log *logrus.Entry, env Interface) (ARMHel
 		return &noopARMHelper{}, nil
 	}
 
-	var armAuthorizer autorest.Authorizer
+	var tokenCredential azcore.TokenCredential
+	var err error
+
 	if os.Getenv("AZURE_ARM_CLIENT_SECRET") != "" {
 		// TODO: migrate away from AZURE_ARM_CLIENT_SECRET and remove this code
 		// path
 
-		ccc := auth.ClientCredentialsConfig{
-			ClientID:     os.Getenv("AZURE_ARM_CLIENT_ID"),
-			ClientSecret: os.Getenv("AZURE_ARM_CLIENT_SECRET"),
-			TenantID:     env.TenantID(),
-			Resource:     env.Environment().ResourceManagerEndpoint,
-			AADEndpoint:  env.Environment().ActiveDirectoryEndpoint,
-		}
-
-		var err error
-		armAuthorizer, err = ccc.Authorizer()
+		tokenCredential, err = azidentity.NewClientSecretCredential(
+			env.TenantID(),
+			os.Getenv("AZURE_ARM_CLIENT_ID"),
+			os.Getenv("AZURE_ARM_CLIENT_SECRET"),
+			env.Environment().ClientSecretCredentialOptions())
 		if err != nil {
 			return nil, err
 		}
@@ -96,20 +93,18 @@ func newARMHelper(ctx context.Context, log *logrus.Entry, env Interface) (ARMHel
 			return nil, err
 		}
 
-		oauthConfig, err := adal.NewOAuthConfig(env.Environment().ActiveDirectoryEndpoint, env.TenantID())
+		options := env.Environment().ClientCertificateCredentialOptions()
+		tokenCredential, err = azidentity.NewClientCertificateCredential(
+			env.TenantID(), os.Getenv("AZURE_ARM_CLIENT_ID"), certs, key, options)
 		if err != nil {
 			return nil, err
 		}
-
-		sp, err := adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, os.Getenv("AZURE_ARM_CLIENT_ID"), certs[0], key, env.Environment().ResourceManagerEndpoint)
-		if err != nil {
-			return nil, err
-		}
-
-		armAuthorizer = autorest.NewBearerAuthorizer(sp)
 	}
 
-	fpGraphAuthorizer, err := env.FPAuthorizer(env.TenantID(), env.Environment().GraphEndpoint)
+	scopes := []string{env.Environment().ResourceManagerScope}
+	armAuthorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
+
+	fpGraphAuthorizer, err := env.FPAuthorizer(env.TenantID(), env.Environment().ActiveDirectoryGraphScope)
 	if err != nil {
 		return nil, err
 	}
@@ -123,7 +118,7 @@ func newARMHelper(ctx context.Context, log *logrus.Entry, env Interface) (ARMHel
 	}, nil
 }
 
-func (ah *armHelper) EnsureARMResourceGroupRoleAssignment(ctx context.Context, fpAuthorizer refreshable.Authorizer, resourceGroup string) error {
+func (ah *armHelper) EnsureARMResourceGroupRoleAssignment(ctx context.Context, fpAuthorizer autorest.Authorizer, resourceGroup string) error {
 	ah.log.Print("ensuring resource group role assignment")
 
 	res, err := ah.applications.GetServicePrincipalsIDByAppID(ctx, ah.env.FPClientID())
diff --git a/pkg/env/core.go b/pkg/env/core.go
index fe1563d09..c890dd498 100644
--- a/pkg/env/core.go
+++ b/pkg/env/core.go
@@ -19,7 +19,7 @@ import (
 // etc.)
 type Core interface {
 	IsLocalDevelopmentMode() bool
-	NewMSIAuthorizer(MSIContext, string) (autorest.Authorizer, error)
+	NewMSIAuthorizer(MSIContext, ...string) (autorest.Authorizer, error)
 	NewLiveConfigManager(context.Context) (liveconfig.Manager, error)
 	instancemetadata.InstanceMetadata
 }
@@ -35,7 +35,7 @@ func (c *core) IsLocalDevelopmentMode() bool {
 }
 
 func (c *core) NewLiveConfigManager(ctx context.Context) (liveconfig.Manager, error) {
-	msiAuthorizer, err := c.NewMSIAuthorizer(MSIContextRP, c.Environment().ResourceManagerEndpoint)
+	msiAuthorizer, err := c.NewMSIAuthorizer(MSIContextRP, c.Environment().ResourceManagerScope)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/env/dev.go b/pkg/env/dev.go
index 547433e4f..8b5e4e7bd 100644
--- a/pkg/env/dev.go
+++ b/pkg/env/dev.go
@@ -9,13 +9,12 @@ import (
 	"net"
 	"os"
 
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
-	"github.com/Azure/go-autorest/autorest/adal"
+	"github.com/Azure/go-autorest/autorest"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/util/clientauthorizer"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
@@ -85,32 +84,20 @@ func (d *dev) Listen() (net.Listener, error) {
 	return net.Listen("tcp", "localhost:8443")
 }
 
-func (d *dev) FPAuthorizer(tenantID, resource string) (refreshable.Authorizer, error) {
-	oauthConfig, err := adal.NewOAuthConfig(d.Environment().ActiveDirectoryEndpoint, tenantID)
+func (d *dev) FPAuthorizer(tenantID string, scopes ...string) (autorest.Authorizer, error) {
+	tokenCredential, err := d.FPNewClientCertificateCredential(tenantID)
 	if err != nil {
 		return nil, err
 	}
 
-	fpPrivateKey, fpCertificates := d.fpCertificateRefresher.GetCertificates()
-
-	sp, err := adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, d.fpClientID, fpCertificates[0], fpPrivateKey, resource)
-	if err != nil {
-		return nil, err
-	}
-
-	return refreshable.NewAuthorizer(sp), nil
+	return azidext.NewTokenCredentialAdapter(tokenCredential, scopes), nil
 }
 
 func (d *dev) FPNewClientCertificateCredential(tenantID string) (*azidentity.ClientCertificateCredential, error) {
 	fpPrivateKey, fpCertificates := d.fpCertificateRefresher.GetCertificates()
 
-	credential, err := azidentity.NewClientCertificateCredential(tenantID, d.fpClientID, fpCertificates, fpPrivateKey, &azidentity.ClientCertificateCredentialOptions{
-		ClientOptions: azcore.ClientOptions{
-			Cloud: d.Environment().Cloud,
-		},
-		SendCertificateChain: true,
-	})
-
+	options := d.Environment().ClientCertificateCredentialOptions()
+	credential, err := azidentity.NewClientCertificateCredential(tenantID, d.fpClientID, fpCertificates, fpPrivateKey, options)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/env/env.go b/pkg/env/env.go
index 762a9252c..75d1d4475 100644
--- a/pkg/env/env.go
+++ b/pkg/env/env.go
@@ -13,13 +13,13 @@ import (
 
 	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
+	"github.com/Azure/go-autorest/autorest"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/proxy"
 	"github.com/Azure/ARO-RP/pkg/util/clientauthorizer"
 	"github.com/Azure/ARO-RP/pkg/util/keyvault"
 	"github.com/Azure/ARO-RP/pkg/util/liveconfig"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 )
 
 type Feature int
@@ -81,7 +81,7 @@ type Interface interface {
 	ClusterKeyvault() keyvault.Manager
 	Domain() string
 	FeatureIsSet(Feature) bool
-	FPAuthorizer(string, string) (refreshable.Authorizer, error)
+	FPAuthorizer(string, ...string) (autorest.Authorizer, error)
 	FPNewClientCertificateCredential(string) (*azidentity.ClientCertificateCredential, error)
 	FPClientID() string
 	Listen() (net.Listener, error)
diff --git a/pkg/env/msiauthorizer.go b/pkg/env/msiauthorizer.go
index c21bdf7bc..fa6c635b9 100644
--- a/pkg/env/msiauthorizer.go
+++ b/pkg/env/msiauthorizer.go
@@ -7,8 +7,10 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/azure/auth"
+	"github.com/jongio/azidext/go/azidext"
 )
 
 type MSIContext string
@@ -18,28 +20,34 @@ const (
 	MSIContextGateway MSIContext = "GATEWAY"
 )
 
-func (c *core) NewMSIAuthorizer(msiContext MSIContext, resource string) (autorest.Authorizer, error) {
-	if !c.IsLocalDevelopmentMode() {
-		return auth.NewAuthorizerFromEnvironmentWithResource(resource)
-	}
+func (c *core) NewMSIAuthorizer(msiContext MSIContext, scopes ...string) (autorest.Authorizer, error) {
+	var tokenCredential azcore.TokenCredential
+	var err error
 
-	for _, key := range []string{
-		"AZURE_" + string(msiContext) + "_CLIENT_ID",
-		"AZURE_" + string(msiContext) + "_CLIENT_SECRET",
-		"AZURE_TENANT_ID",
-	} {
-		if _, found := os.LookupEnv(key); !found {
-			return nil, fmt.Errorf("environment variable %q unset (development mode)", key)
+	if !c.IsLocalDevelopmentMode() {
+		options := c.Environment().ManagedIdentityCredentialOptions()
+		tokenCredential, err = azidentity.NewManagedIdentityCredential(options)
+	} else {
+		for _, key := range []string{
+			"AZURE_" + string(msiContext) + "_CLIENT_ID",
+			"AZURE_" + string(msiContext) + "_CLIENT_SECRET",
+			"AZURE_TENANT_ID",
+		} {
+			if _, found := os.LookupEnv(key); !found {
+				return nil, fmt.Errorf("environment variable %q unset (development mode)", key)
+			}
 		}
-	}
 
-	config := &auth.ClientCredentialsConfig{
-		ClientID:     os.Getenv("AZURE_" + string(msiContext) + "_CLIENT_ID"),
-		ClientSecret: os.Getenv("AZURE_" + string(msiContext) + "_CLIENT_SECRET"),
-		TenantID:     os.Getenv("AZURE_TENANT_ID"),
-		Resource:     resource,
-		AADEndpoint:  c.Environment().ActiveDirectoryEndpoint,
+		options := c.Environment().ClientSecretCredentialOptions()
+		tokenCredential, err = azidentity.NewClientSecretCredential(
+			os.Getenv("AZURE_TENANT_ID"),
+			os.Getenv("AZURE_"+string(msiContext)+"_CLIENT_ID"),
+			os.Getenv("AZURE_"+string(msiContext)+"_CLIENT_SECRET"),
+			options)
+	}
+	if err != nil {
+		return nil, err
 	}
 
-	return config.Authorizer()
+	return azidext.NewTokenCredentialAdapter(tokenCredential, scopes), nil
 }
diff --git a/pkg/env/prod.go b/pkg/env/prod.go
index 61e587456..89e10b2d5 100644
--- a/pkg/env/prod.go
+++ b/pkg/env/prod.go
@@ -14,11 +14,11 @@ import (
 	"strings"
 	"time"
 
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
-	"github.com/Azure/go-autorest/autorest/adal"
+	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/azure"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/proxy"
@@ -27,7 +27,6 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/computeskus"
 	"github.com/Azure/ARO-RP/pkg/util/keyvault"
 	"github.com/Azure/ARO-RP/pkg/util/liveconfig"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
@@ -127,12 +126,12 @@ func newProd(ctx context.Context, log *logrus.Entry) (*prod, error) {
 		}
 	}
 
-	msiAuthorizer, err := p.NewMSIAuthorizer(MSIContextRP, p.Environment().ResourceManagerEndpoint)
+	msiAuthorizer, err := p.NewMSIAuthorizer(MSIContextRP, p.Environment().ResourceManagerScope)
 	if err != nil {
 		return nil, err
 	}
 
-	msiKVAuthorizer, err := p.NewMSIAuthorizer(MSIContextRP, p.Environment().ResourceIdentifiers.KeyVault)
+	msiKVAuthorizer, err := p.NewMSIAuthorizer(MSIContextRP, p.Environment().KeyVaultScope)
 	if err != nil {
 		return nil, err
 	}
@@ -156,7 +155,7 @@ func newProd(ctx context.Context, log *logrus.Entry) (*prod, error) {
 		return nil, err
 	}
 
-	localFPKVAuthorizer, err := p.FPAuthorizer(p.TenantID(), p.Environment().ResourceIdentifiers.KeyVault)
+	localFPKVAuthorizer, err := p.FPAuthorizer(p.TenantID(), p.Environment().KeyVaultScope)
 	if err != nil {
 		return nil, err
 	}
@@ -321,20 +320,13 @@ func (p *prod) FeatureIsSet(f Feature) bool {
 	return p.features[f]
 }
 
-func (p *prod) FPAuthorizer(tenantID, resource string) (refreshable.Authorizer, error) {
-	oauthConfig, err := adal.NewOAuthConfig(p.Environment().ActiveDirectoryEndpoint, tenantID)
+func (p *prod) FPAuthorizer(tenantID string, scopes ...string) (autorest.Authorizer, error) {
+	tokenCredential, err := p.FPNewClientCertificateCredential(tenantID)
 	if err != nil {
 		return nil, err
 	}
 
-	fpPrivateKey, fpCertificates := p.fpCertificateRefresher.GetCertificates()
-
-	sp, err := adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, p.fpClientID, fpCertificates[0], fpPrivateKey, resource)
-	if err != nil {
-		return nil, err
-	}
-
-	return refreshable.NewAuthorizer(sp), nil
+	return azidext.NewTokenCredentialAdapter(tokenCredential, scopes), nil
 }
 
 func (p *prod) FPClientID() string {
@@ -376,13 +368,8 @@ func (p *prod) LiveConfig() liveconfig.Manager {
 func (p *prod) FPNewClientCertificateCredential(tenantID string) (*azidentity.ClientCertificateCredential, error) {
 	fpPrivateKey, fpCertificates := p.fpCertificateRefresher.GetCertificates()
 
-	credential, err := azidentity.NewClientCertificateCredential(tenantID, p.fpClientID, fpCertificates, fpPrivateKey, &azidentity.ClientCertificateCredentialOptions{
-		ClientOptions: azcore.ClientOptions{
-			Cloud: p.Environment().Cloud,
-		},
-		SendCertificateChain: true,
-	})
-
+	options := p.Environment().ClientCertificateCredentialOptions()
+	credential, err := azidentity.NewClientCertificateCredential(tenantID, p.fpClientID, fpCertificates, fpPrivateKey, options)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/frontend/adminactions/azureactions.go b/pkg/frontend/adminactions/azureactions.go
index 1278ecd0c..1fc1fc88e 100644
--- a/pkg/frontend/adminactions/azureactions.go
+++ b/pkg/frontend/adminactions/azureactions.go
@@ -60,7 +60,7 @@ type azureActions struct {
 func NewAzureActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster,
 	subscriptionDoc *api.SubscriptionDocument) (AzureActions, error) {
 	fpAuth, err := env.FPAuthorizer(subscriptionDoc.Subscription.Properties.TenantID,
-		env.Environment().ResourceManagerEndpoint)
+		env.Environment().ResourceManagerScope)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/frontend/providers_validation.go b/pkg/frontend/providers_validation.go
index d7a162c62..7bd1596bf 100644
--- a/pkg/frontend/providers_validation.go
+++ b/pkg/frontend/providers_validation.go
@@ -29,7 +29,7 @@ var requiredResourceProviders = []string{
 }
 
 func (p providersValidator) ValidateProviders(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string) error {
-	fpAuthorizer, err := environment.FPAuthorizer(tenantID, environment.Environment().ResourceManagerEndpoint)
+	fpAuthorizer, err := environment.FPAuthorizer(tenantID, environment.Environment().ResourceManagerScope)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/frontend/quota_validation.go b/pkg/frontend/quota_validation.go
index a94a8f082..0a08e7523 100644
--- a/pkg/frontend/quota_validation.go
+++ b/pkg/frontend/quota_validation.go
@@ -39,7 +39,7 @@ func addRequiredResources(requiredResources map[string]int, vmSize api.VMSize, c
 // creation
 // It is a method on struct so we can make use of interfaces.
 func (q quotaValidator) ValidateQuota(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string, oc *api.OpenShiftCluster) error {
-	fpAuthorizer, err := environment.FPAuthorizer(tenantID, environment.Environment().ResourceManagerEndpoint)
+	fpAuthorizer, err := environment.FPAuthorizer(tenantID, environment.Environment().ResourceManagerScope)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/frontend/sku_validation.go b/pkg/frontend/sku_validation.go
index 2a03d787a..8df0027d8 100644
--- a/pkg/frontend/sku_validation.go
+++ b/pkg/frontend/sku_validation.go
@@ -24,7 +24,7 @@ type SkuValidator interface {
 type skuValidator struct{}
 
 func (s skuValidator) ValidateVMSku(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string, oc *api.OpenShiftCluster) error {
-	fpAuthorizer, err := environment.FPAuthorizer(tenantID, environment.Environment().ResourceManagerEndpoint)
+	fpAuthorizer, err := environment.FPAuthorizer(tenantID, environment.Environment().ResourceManagerScope)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/installer/install.go b/pkg/installer/install.go
index a1972a5db..e3bf4e2ae 100644
--- a/pkg/installer/install.go
+++ b/pkg/installer/install.go
@@ -42,7 +42,7 @@ func (m *manager) Install(ctx context.Context) error {
 		steps.Action(func(ctx context.Context) error {
 			return m.persistGraph(ctx, g)
 		}),
-		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.deployResourceTemplate)),
+		steps.Action(m.deployResourceTemplate),
 		steps.Action(m.initializeKubernetesClients),
 		steps.Condition(m.bootstrapConfigMapReady, 30*time.Minute, true),
 	}
diff --git a/pkg/installer/manager.go b/pkg/installer/manager.go
index 74e62fc2c..84245e2ae 100644
--- a/pkg/installer/manager.go
+++ b/pkg/installer/manager.go
@@ -6,6 +6,7 @@ package installer
 import (
 	"context"
 
+	"github.com/Azure/go-autorest/autorest"
 	"github.com/sirupsen/logrus"
 	"k8s.io/client-go/kubernetes"
 
@@ -13,7 +14,6 @@ import (
 	"github.com/Azure/ARO-RP/pkg/cluster/graph"
 	"github.com/Azure/ARO-RP/pkg/env"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/features"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 )
 
 type manager struct {
@@ -28,7 +28,7 @@ type manager struct {
 	sub         *api.Subscription
 	version     *api.OpenShiftVersion
 
-	fpAuthorizer refreshable.Authorizer
+	fpAuthorizer autorest.Authorizer
 
 	deployments features.DeploymentsClient
 
@@ -41,7 +41,7 @@ type Interface interface {
 	Install(ctx context.Context) error
 }
 
-func NewInstaller(log *logrus.Entry, _env env.Interface, clusterUUID string, oc *api.OpenShiftCluster, subscription *api.Subscription, version *api.OpenShiftVersion, fpAuthorizer refreshable.Authorizer, deployments features.DeploymentsClient, g graph.Manager) Interface {
+func NewInstaller(log *logrus.Entry, _env env.Interface, clusterUUID string, oc *api.OpenShiftCluster, subscription *api.Subscription, version *api.OpenShiftVersion, fpAuthorizer autorest.Authorizer, deployments features.DeploymentsClient, g graph.Manager) Interface {
 	return &manager{
 		log:          log,
 		env:          _env,
diff --git a/pkg/operator/controllers/checkers/serviceprincipalchecker/checker.go b/pkg/operator/controllers/checkers/serviceprincipalchecker/checker.go
index aa8ab653c..2ea6da721 100644
--- a/pkg/operator/controllers/checkers/serviceprincipalchecker/checker.go
+++ b/pkg/operator/controllers/checkers/serviceprincipalchecker/checker.go
@@ -6,11 +6,11 @@ package serviceprincipalchecker
 import (
 	"context"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/sirupsen/logrus"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/Azure/ARO-RP/pkg/api/validate/dynamic"
-	"github.com/Azure/ARO-RP/pkg/util/aad"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
 	"github.com/Azure/ARO-RP/pkg/util/clusterauthorizer"
 )
@@ -22,21 +22,21 @@ type servicePrincipalChecker interface {
 type checker struct {
 	log *logrus.Entry
 
-	credentials    func(ctx context.Context) (*clusterauthorizer.Credentials, error)
-	newSPValidator func(azEnv *azureclient.AROEnvironment) (dynamic.ServicePrincipalValidator, error)
+	credentials        func(ctx context.Context) (*clusterauthorizer.Credentials, error)
+	getTokenCredential func(azEnv *azureclient.AROEnvironment, credentials *clusterauthorizer.Credentials) (azcore.TokenCredential, error)
+	newSPValidator     func(azEnv *azureclient.AROEnvironment) (dynamic.ServicePrincipalValidator, error)
 }
 
 func newServicePrincipalChecker(log *logrus.Entry, client client.Client) *checker {
-	tokenClient := aad.NewTokenClient()
-
 	return &checker{
 		log: log,
 
 		credentials: func(ctx context.Context) (*clusterauthorizer.Credentials, error) {
 			return clusterauthorizer.AzCredentials(ctx, client)
 		},
+		getTokenCredential: clusterauthorizer.GetTokenCredential,
 		newSPValidator: func(azEnv *azureclient.AROEnvironment) (dynamic.ServicePrincipalValidator, error) {
-			return dynamic.NewServicePrincipalValidator(log, azEnv, dynamic.AuthorizerClusterServicePrincipal, tokenClient)
+			return dynamic.NewServicePrincipalValidator(log, azEnv, dynamic.AuthorizerClusterServicePrincipal)
 		},
 	}
 }
@@ -57,5 +57,10 @@ func (r *checker) Check(ctx context.Context, AZEnvironment string) error {
 		return err
 	}
 
-	return spDynamic.ValidateServicePrincipal(ctx, string(azCred.ClientID), string(azCred.ClientSecret), string(azCred.TenantID))
+	tokenCredential, err := r.getTokenCredential(&azEnv, azCred)
+	if err != nil {
+		return err
+	}
+
+	return spDynamic.ValidateServicePrincipal(ctx, tokenCredential)
 }
diff --git a/pkg/operator/controllers/previewfeature/previewfeature_controller.go b/pkg/operator/controllers/previewfeature/previewfeature_controller.go
index b19cf7e20..d101f6f8f 100644
--- a/pkg/operator/controllers/previewfeature/previewfeature_controller.go
+++ b/pkg/operator/controllers/previewfeature/previewfeature_controller.go
@@ -18,7 +18,6 @@ import (
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
 	aropreviewv1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/preview.aro.openshift.io/v1alpha1"
 	"github.com/Azure/ARO-RP/pkg/operator/controllers/previewfeature/nsgflowlogs"
-	"github.com/Azure/ARO-RP/pkg/util/aad"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network"
 	"github.com/Azure/ARO-RP/pkg/util/clusterauthorizer"
@@ -74,7 +73,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
 	}
 
 	// create refreshable authorizer from token
-	azRefreshAuthorizer, err := clusterauthorizer.NewAzRefreshableAuthorizer(r.log, &azEnv, r.client, aad.NewTokenClient())
+	azRefreshAuthorizer, err := clusterauthorizer.NewAzRefreshableAuthorizer(r.log, &azEnv, r.client)
 	if err != nil {
 		return reconcile.Result{}, err
 	}
diff --git a/pkg/operator/controllers/storageaccounts/storageaccount_controller.go b/pkg/operator/controllers/storageaccounts/storageaccount_controller.go
index 747a98997..b6aeb851a 100644
--- a/pkg/operator/controllers/storageaccounts/storageaccount_controller.go
+++ b/pkg/operator/controllers/storageaccounts/storageaccount_controller.go
@@ -20,7 +20,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/source"
 
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
-	"github.com/Azure/ARO-RP/pkg/util/aad"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/storage"
 	"github.com/Azure/ARO-RP/pkg/util/clusterauthorizer"
@@ -87,7 +86,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
 	}
 
 	// create refreshable authorizer from token
-	azRefreshAuthorizer, err := clusterauthorizer.NewAzRefreshableAuthorizer(r.log, &azEnv, r.client, aad.NewTokenClient())
+	azRefreshAuthorizer, err := clusterauthorizer.NewAzRefreshableAuthorizer(r.log, &azEnv, r.client)
 	if err != nil {
 		return reconcile.Result{}, err
 	}
diff --git a/pkg/operator/controllers/subnets/subnets_controller.go b/pkg/operator/controllers/subnets/subnets_controller.go
index d6d332321..f24357af1 100644
--- a/pkg/operator/controllers/subnets/subnets_controller.go
+++ b/pkg/operator/controllers/subnets/subnets_controller.go
@@ -22,7 +22,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/source"
 
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
-	"github.com/Azure/ARO-RP/pkg/util/aad"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
 	"github.com/Azure/ARO-RP/pkg/util/clusterauthorizer"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
@@ -94,7 +93,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
 	}
 
 	// create a refreshable authorizer from token
-	azRefreshAuthorizer, err := clusterauthorizer.NewAzRefreshableAuthorizer(r.log, &azEnv, r.client, aad.NewTokenClient())
+	azRefreshAuthorizer, err := clusterauthorizer.NewAzRefreshableAuthorizer(r.log, &azEnv, r.client)
 	if err != nil {
 		return reconcile.Result{}, err
 	}
diff --git a/pkg/util/aad/aad.go b/pkg/util/aad/aad.go
deleted file mode 100644
index b6ade4939..000000000
--- a/pkg/util/aad/aad.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package aad
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-	"net/http"
-	"time"
-
-	"github.com/Azure/go-autorest/autorest/adal"
-	"github.com/Azure/go-autorest/autorest/azure/auth"
-	"github.com/form3tech-oss/jwt-go"
-	"github.com/sirupsen/logrus"
-	"k8s.io/apimachinery/pkg/util/wait"
-
-	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
-)
-
-const (
-	defaultTimeout = 5 * time.Minute
-)
-
-type TokenClient interface {
-	GetToken(ctx context.Context, log *logrus.Entry, clientID, clientSecret, tenantID string, aadEndpoint, resource string) (*adal.ServicePrincipalToken, error)
-}
-
-type tokenClient struct{}
-
-func NewTokenClient() TokenClient {
-	return &tokenClient{}
-}
-
-func (tc *tokenClient) GetToken(ctx context.Context, log *logrus.Entry, clientID, clientSecret, tenantID, aadEndpoint, resource string) (*adal.ServicePrincipalToken, error) {
-	spToken, err := newServicePrincipalToken(clientID, clientSecret, tenantID, aadEndpoint, resource)
-	if err != nil {
-		return spToken, err
-	}
-
-	tokenAuthorizer := refreshable.NewAuthorizer(spToken)
-
-	err = authenticateServicePrincipalToken(ctx, log, tokenAuthorizer, defaultTimeout)
-	return spToken, err
-}
-
-func refreshContext(ctx context.Context, authorizer refreshable.Authorizer, log *logrus.Entry) (bool, error) {
-	done, err := authorizer.RefreshWithContext(ctx, log)
-	if err != nil {
-		err = api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidServicePrincipalCredentials, "properties.servicePrincipalProfile", "The provided service principal credentials are invalid.")
-	}
-	return done, err
-}
-
-// GetToken authenticates in the customer's tenant as the cluster service
-// principal and returns a token.
-func newServicePrincipalToken(clientID, clientSecret, tenantID, aadEndpoint, resource string) (*adal.ServicePrincipalToken, error) {
-	conf := auth.ClientCredentialsConfig{
-		ClientID:     clientID,
-		ClientSecret: clientSecret,
-		TenantID:     tenantID,
-		Resource:     resource,
-		AADEndpoint:  aadEndpoint,
-	}
-
-	spToken, err := conf.ServicePrincipalToken()
-	if err != nil {
-		return nil, err
-	}
-
-	return spToken, nil
-}
-
-// AuthenticateServicePrincipalToken authenticates in the customer's tenant as the cluster service principal and returns a token.
-func authenticateServicePrincipalToken(ctx context.Context, log *logrus.Entry, authorizer refreshable.Authorizer, timeout time.Duration) error {
-	// during credentials rotation this can take time to propagate
-	// it is overridable so we can have unit tests pass/fail quicker
-	timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
-	defer cancel()
-
-	var err error
-	done := false
-	// NOTE: Do not override err with the error returned by
-	// wait.PollImmediateUntil. Doing this will not propagate the latest error
-	// to the user in case when wait exceeds the timeout
-	_ = wait.PollImmediateUntil(10*time.Second, func() (bool, error) {
-		done, err = refreshContext(ctx, authorizer, log)
-		if !done || err != nil {
-			return false, err
-		}
-
-		p := &jwt.Parser{}
-		claims := jwt.MapClaims{}
-		_, _, err = p.ParseUnverified(authorizer.OAuthToken(), claims)
-		if err != nil {
-			err = api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidServicePrincipalToken, "properties.servicePrincipalProfile", "The provided service principal generated an invalid token.")
-			return false, err
-		}
-
-		for _, claim := range []string{"altsecid", "oid", "puid"} {
-			if _, found := claims[claim]; found {
-				return true, nil
-			}
-		}
-
-		// populate err with a user-facing error that will be visible if we're
-		// not successful.
-		log.Info("token does not contain the required claims")
-		err = api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidServicePrincipalClaims, "properties.servicePrincipalProfile", "The provided service principal does not give an access token with at least one of the claims 'altsecid', 'oid' or 'puid'.")
-
-		return false, nil
-	}, timeoutCtx.Done())
-
-	if err != nil {
-		return err
-	}
-
-	if !done && authorizer.LastError() != nil {
-		return api.NewCloudError(
-			http.StatusBadRequest,
-			api.CloudErrorCodeInvalidServicePrincipalCredentials,
-			"properties.servicePrincipalProfile",
-			"The provided service principal credentials are invalid.")
-	}
-
-	return nil
-}
diff --git a/pkg/util/aad/generate.go b/pkg/util/aad/generate.go
deleted file mode 100644
index 1f964be24..000000000
--- a/pkg/util/aad/generate.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package aad
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-//go:generate rm -rf ../mocks/$GOPACKAGE
-//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/$GOPACKAGE TokenClient
-//go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../mocks/$GOPACKAGE/$GOPACKAGE.go
diff --git a/pkg/util/azureclient/environments.go b/pkg/util/azureclient/environments.go
index e20ce29de..6abd2307d 100644
--- a/pkg/util/azureclient/environments.go
+++ b/pkg/util/azureclient/environments.go
@@ -7,7 +7,9 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	"github.com/Azure/go-autorest/autorest/azure"
 )
 
@@ -23,6 +25,11 @@ type AROEnvironment struct {
 	AuthzRemotePDPEndPoint   string
 	AzureRbacPDPEnvironment
 	Cloud cloud.Configuration
+	// Microsoft identity platform scopes used by ARO
+	// See https://learn.microsoft.com/EN-US/azure/active-directory/develop/scopes-oidc#the-default-scope
+	ResourceManagerScope      string
+	KeyVaultScope             string
+	ActiveDirectoryGraphScope string
 }
 
 // AzureRbacPDPEnvironment contains cloud specific instance of Authz RBAC PDP Remote Server
@@ -46,6 +53,9 @@ var (
 			Endpoint:   "https://%s.authorization.azure.net/providers/Microsoft.Authorization/checkAccess?api-version=2021-06-01-preview",
 			OAuthScope: "https://authorization.azure.net/.default",
 		},
+		ResourceManagerScope:      azure.PublicCloud.ResourceManagerEndpoint + "/.default",
+		KeyVaultScope:             azure.PublicCloud.ResourceIdentifiers.KeyVault + "/.default",
+		ActiveDirectoryGraphScope: azure.PublicCloud.GraphEndpoint + "/.default",
 	}
 
 	// USGovernmentCloud contains additional ARO information for the US Gov cloud environment.
@@ -62,6 +72,9 @@ var (
 			Endpoint:   "https://%s.authorization.azure.us/providers/Microsoft.Authorization/checkAccess?api-version=2021-06-01-preview",
 			OAuthScope: "https://authorization.azure.us/.default",
 		},
+		ResourceManagerScope:      azure.USGovernmentCloud.ResourceManagerEndpoint + "/.default",
+		KeyVaultScope:             azure.USGovernmentCloud.ResourceIdentifiers.KeyVault + "/.default",
+		ActiveDirectoryGraphScope: azure.USGovernmentCloud.GraphEndpoint + "/.default",
 	}
 )
 
@@ -75,3 +88,37 @@ func EnvironmentFromName(name string) (AROEnvironment, error) {
 	}
 	return AROEnvironment{}, fmt.Errorf("cloud environment %q is unsupported by ARO", name)
 }
+
+func (e *AROEnvironment) ClientCertificateCredentialOptions() *azidentity.ClientCertificateCredentialOptions {
+	return &azidentity.ClientCertificateCredentialOptions{
+		ClientOptions: azcore.ClientOptions{
+			Cloud: e.Cloud,
+		},
+		// Required for Subject Name/Issuer (SNI) authentication
+		SendCertificateChain: true,
+	}
+}
+
+func (e *AROEnvironment) ClientSecretCredentialOptions() *azidentity.ClientSecretCredentialOptions {
+	return &azidentity.ClientSecretCredentialOptions{
+		ClientOptions: azcore.ClientOptions{
+			Cloud: e.Cloud,
+		},
+	}
+}
+
+func (e *AROEnvironment) EnvironmentCredentialOptions() *azidentity.EnvironmentCredentialOptions {
+	return &azidentity.EnvironmentCredentialOptions{
+		ClientOptions: azcore.ClientOptions{
+			Cloud: e.Cloud,
+		},
+	}
+}
+
+func (e *AROEnvironment) ManagedIdentityCredentialOptions() *azidentity.ManagedIdentityCredentialOptions {
+	return &azidentity.ManagedIdentityCredentialOptions{
+		ClientOptions: azcore.ClientOptions{
+			Cloud: e.Cloud,
+		},
+	}
+}
diff --git a/pkg/util/billing/billing.go b/pkg/util/billing/billing.go
index 9adc89ece..a8e97b8a2 100644
--- a/pkg/util/billing/billing.go
+++ b/pkg/util/billing/billing.go
@@ -64,7 +64,7 @@ func storageClient(env env.Interface, billing database.Billing, sub database.Sub
 		return nil, err
 	}
 
-	localFPAuthorizer, err := env.FPAuthorizer(env.TenantID(), env.Environment().ResourceManagerEndpoint)
+	localFPAuthorizer, err := env.FPAuthorizer(env.TenantID(), env.Environment().ResourceManagerScope)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/util/cluster/cluster.go b/pkg/util/cluster/cluster.go
index d7e0cb073..1bc1152ea 100644
--- a/pkg/util/cluster/cluster.go
+++ b/pkg/util/cluster/cluster.go
@@ -15,14 +15,15 @@ import (
 	"strings"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	mgmtkeyvault "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2019-09-01/keyvault"
 	mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network"
 	mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization"
 	mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features"
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/Azure/go-autorest/autorest/azure/auth"
 	"github.com/Azure/go-autorest/autorest/to"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/util/wait"
 
@@ -93,15 +94,17 @@ func New(log *logrus.Entry, environment env.Core, ci bool) (*Cluster, error) {
 		}
 	}
 
-	authorizer, err := auth.NewAuthorizerFromEnvironment()
+	options := environment.Environment().EnvironmentCredentialOptions()
+	tokenCredential, err := azidentity.NewEnvironmentCredential(options)
 	if err != nil {
 		return nil, err
 	}
 
-	graphAuthorizer, err := auth.NewAuthorizerFromEnvironmentWithResource(environment.Environment().GraphEndpoint)
-	if err != nil {
-		return nil, err
-	}
+	scopes := []string{environment.Environment().GraphEndpoint + "/.default"}
+	graphAuthorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
+
+	scopes = []string{environment.Environment().ResourceManagerScope}
+	authorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
 
 	c := &Cluster{
 		log: log,
diff --git a/pkg/util/clusterauthorizer/authorizer.go b/pkg/util/clusterauthorizer/authorizer.go
index 55cb7fe30..e654ad702 100644
--- a/pkg/util/clusterauthorizer/authorizer.go
+++ b/pkg/util/clusterauthorizer/authorizer.go
@@ -8,17 +8,17 @@ import (
 	"fmt"
 	"net/http"
 
-	"github.com/form3tech-oss/jwt-go"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	"github.com/Azure/go-autorest/autorest"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/util/aad"
-	"github.com/Azure/ARO-RP/pkg/util/azureclaim"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 )
 
 type Credentials struct {
@@ -31,12 +31,13 @@ type azRefreshableAuthorizer struct {
 	log              *logrus.Entry
 	azureEnvironment *azureclient.AROEnvironment
 	client           client.Client
-	tokenClient      aad.TokenClient
+
+	getTokenCredential func(*azureclient.AROEnvironment, *Credentials) (azcore.TokenCredential, error)
 }
 
 // NewAzRefreshableAuthorizer returns a new refreshable authorizer
 // using Cluster Service Principal.
-func NewAzRefreshableAuthorizer(log *logrus.Entry, azEnv *azureclient.AROEnvironment, client client.Client, tokenClient aad.TokenClient) (*azRefreshableAuthorizer, error) {
+func NewAzRefreshableAuthorizer(log *logrus.Entry, azEnv *azureclient.AROEnvironment, client client.Client) (*azRefreshableAuthorizer, error) {
 	if log == nil {
 		return nil, fmt.Errorf("log entry cannot be nil")
 	}
@@ -44,39 +45,37 @@ func NewAzRefreshableAuthorizer(log *logrus.Entry, azEnv *azureclient.AROEnviron
 		return nil, fmt.Errorf("azureEnvironment cannot be nil")
 	}
 	return &azRefreshableAuthorizer{
-		log:              log,
-		azureEnvironment: azEnv,
-		client:           client,
-		tokenClient:      tokenClient,
+		log:                log,
+		azureEnvironment:   azEnv,
+		client:             client,
+		getTokenCredential: GetTokenCredential,
 	}, nil
 }
 
-func (a *azRefreshableAuthorizer) NewRefreshableAuthorizerToken(ctx context.Context) (refreshable.Authorizer, error) {
+func (a *azRefreshableAuthorizer) NewRefreshableAuthorizerToken(ctx context.Context) (autorest.Authorizer, error) {
 	// Grab azure-credentials from secret
 	credentials, err := AzCredentials(ctx, a.client)
 	if err != nil {
 		return nil, err
 	}
-	// create service principal token from azure-credentials
-	token, err := a.tokenClient.GetToken(ctx,
-		a.log,
-		string(credentials.ClientID),
-		string(credentials.ClientSecret),
-		string(credentials.TenantID),
-		a.azureEnvironment.ActiveDirectoryEndpoint,
-		a.azureEnvironment.ResourceManagerEndpoint)
-	if err != nil {
-		return nil, err
-	}
 
-	p := &jwt.Parser{}
-	c := &azureclaim.AzureClaim{}
-	_, _, err = p.ParseUnverified(token.OAuthToken(), c)
+	// Create service principal token from azure-credentials
+	tokenCredential, err := a.getTokenCredential(a.azureEnvironment, credentials)
 	if err != nil {
 		return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidServicePrincipalCredentials, "properties.servicePrincipalProfile", "the provided service principal is invalid")
 	}
 
-	return refreshable.NewAuthorizer(token), nil
+	scopes := []string{a.azureEnvironment.ResourceManagerScope}
+
+	return azidext.NewTokenCredentialAdapter(tokenCredential, scopes), nil
+}
+
+func GetTokenCredential(environment *azureclient.AROEnvironment, credentials *Credentials) (azcore.TokenCredential, error) {
+	return azidentity.NewClientSecretCredential(
+		string(credentials.TenantID),
+		string(credentials.ClientID),
+		string(credentials.ClientSecret),
+		environment.ClientSecretCredentialOptions())
 }
 
 // AzCredentials gets Cluster Service Principal credentials from the Kubernetes secrets
diff --git a/pkg/util/instancemetadata/generate.go b/pkg/util/instancemetadata/generate.go
index 4818c0260..471680826 100644
--- a/pkg/util/instancemetadata/generate.go
+++ b/pkg/util/instancemetadata/generate.go
@@ -4,5 +4,5 @@ package instancemetadata
 // Licensed under the Apache License 2.0.
 
 //go:generate rm -rf ../mocks/$GOPACKAGE
-//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/$GOPACKAGE ServicePrincipalToken,InstanceMetadata
+//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/$GOPACKAGE InstanceMetadata
 //go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../mocks/$GOPACKAGE/$GOPACKAGE.go
diff --git a/pkg/util/instancemetadata/prod.go b/pkg/util/instancemetadata/prod.go
index daea87c08..6dd77386f 100644
--- a/pkg/util/instancemetadata/prod.go
+++ b/pkg/util/instancemetadata/prod.go
@@ -11,31 +11,23 @@ import (
 	"os"
 	"strings"
 
-	"github.com/Azure/go-autorest/autorest/adal"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	"github.com/form3tech-oss/jwt-go"
 
 	"github.com/Azure/ARO-RP/pkg/util/azureclaim"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
 )
 
-type ServicePrincipalToken interface {
-	RefreshWithContext(context.Context) error
-	OAuthToken() string
-}
-
 type prod struct {
 	instanceMetadata
 
-	do                              func(*http.Request) (*http.Response, error)
-	newServicePrincipalTokenFromMSI func(string, string) (ServicePrincipalToken, error)
+	do func(*http.Request) (*http.Response, error)
 }
 
 func newProd(ctx context.Context) (InstanceMetadata, error) {
 	p := &prod{
 		do: http.DefaultClient.Do,
-		newServicePrincipalTokenFromMSI: func(msiEndpoint, resource string) (ServicePrincipalToken, error) {
-			return adal.NewServicePrincipalTokenFromMSI(msiEndpoint, resource)
-		},
 	}
 
 	err := p.populateInstanceMetadata()
@@ -52,24 +44,23 @@ func newProd(ctx context.Context) (InstanceMetadata, error) {
 }
 
 func (p *prod) populateTenantIDFromMSI(ctx context.Context) error {
-	msiEndpoint, err := adal.GetMSIVMEndpoint()
+	options := p.Environment().ManagedIdentityCredentialOptions()
+	tokenCredential, err := azidentity.NewManagedIdentityCredential(options)
 	if err != nil {
 		return err
 	}
 
-	token, err := p.newServicePrincipalTokenFromMSI(msiEndpoint, p.Environment().ResourceManagerEndpoint)
-	if err != nil {
-		return err
+	tokenRequestOptions := policy.TokenRequestOptions{
+		Scopes: []string{p.Environment().ResourceManagerScope},
 	}
-
-	err = token.RefreshWithContext(ctx)
+	token, err := tokenCredential.GetToken(ctx, tokenRequestOptions)
 	if err != nil {
 		return err
 	}
 
 	parser := &jwt.Parser{}
 	c := &azureclaim.AzureClaim{}
-	_, _, err = parser.ParseUnverified(token.OAuthToken(), c)
+	_, _, err = parser.ParseUnverified(token.Token, c)
 	if err != nil {
 		return fmt.Errorf("the provided service principal is invalid")
 	}
diff --git a/pkg/util/mocks/aad/aad.go b/pkg/util/mocks/aad/aad.go
deleted file mode 100644
index c3027e588..000000000
--- a/pkg/util/mocks/aad/aad.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/Azure/ARO-RP/pkg/util/aad (interfaces: TokenClient)
-
-// Package mock_aad is a generated GoMock package.
-package mock_aad
-
-import (
-	context "context"
-	reflect "reflect"
-
-	adal "github.com/Azure/go-autorest/autorest/adal"
-	gomock "github.com/golang/mock/gomock"
-	logrus "github.com/sirupsen/logrus"
-)
-
-// MockTokenClient is a mock of TokenClient interface.
-type MockTokenClient struct {
-	ctrl     *gomock.Controller
-	recorder *MockTokenClientMockRecorder
-}
-
-// MockTokenClientMockRecorder is the mock recorder for MockTokenClient.
-type MockTokenClientMockRecorder struct {
-	mock *MockTokenClient
-}
-
-// NewMockTokenClient creates a new mock instance.
-func NewMockTokenClient(ctrl *gomock.Controller) *MockTokenClient {
-	mock := &MockTokenClient{ctrl: ctrl}
-	mock.recorder = &MockTokenClientMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockTokenClient) EXPECT() *MockTokenClientMockRecorder {
-	return m.recorder
-}
-
-// GetToken mocks base method.
-func (m *MockTokenClient) GetToken(arg0 context.Context, arg1 *logrus.Entry, arg2, arg3, arg4, arg5, arg6 string) (*adal.ServicePrincipalToken, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetToken", arg0, arg1, arg2, arg3, arg4, arg5, arg6)
-	ret0, _ := ret[0].(*adal.ServicePrincipalToken)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// GetToken indicates an expected call of GetToken.
-func (mr *MockTokenClientMockRecorder) GetToken(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockTokenClient)(nil).GetToken), arg0, arg1, arg2, arg3, arg4, arg5, arg6)
-}
diff --git a/pkg/util/mocks/dynamic/dynamic.go b/pkg/util/mocks/dynamic/dynamic.go
index b8c7187ab..9eaea628d 100644
--- a/pkg/util/mocks/dynamic/dynamic.go
+++ b/pkg/util/mocks/dynamic/dynamic.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/Azure/ARO-RP/pkg/api/validate/dynamic (interfaces: Dynamic)
+// Source: dynamic.go
 
 // Package mock_dynamic is a generated GoMock package.
 package mock_dynamic
@@ -8,12 +8,50 @@ import (
 	context "context"
 	reflect "reflect"
 
+	azcore "github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	gomock "github.com/golang/mock/gomock"
 
 	api "github.com/Azure/ARO-RP/pkg/api"
 	dynamic "github.com/Azure/ARO-RP/pkg/api/validate/dynamic"
 )
 
+// MockServicePrincipalValidator is a mock of ServicePrincipalValidator interface.
+type MockServicePrincipalValidator struct {
+	ctrl     *gomock.Controller
+	recorder *MockServicePrincipalValidatorMockRecorder
+}
+
+// MockServicePrincipalValidatorMockRecorder is the mock recorder for MockServicePrincipalValidator.
+type MockServicePrincipalValidatorMockRecorder struct {
+	mock *MockServicePrincipalValidator
+}
+
+// NewMockServicePrincipalValidator creates a new mock instance.
+func NewMockServicePrincipalValidator(ctrl *gomock.Controller) *MockServicePrincipalValidator {
+	mock := &MockServicePrincipalValidator{ctrl: ctrl}
+	mock.recorder = &MockServicePrincipalValidatorMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockServicePrincipalValidator) EXPECT() *MockServicePrincipalValidatorMockRecorder {
+	return m.recorder
+}
+
+// ValidateServicePrincipal mocks base method.
+func (m *MockServicePrincipalValidator) ValidateServicePrincipal(ctx context.Context, tokenCredential azcore.TokenCredential) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ValidateServicePrincipal", ctx, tokenCredential)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ValidateServicePrincipal indicates an expected call of ValidateServicePrincipal.
+func (mr *MockServicePrincipalValidatorMockRecorder) ValidateServicePrincipal(ctx, tokenCredential interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateServicePrincipal", reflect.TypeOf((*MockServicePrincipalValidator)(nil).ValidateServicePrincipal), ctx, tokenCredential)
+}
+
 // MockDynamic is a mock of Dynamic interface.
 type MockDynamic struct {
 	ctrl     *gomock.Controller
@@ -38,66 +76,66 @@ func (m *MockDynamic) EXPECT() *MockDynamicMockRecorder {
 }
 
 // ValidateDiskEncryptionSets mocks base method.
-func (m *MockDynamic) ValidateDiskEncryptionSets(arg0 context.Context, arg1 *api.OpenShiftCluster) error {
+func (m *MockDynamic) ValidateDiskEncryptionSets(ctx context.Context, oc *api.OpenShiftCluster) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ValidateDiskEncryptionSets", arg0, arg1)
+	ret := m.ctrl.Call(m, "ValidateDiskEncryptionSets", ctx, oc)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // ValidateDiskEncryptionSets indicates an expected call of ValidateDiskEncryptionSets.
-func (mr *MockDynamicMockRecorder) ValidateDiskEncryptionSets(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockDynamicMockRecorder) ValidateDiskEncryptionSets(ctx, oc interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateDiskEncryptionSets", reflect.TypeOf((*MockDynamic)(nil).ValidateDiskEncryptionSets), arg0, arg1)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateDiskEncryptionSets", reflect.TypeOf((*MockDynamic)(nil).ValidateDiskEncryptionSets), ctx, oc)
 }
 
 // ValidateEncryptionAtHost mocks base method.
-func (m *MockDynamic) ValidateEncryptionAtHost(arg0 context.Context, arg1 *api.OpenShiftCluster) error {
+func (m *MockDynamic) ValidateEncryptionAtHost(ctx context.Context, oc *api.OpenShiftCluster) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ValidateEncryptionAtHost", arg0, arg1)
+	ret := m.ctrl.Call(m, "ValidateEncryptionAtHost", ctx, oc)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // ValidateEncryptionAtHost indicates an expected call of ValidateEncryptionAtHost.
-func (mr *MockDynamicMockRecorder) ValidateEncryptionAtHost(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockDynamicMockRecorder) ValidateEncryptionAtHost(ctx, oc interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateEncryptionAtHost", reflect.TypeOf((*MockDynamic)(nil).ValidateEncryptionAtHost), arg0, arg1)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateEncryptionAtHost", reflect.TypeOf((*MockDynamic)(nil).ValidateEncryptionAtHost), ctx, oc)
 }
 
 // ValidateServicePrincipal mocks base method.
-func (m *MockDynamic) ValidateServicePrincipal(arg0 context.Context, arg1, arg2, arg3 string) error {
+func (m *MockDynamic) ValidateServicePrincipal(ctx context.Context, tokenCredential azcore.TokenCredential) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ValidateServicePrincipal", arg0, arg1, arg2, arg3)
+	ret := m.ctrl.Call(m, "ValidateServicePrincipal", ctx, tokenCredential)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // ValidateServicePrincipal indicates an expected call of ValidateServicePrincipal.
-func (mr *MockDynamicMockRecorder) ValidateServicePrincipal(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+func (mr *MockDynamicMockRecorder) ValidateServicePrincipal(ctx, tokenCredential interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateServicePrincipal", reflect.TypeOf((*MockDynamic)(nil).ValidateServicePrincipal), arg0, arg1, arg2, arg3)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateServicePrincipal", reflect.TypeOf((*MockDynamic)(nil).ValidateServicePrincipal), ctx, tokenCredential)
 }
 
 // ValidateSubnets mocks base method.
-func (m *MockDynamic) ValidateSubnets(arg0 context.Context, arg1 *api.OpenShiftCluster, arg2 []dynamic.Subnet) error {
+func (m *MockDynamic) ValidateSubnets(ctx context.Context, oc *api.OpenShiftCluster, subnets []dynamic.Subnet) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ValidateSubnets", arg0, arg1, arg2)
+	ret := m.ctrl.Call(m, "ValidateSubnets", ctx, oc, subnets)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // ValidateSubnets indicates an expected call of ValidateSubnets.
-func (mr *MockDynamicMockRecorder) ValidateSubnets(arg0, arg1, arg2 interface{}) *gomock.Call {
+func (mr *MockDynamicMockRecorder) ValidateSubnets(ctx, oc, subnets interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateSubnets", reflect.TypeOf((*MockDynamic)(nil).ValidateSubnets), arg0, arg1, arg2)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateSubnets", reflect.TypeOf((*MockDynamic)(nil).ValidateSubnets), ctx, oc, subnets)
 }
 
 // ValidateVnet mocks base method.
-func (m *MockDynamic) ValidateVnet(arg0 context.Context, arg1 string, arg2 []dynamic.Subnet, arg3 ...string) error {
+func (m *MockDynamic) ValidateVnet(ctx context.Context, location string, subnets []dynamic.Subnet, additionalCIDRs ...string) error {
 	m.ctrl.T.Helper()
-	varargs := []interface{}{arg0, arg1, arg2}
-	for _, a := range arg3 {
+	varargs := []interface{}{ctx, location, subnets}
+	for _, a := range additionalCIDRs {
 		varargs = append(varargs, a)
 	}
 	ret := m.ctrl.Call(m, "ValidateVnet", varargs...)
@@ -106,8 +144,8 @@ func (m *MockDynamic) ValidateVnet(arg0 context.Context, arg1 string, arg2 []dyn
 }
 
 // ValidateVnet indicates an expected call of ValidateVnet.
-func (mr *MockDynamicMockRecorder) ValidateVnet(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
+func (mr *MockDynamicMockRecorder) ValidateVnet(ctx, location, subnets interface{}, additionalCIDRs ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
+	varargs := append([]interface{}{ctx, location, subnets}, additionalCIDRs...)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateVnet", reflect.TypeOf((*MockDynamic)(nil).ValidateVnet), varargs...)
 }
diff --git a/pkg/util/mocks/env/env.go b/pkg/util/mocks/env/env.go
index df79beb00..3d4d20f6d 100644
--- a/pkg/util/mocks/env/env.go
+++ b/pkg/util/mocks/env/env.go
@@ -21,7 +21,6 @@ import (
 	clientauthorizer "github.com/Azure/ARO-RP/pkg/util/clientauthorizer"
 	keyvault "github.com/Azure/ARO-RP/pkg/util/keyvault"
 	liveconfig "github.com/Azure/ARO-RP/pkg/util/liveconfig"
-	refreshable "github.com/Azure/ARO-RP/pkg/util/refreshable"
 )
 
 // MockCore is a mock of Core interface.
@@ -119,18 +118,23 @@ func (mr *MockCoreMockRecorder) NewLiveConfigManager(arg0 interface{}) *gomock.C
 }
 
 // NewMSIAuthorizer mocks base method.
-func (m *MockCore) NewMSIAuthorizer(arg0 env.MSIContext, arg1 string) (autorest.Authorizer, error) {
+func (m *MockCore) NewMSIAuthorizer(arg0 env.MSIContext, arg1 ...string) (autorest.Authorizer, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "NewMSIAuthorizer", arg0, arg1)
+	varargs := []interface{}{arg0}
+	for _, a := range arg1 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "NewMSIAuthorizer", varargs...)
 	ret0, _ := ret[0].(autorest.Authorizer)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // NewMSIAuthorizer indicates an expected call of NewMSIAuthorizer.
-func (mr *MockCoreMockRecorder) NewMSIAuthorizer(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockCoreMockRecorder) NewMSIAuthorizer(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewMSIAuthorizer", reflect.TypeOf((*MockCore)(nil).NewMSIAuthorizer), arg0, arg1)
+	varargs := append([]interface{}{arg0}, arg1...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewMSIAuthorizer", reflect.TypeOf((*MockCore)(nil).NewMSIAuthorizer), varargs...)
 }
 
 // ResourceGroup mocks base method.
@@ -383,7 +387,7 @@ func (mr *MockInterfaceMockRecorder) Domain() *gomock.Call {
 }
 
 // EnsureARMResourceGroupRoleAssignment mocks base method.
-func (m *MockInterface) EnsureARMResourceGroupRoleAssignment(arg0 context.Context, arg1 refreshable.Authorizer, arg2 string) error {
+func (m *MockInterface) EnsureARMResourceGroupRoleAssignment(arg0 context.Context, arg1 autorest.Authorizer, arg2 string) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "EnsureARMResourceGroupRoleAssignment", arg0, arg1, arg2)
 	ret0, _ := ret[0].(error)
@@ -411,18 +415,23 @@ func (mr *MockInterfaceMockRecorder) Environment() *gomock.Call {
 }
 
 // FPAuthorizer mocks base method.
-func (m *MockInterface) FPAuthorizer(arg0, arg1 string) (refreshable.Authorizer, error) {
+func (m *MockInterface) FPAuthorizer(arg0 string, arg1 ...string) (autorest.Authorizer, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "FPAuthorizer", arg0, arg1)
-	ret0, _ := ret[0].(refreshable.Authorizer)
+	varargs := []interface{}{arg0}
+	for _, a := range arg1 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "FPAuthorizer", varargs...)
+	ret0, _ := ret[0].(autorest.Authorizer)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // FPAuthorizer indicates an expected call of FPAuthorizer.
-func (mr *MockInterfaceMockRecorder) FPAuthorizer(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockInterfaceMockRecorder) FPAuthorizer(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FPAuthorizer", reflect.TypeOf((*MockInterface)(nil).FPAuthorizer), arg0, arg1)
+	varargs := append([]interface{}{arg0}, arg1...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FPAuthorizer", reflect.TypeOf((*MockInterface)(nil).FPAuthorizer), varargs...)
 }
 
 // FPClientID mocks base method.
@@ -597,18 +606,23 @@ func (mr *MockInterfaceMockRecorder) NewLiveConfigManager(arg0 interface{}) *gom
 }
 
 // NewMSIAuthorizer mocks base method.
-func (m *MockInterface) NewMSIAuthorizer(arg0 env.MSIContext, arg1 string) (autorest.Authorizer, error) {
+func (m *MockInterface) NewMSIAuthorizer(arg0 env.MSIContext, arg1 ...string) (autorest.Authorizer, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "NewMSIAuthorizer", arg0, arg1)
+	varargs := []interface{}{arg0}
+	for _, a := range arg1 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "NewMSIAuthorizer", varargs...)
 	ret0, _ := ret[0].(autorest.Authorizer)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // NewMSIAuthorizer indicates an expected call of NewMSIAuthorizer.
-func (mr *MockInterfaceMockRecorder) NewMSIAuthorizer(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockInterfaceMockRecorder) NewMSIAuthorizer(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewMSIAuthorizer", reflect.TypeOf((*MockInterface)(nil).NewMSIAuthorizer), arg0, arg1)
+	varargs := append([]interface{}{arg0}, arg1...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewMSIAuthorizer", reflect.TypeOf((*MockInterface)(nil).NewMSIAuthorizer), varargs...)
 }
 
 // ResourceGroup mocks base method.
diff --git a/pkg/util/mocks/instancemetadata/instancemetadata.go b/pkg/util/mocks/instancemetadata/instancemetadata.go
index d27bbf6dd..cfe0cc9d4 100644
--- a/pkg/util/mocks/instancemetadata/instancemetadata.go
+++ b/pkg/util/mocks/instancemetadata/instancemetadata.go
@@ -1,11 +1,10 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/Azure/ARO-RP/pkg/util/instancemetadata (interfaces: ServicePrincipalToken,InstanceMetadata)
+// Source: github.com/Azure/ARO-RP/pkg/util/instancemetadata (interfaces: InstanceMetadata)
 
 // Package mock_instancemetadata is a generated GoMock package.
 package mock_instancemetadata
 
 import (
-	context "context"
 	reflect "reflect"
 
 	gomock "github.com/golang/mock/gomock"
@@ -13,57 +12,6 @@ import (
 	azureclient "github.com/Azure/ARO-RP/pkg/util/azureclient"
 )
 
-// MockServicePrincipalToken is a mock of ServicePrincipalToken interface.
-type MockServicePrincipalToken struct {
-	ctrl     *gomock.Controller
-	recorder *MockServicePrincipalTokenMockRecorder
-}
-
-// MockServicePrincipalTokenMockRecorder is the mock recorder for MockServicePrincipalToken.
-type MockServicePrincipalTokenMockRecorder struct {
-	mock *MockServicePrincipalToken
-}
-
-// NewMockServicePrincipalToken creates a new mock instance.
-func NewMockServicePrincipalToken(ctrl *gomock.Controller) *MockServicePrincipalToken {
-	mock := &MockServicePrincipalToken{ctrl: ctrl}
-	mock.recorder = &MockServicePrincipalTokenMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockServicePrincipalToken) EXPECT() *MockServicePrincipalTokenMockRecorder {
-	return m.recorder
-}
-
-// OAuthToken mocks base method.
-func (m *MockServicePrincipalToken) OAuthToken() string {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "OAuthToken")
-	ret0, _ := ret[0].(string)
-	return ret0
-}
-
-// OAuthToken indicates an expected call of OAuthToken.
-func (mr *MockServicePrincipalTokenMockRecorder) OAuthToken() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OAuthToken", reflect.TypeOf((*MockServicePrincipalToken)(nil).OAuthToken))
-}
-
-// RefreshWithContext mocks base method.
-func (m *MockServicePrincipalToken) RefreshWithContext(arg0 context.Context) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "RefreshWithContext", arg0)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// RefreshWithContext indicates an expected call of RefreshWithContext.
-func (mr *MockServicePrincipalTokenMockRecorder) RefreshWithContext(arg0 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshWithContext", reflect.TypeOf((*MockServicePrincipalToken)(nil).RefreshWithContext), arg0)
-}
-
 // MockInstanceMetadata is a mock of InstanceMetadata interface.
 type MockInstanceMetadata struct {
 	ctrl     *gomock.Controller
diff --git a/pkg/util/purge/purge.go b/pkg/util/purge/purge.go
index 953208c37..886493c86 100644
--- a/pkg/util/purge/purge.go
+++ b/pkg/util/purge/purge.go
@@ -6,8 +6,9 @@ package purge
 // all the purge functions are located here
 
 import (
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features"
-	"github.com/Azure/go-autorest/autorest/azure/auth"
+	"github.com/jongio/azidext/go/azidext"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/env"
@@ -35,11 +36,15 @@ type ResourceCleaner struct {
 
 // NewResourceCleaner instantiates the new RC object
 func NewResourceCleaner(log *logrus.Entry, env env.Core, shouldDelete checkFn, dryRun bool) (*ResourceCleaner, error) {
-	authorizer, err := auth.NewAuthorizerFromEnvironment()
+	options := env.Environment().EnvironmentCredentialOptions()
+	tokenCredential, err := azidentity.NewEnvironmentCredential(options)
 	if err != nil {
 		return nil, err
 	}
 
+	scopes := []string{env.Environment().ResourceManagerScope}
+	authorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
+
 	return &ResourceCleaner{
 		log:    log,
 		dryRun: dryRun,
diff --git a/pkg/util/refreshable/generate.go b/pkg/util/refreshable/generate.go
deleted file mode 100644
index c96dafa2a..000000000
--- a/pkg/util/refreshable/generate.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package refreshable
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-//go:generate rm -rf ../mocks/$GOPACKAGE
-//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/$GOPACKAGE Authorizer
-//go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../mocks/$GOPACKAGE/$GOPACKAGE.go
diff --git a/pkg/util/refreshable/refreshable.go b/pkg/util/refreshable/refreshable.go
deleted file mode 100644
index 3fba37725..000000000
--- a/pkg/util/refreshable/refreshable.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package refreshable
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-
-	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/adal"
-	"github.com/sirupsen/logrus"
-
-	"github.com/Azure/ARO-RP/pkg/util/azureerrors"
-)
-
-type Authorizer interface {
-	autorest.Authorizer
-	RefreshWithContext(context.Context, *logrus.Entry) (bool, error)
-	LastError() error
-	OAuthToken() string
-}
-
-type authorizer struct {
-	autorest.Authorizer
-	sp        *adal.ServicePrincipalToken
-	lastError error
-}
-
-// RefreshWithContext attempts to refresh a service principal token.  It should
-// be called from within a wait.Poll* loop and its return values match
-// accordingly.  It requests a retry in the cases below.  Unfortunately there
-// doesn't seem to be a way to distinguish whether these cases occur due to
-// misconfiguration or AAD propagation delays.
-//
-// 1. `{"error": "unauthorized_client", "error_description": "AADSTS700016:
-// Application with identifier 'xxx' was not found in the directory 'xxx'. This
-// can happen if the application has not been installed by the administrator of
-// the tenant or consented to by any user in the tenant. You may have sent your
-// authentication request to the wrong tenant. ...", "error_codes": [700016]}`.
-// This can be an indicator of AAD propagation delay.
-//
-// 2. Lack of an altsecid, puid or oid claim in the token.  Continuing would
-// subsequently cause the ARM error `Code="InvalidAuthenticationToken"
-// Message="The received access token is not valid: at least one of the claims
-// 'puid' or 'altsecid' or 'oid' should be present. If you are accessing as an
-// application please make sure service principal is properly created in the
-// tenant."`.  I think this can be returned when the service principal
-// associated with the application hasn't yet caught up with the application
-// itself.
-//
-// 3. Network failures.  If the error is not an adal.TokenRefreshError, then
-// it's likely a transient failure. For example, connection reset by peer.
-//
-// 4. If credentials are just created, they might fail with `adal.tokenRefreshError)
-// adal: Refresh request failed. Status Code = '401'.  Response body:
-// {"error":"invalid_client","error_description":"AADSTS7000215: Invalid client secret is provided.`
-// Once aad starts propagating credentials you might see occasional success in authentication.
-func (a *authorizer) RefreshWithContext(ctx context.Context, log *logrus.Entry) (bool, error) {
-	a.lastError = a.sp.RefreshWithContext(ctx)
-	if a.lastError == nil {
-		return true, nil
-	}
-
-	log.Info(a.lastError)
-
-	if !autorest.IsTokenRefreshError(a.lastError) ||
-		azureerrors.IsUnauthorizedClientError(a.lastError) ||
-		azureerrors.IsInvalidSecretError(a.lastError) {
-		return false, nil
-	}
-	return false, a.lastError
-}
-
-func (a *authorizer) LastError() error {
-	return a.lastError
-}
-
-func (a *authorizer) OAuthToken() string {
-	return a.sp.OAuthToken()
-}
-
-func NewAuthorizer(sp *adal.ServicePrincipalToken) Authorizer {
-	return &authorizer{
-		Authorizer: autorest.NewBearerAuthorizer(sp),
-		sp:         sp,
-	}
-}
diff --git a/pkg/util/steps/refreshing.go b/pkg/util/steps/refreshing.go
deleted file mode 100644
index 3e69fc4a6..000000000
--- a/pkg/util/steps/refreshing.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package steps
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"strings"
-	"time"
-
-	"github.com/sirupsen/logrus"
-	"k8s.io/apimachinery/pkg/util/wait"
-
-	"github.com/Azure/ARO-RP/pkg/util/azureerrors"
-	"github.com/Azure/ARO-RP/pkg/util/refreshable"
-)
-
-var ErrWantRefresh = errors.New("want refresh")
-
-// AuthorizationRefreshingAction returns a wrapper Step which will refresh
-// `authorizer` if the step returns an Azure AuthenticationError and rerun it.
-// The step will be retried until `retryTimeout` is hit. Any other error will be
-// returned directly.
-func AuthorizationRefreshingAction(authorizer refreshable.Authorizer, step Step) Step {
-	return authorizationRefreshingActionStep{
-		step:       step,
-		authorizer: authorizer,
-	}
-}
-
-type authorizationRefreshingActionStep struct {
-	step         Step
-	authorizer   refreshable.Authorizer
-	retryTimeout time.Duration
-	pollInterval time.Duration
-}
-
-func (s authorizationRefreshingActionStep) run(ctx context.Context, log *logrus.Entry) error {
-	var pollInterval time.Duration
-	var retryTimeout time.Duration
-
-	// If no pollInterval has been set, use a default
-	if s.retryTimeout == time.Duration(0) {
-		retryTimeout = 10 * time.Minute
-	} else {
-		retryTimeout = s.retryTimeout
-	}
-
-	// If no pollInterval has been set, use a default
-	if s.pollInterval == time.Duration(0) {
-		pollInterval = 10 * time.Second
-	} else {
-		pollInterval = s.pollInterval
-	}
-
-	timeoutCtx, cancel := context.WithTimeout(ctx, retryTimeout)
-	defer cancel()
-
-	// Run the step immediately. If an Azure authorization error is returned and
-	// we have not hit the retry timeout, the authorizer is refreshed and the
-	// step is called again after runner.pollInterval. If we have timed out or
-	// any other error is returned, the error from the step is returned
-	// directly.
-	return wait.PollImmediateUntil(pollInterval, func() (bool, error) {
-		// We use the outer context, not the timeout context, as we do not want
-		// to time out the condition function itself, only stop retrying once
-		// timeoutCtx's timeout has fired.
-		err := s.step.run(ctx, log)
-
-		// Don't refresh if we have timed out
-		if timeoutCtx.Err() == nil &&
-			(azureerrors.HasAuthorizationFailedError(err) ||
-				azureerrors.HasLinkedAuthorizationFailedError(err) ||
-				err == ErrWantRefresh) {
-			log.Print(err)
-			// Try refreshing auth.
-			if s.authorizer == nil {
-				return false, nil // retry step
-			}
-			_, err = s.authorizer.RefreshWithContext(ctx, log)
-			return false, err // retry step
-		}
-		return true, err
-	}, timeoutCtx.Done())
-}
-
-func (s authorizationRefreshingActionStep) String() string {
-	return fmt.Sprintf("[AuthorizationRefreshingAction %s]", s.step)
-}
-
-func (s authorizationRefreshingActionStep) metricsName() string {
-	trimedName := strings.ReplaceAll(strings.ReplaceAll(s.step.String(), "[", ""), "]", "")
-	return fmt.Sprintf("refreshing.%s", shortName(trimedName))
-}
diff --git a/portal/v1/package-lock.json b/portal/v1/package-lock.json
index c63a17dd7..0754f44ab 100644
--- a/portal/v1/package-lock.json
+++ b/portal/v1/package-lock.json
@@ -22,7 +22,7 @@
                 "html-webpack-plugin": "^5.3.1",
                 "mini-css-extract-plugin": "^2.6.1",
                 "style-loader": "^3.3.1",
-                "webpack": "^5.74.0",
+                "webpack": "^5.76.0",
                 "webpack-cli": "^4.10.0",
                 "webpack-dev-server": "^4.11.0",
                 "webpack-merge": "^5.7.3"
@@ -4809,9 +4809,9 @@
             }
         },
         "node_modules/webpack": {
-            "version": "5.75.0",
-            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
-            "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
+            "version": "5.76.0",
+            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz",
+            "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==",
             "dev": true,
             "dependencies": {
                 "@types/eslint-scope": "^3.7.3",
@@ -8694,9 +8694,9 @@
             }
         },
         "webpack": {
-            "version": "5.75.0",
-            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
-            "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
+            "version": "5.76.0",
+            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz",
+            "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==",
             "dev": true,
             "requires": {
                 "@types/eslint-scope": "^3.7.3",
diff --git a/portal/v1/package.json b/portal/v1/package.json
index e9b2844ff..2f92acfa3 100644
--- a/portal/v1/package.json
+++ b/portal/v1/package.json
@@ -23,7 +23,7 @@
         "html-webpack-plugin": "^5.3.1",
         "mini-css-extract-plugin": "^2.6.1",
         "style-loader": "^3.3.1",
-        "webpack": "^5.74.0",
+        "webpack": "^5.76.0",
         "webpack-cli": "^4.10.0",
         "webpack-dev-server": "^4.11.0",
         "webpack-merge": "^5.7.3"
diff --git a/portal/v2/package-lock.json b/portal/v2/package-lock.json
index 2d9fdd955..63bdde8f8 100644
--- a/portal/v2/package-lock.json
+++ b/portal/v2/package-lock.json
@@ -17266,9 +17266,9 @@
       }
     },
     "node_modules/webpack": {
-      "version": "5.75.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
-      "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
+      "version": "5.77.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.77.0.tgz",
+      "integrity": "sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q==",
       "dev": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.3",
@@ -30669,9 +30669,9 @@
       "dev": true
     },
     "webpack": {
-      "version": "5.75.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
-      "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
+      "version": "5.77.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.77.0.tgz",
+      "integrity": "sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q==",
       "dev": true,
       "requires": {
         "@types/eslint-scope": "^3.7.3",
diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/README.md b/vendor/github.com/Azure/go-autorest/autorest/adal/README.md
index fec416a9c..b11eb0788 100644
--- a/vendor/github.com/Azure/go-autorest/autorest/adal/README.md
+++ b/vendor/github.com/Azure/go-autorest/autorest/adal/README.md
@@ -1,3 +1,5 @@
+# NOTE: This module will go out of support by March 31, 2023.  For authenticating with Azure AD, use module [azidentity](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity) instead.  For help migrating from `adal` to `azidentiy` please consult the [migration guide](https://aka.ms/azsdk/go/identity/migration).  General information about the retirement of this and other legacy modules can be found [here](https://azure.microsoft.com/updates/support-for-azure-sdk-libraries-that-do-not-conform-to-our-current-azure-sdk-guidelines-will-be-retired-as-of-31-march-2023/).
+
 # Azure Active Directory authentication for Go
 
 This is a standalone package for authenticating with Azure Active
@@ -18,7 +20,7 @@ go get -u github.com/Azure/go-autorest/autorest/adal
 
 ## Usage
 
-An Active Directory application is required in order to use this library. An application can be registered in the [Azure Portal](https://portal.azure.com/) by following these [guidelines](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-integrating-applications) or using the [Azure CLI](https://github.com/Azure/azure-cli).
+An Active Directory application is required in order to use this library. An application can be registered in the [Azure Portal](https://portal.azure.com/) by following these [guidelines](https://docs.microsoft.com/azure/active-directory/develop/active-directory-integrating-applications) or using the [Azure CLI](https://github.com/Azure/azure-cli).
 
 ### Register an Azure AD Application with secret
 
@@ -88,7 +90,7 @@ An Active Directory application is required in order to use this library. An app
 ### Grant the necessary permissions
 
 Azure relies on a Role-Based Access Control (RBAC) model to manage the access to resources at a fine-grained
-level. There is a set of [pre-defined roles](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-built-in-roles)
+level. There is a set of [pre-defined roles](https://docs.microsoft.com/azure/active-directory/role-based-access-built-in-roles)
 which can be assigned to a service principal of an Azure AD application depending of your needs.
 
 ```
@@ -104,7 +106,7 @@ It is also possible to define custom role definitions.
 az role definition create --role-definition role-definition.json
 ```
 
-* Check [custom roles](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-control-custom-roles) for more details regarding the content of `role-definition.json` file.
+* Check [custom roles](https://docs.microsoft.com/azure/active-directory/role-based-access-control-custom-roles) for more details regarding the content of `role-definition.json` file.
 
 
 ### Acquire Access Token
diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/token.go b/vendor/github.com/Azure/go-autorest/autorest/adal/token.go
index 310be07ec..1a9c8ab53 100644
--- a/vendor/github.com/Azure/go-autorest/autorest/adal/token.go
+++ b/vendor/github.com/Azure/go-autorest/autorest/adal/token.go
@@ -365,6 +365,25 @@ func (secret ServicePrincipalAuthorizationCodeSecret) MarshalJSON() ([]byte, err
 	})
 }
 
+// ServicePrincipalFederatedSecret implements ServicePrincipalSecret for Federated JWTs.
+type ServicePrincipalFederatedSecret struct {
+	jwt string
+}
+
+// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
+// It will populate the form submitted during OAuth Token Acquisition using a JWT signed by an OIDC issuer.
+func (secret *ServicePrincipalFederatedSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
+
+	v.Set("client_assertion", secret.jwt)
+	v.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
+	return nil
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (secret ServicePrincipalFederatedSecret) MarshalJSON() ([]byte, error) {
+	return nil, errors.New("marshalling ServicePrincipalFederatedSecret is not supported")
+}
+
 // ServicePrincipalToken encapsulates a Token created for a Service Principal.
 type ServicePrincipalToken struct {
 	inner             servicePrincipalToken
@@ -419,6 +438,8 @@ func (spt *ServicePrincipalToken) UnmarshalJSON(data []byte) error {
 		spt.inner.Secret = &ServicePrincipalUsernamePasswordSecret{}
 	case "ServicePrincipalAuthorizationCodeSecret":
 		spt.inner.Secret = &ServicePrincipalAuthorizationCodeSecret{}
+	case "ServicePrincipalFederatedSecret":
+		return errors.New("unmarshalling ServicePrincipalFederatedSecret is not supported")
 	default:
 		return fmt.Errorf("unrecognized token type '%s'", secret["type"])
 	}
@@ -665,6 +686,31 @@ func NewServicePrincipalTokenFromAuthorizationCode(oauthConfig OAuthConfig, clie
 	)
 }
 
+// NewServicePrincipalTokenFromFederatedToken creates a ServicePrincipalToken from the supplied federated OIDC JWT.
+func NewServicePrincipalTokenFromFederatedToken(oauthConfig OAuthConfig, clientID string, jwt string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
+	if err := validateOAuthConfig(oauthConfig); err != nil {
+		return nil, err
+	}
+	if err := validateStringParam(clientID, "clientID"); err != nil {
+		return nil, err
+	}
+	if err := validateStringParam(resource, "resource"); err != nil {
+		return nil, err
+	}
+	if jwt == "" {
+		return nil, fmt.Errorf("parameter 'jwt' cannot be empty")
+	}
+	return NewServicePrincipalTokenWithSecret(
+		oauthConfig,
+		clientID,
+		resource,
+		&ServicePrincipalFederatedSecret{
+			jwt: jwt,
+		},
+		callbacks...,
+	)
+}
+
 type msiType int
 
 const (
@@ -1058,8 +1104,8 @@ func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource
 
 		// AAD returns expires_in as a string, ADFS returns it as an int
 		ExpiresIn json.Number `json:"expires_in"`
-		// expires_on can be in two formats, a UTC time stamp or the number of seconds.
-		ExpiresOn string      `json:"expires_on"`
+		// expires_on can be in three formats, a UTC time stamp, or the number of seconds as a string *or* int.
+		ExpiresOn interface{} `json:"expires_on"`
 		NotBefore json.Number `json:"not_before"`
 
 		Resource string `json:"resource"`
@@ -1072,7 +1118,7 @@ func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource
 	}
 	expiresOn := json.Number("")
 	// ADFS doesn't include the expires_on field
-	if token.ExpiresOn != "" {
+	if token.ExpiresOn != nil {
 		if expiresOn, err = parseExpiresOn(token.ExpiresOn); err != nil {
 			return newTokenRefreshError(fmt.Sprintf("adal: failed to parse expires_on: %v value '%s'", err, token.ExpiresOn), resp)
 		}
@@ -1089,18 +1135,27 @@ func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource
 }
 
 // converts expires_on to the number of seconds
-func parseExpiresOn(s string) (json.Number, error) {
-	// convert the expiration date to the number of seconds from now
+func parseExpiresOn(s interface{}) (json.Number, error) {
+	// the JSON unmarshaler treats JSON numbers unmarshaled into an interface{} as float64
+	asFloat64, ok := s.(float64)
+	if ok {
+		// this is the number of seconds as int case
+		return json.Number(strconv.FormatInt(int64(asFloat64), 10)), nil
+	}
+	asStr, ok := s.(string)
+	if !ok {
+		return "", fmt.Errorf("unexpected expires_on type %T", s)
+	}
+	// convert the expiration date to the number of seconds from the unix epoch
 	timeToDuration := func(t time.Time) json.Number {
-		dur := t.Sub(time.Now().UTC())
-		return json.Number(strconv.FormatInt(int64(dur.Round(time.Second).Seconds()), 10))
+		return json.Number(strconv.FormatInt(t.UTC().Unix(), 10))
 	}
-	if _, err := strconv.ParseInt(s, 10, 64); err == nil {
+	if _, err := json.Number(asStr).Int64(); err == nil {
 		// this is the number of seconds case, no conversion required
-		return json.Number(s), nil
-	} else if eo, err := time.Parse(expiresOnDateFormatPM, s); err == nil {
+		return json.Number(asStr), nil
+	} else if eo, err := time.Parse(expiresOnDateFormatPM, asStr); err == nil {
 		return timeToDuration(eo), nil
-	} else if eo, err := time.Parse(expiresOnDateFormat, s); err == nil {
+	} else if eo, err := time.Parse(expiresOnDateFormat, asStr); err == nil {
 		return timeToDuration(eo), nil
 	} else {
 		// unknown format
@@ -1317,12 +1372,25 @@ func NewMultiTenantServicePrincipalTokenFromCertificate(multiTenantCfg MultiTena
 
 // MSIAvailable returns true if the MSI endpoint is available for authentication.
 func MSIAvailable(ctx context.Context, s Sender) bool {
+	msiType, _, err := getMSIType()
+
+	if err != nil {
+		return false
+	}
+
+	if msiType != msiTypeIMDS {
+		return true
+	}
+
 	if s == nil {
 		s = sender()
 	}
+
 	resp, err := getMSIEndpoint(ctx, s)
+
 	if err == nil {
 		resp.Body.Close()
 	}
+
 	return err == nil
 }
diff --git a/vendor/github.com/Azure/go-autorest/autorest/azure/environments.go b/vendor/github.com/Azure/go-autorest/autorest/azure/environments.go
index e19ea7556..b0a53769f 100644
--- a/vendor/github.com/Azure/go-autorest/autorest/azure/environments.go
+++ b/vendor/github.com/Azure/go-autorest/autorest/azure/environments.go
@@ -34,8 +34,10 @@ const (
 var environments = map[string]Environment{
 	"AZURECHINACLOUD":        ChinaCloud,
 	"AZUREGERMANCLOUD":       GermanCloud,
+	"AZURECLOUD":             PublicCloud,
 	"AZUREPUBLICCLOUD":       PublicCloud,
-	"AZUREUSGOVERNMENTCLOUD": USGovernmentCloud,
+	"AZUREUSGOVERNMENT":      USGovernmentCloud,
+	"AZUREUSGOVERNMENTCLOUD": USGovernmentCloud, //TODO: deprecate
 }
 
 // ResourceIdentifier contains a set of Azure resource IDs.
@@ -52,6 +54,7 @@ type ResourceIdentifier struct {
 	SQLDatabase         string `json:"sqlDatabase"`
 	CosmosDB            string `json:"cosmosDB"`
 	ManagedHSM          string `json:"managedHSM"`
+	MicrosoftGraph      string `json:"microsoftGraph"`
 }
 
 // Environment represents a set of endpoints for each of Azure's Clouds.
@@ -68,6 +71,7 @@ type Environment struct {
 	GraphEndpoint                string             `json:"graphEndpoint"`
 	ServiceBusEndpoint           string             `json:"serviceBusEndpoint"`
 	BatchManagementEndpoint      string             `json:"batchManagementEndpoint"`
+	MicrosoftGraphEndpoint       string             `json:"microsoftGraphEndpoint"`
 	StorageEndpointSuffix        string             `json:"storageEndpointSuffix"`
 	CosmosDBDNSSuffix            string             `json:"cosmosDBDNSSuffix"`
 	MariaDBDNSSuffix             string             `json:"mariaDBDNSSuffix"`
@@ -84,6 +88,7 @@ type Environment struct {
 	TokenAudience                string             `json:"tokenAudience"`
 	APIManagementHostNameSuffix  string             `json:"apiManagementHostNameSuffix"`
 	SynapseEndpointSuffix        string             `json:"synapseEndpointSuffix"`
+	DatalakeSuffix               string             `json:"datalakeSuffix"`
 	ResourceIdentifiers          ResourceIdentifier `json:"resourceIdentifiers"`
 }
 
@@ -102,6 +107,7 @@ var (
 		GraphEndpoint:                "https://graph.windows.net/",
 		ServiceBusEndpoint:           "https://servicebus.windows.net/",
 		BatchManagementEndpoint:      "https://batch.core.windows.net/",
+		MicrosoftGraphEndpoint:       "https://graph.microsoft.com/",
 		StorageEndpointSuffix:        "core.windows.net",
 		CosmosDBDNSSuffix:            "documents.azure.com",
 		MariaDBDNSSuffix:             "mariadb.database.azure.com",
@@ -118,6 +124,7 @@ var (
 		TokenAudience:                "https://management.azure.com/",
 		APIManagementHostNameSuffix:  "azure-api.net",
 		SynapseEndpointSuffix:        "dev.azuresynapse.net",
+		DatalakeSuffix:               "azuredatalakestore.net",
 		ResourceIdentifiers: ResourceIdentifier{
 			Graph:               "https://graph.windows.net/",
 			KeyVault:            "https://vault.azure.net",
@@ -131,6 +138,7 @@ var (
 			SQLDatabase:         "https://database.windows.net/",
 			CosmosDB:            "https://cosmos.azure.com",
 			ManagedHSM:          "https://managedhsm.azure.net",
+			MicrosoftGraph:      "https://graph.microsoft.com/",
 		},
 	}
 
@@ -148,6 +156,7 @@ var (
 		GraphEndpoint:                "https://graph.windows.net/",
 		ServiceBusEndpoint:           "https://servicebus.usgovcloudapi.net/",
 		BatchManagementEndpoint:      "https://batch.core.usgovcloudapi.net/",
+		MicrosoftGraphEndpoint:       "https://graph.microsoft.us/",
 		StorageEndpointSuffix:        "core.usgovcloudapi.net",
 		CosmosDBDNSSuffix:            "documents.azure.us",
 		MariaDBDNSSuffix:             "mariadb.database.usgovcloudapi.net",
@@ -163,7 +172,8 @@ var (
 		ContainerRegistryDNSSuffix:   "azurecr.us",
 		TokenAudience:                "https://management.usgovcloudapi.net/",
 		APIManagementHostNameSuffix:  "azure-api.us",
-		SynapseEndpointSuffix:        NotAvailable,
+		SynapseEndpointSuffix:        "dev.azuresynapse.usgovcloudapi.net",
+		DatalakeSuffix:               NotAvailable,
 		ResourceIdentifiers: ResourceIdentifier{
 			Graph:               "https://graph.windows.net/",
 			KeyVault:            "https://vault.usgovcloudapi.net",
@@ -172,11 +182,12 @@ var (
 			OperationalInsights: "https://api.loganalytics.us",
 			OSSRDBMS:            "https://ossrdbms-aad.database.usgovcloudapi.net",
 			Storage:             "https://storage.azure.com/",
-			Synapse:             NotAvailable,
+			Synapse:             "https://dev.azuresynapse.usgovcloudapi.net",
 			ServiceBus:          "https://servicebus.azure.net/",
 			SQLDatabase:         "https://database.usgovcloudapi.net/",
 			CosmosDB:            "https://cosmos.azure.com",
 			ManagedHSM:          NotAvailable,
+			MicrosoftGraph:      "https://graph.microsoft.us/",
 		},
 	}
 
@@ -194,6 +205,7 @@ var (
 		GraphEndpoint:                "https://graph.chinacloudapi.cn/",
 		ServiceBusEndpoint:           "https://servicebus.chinacloudapi.cn/",
 		BatchManagementEndpoint:      "https://batch.chinacloudapi.cn/",
+		MicrosoftGraphEndpoint:       "https://microsoftgraph.chinacloudapi.cn/",
 		StorageEndpointSuffix:        "core.chinacloudapi.cn",
 		CosmosDBDNSSuffix:            "documents.azure.cn",
 		MariaDBDNSSuffix:             "mariadb.database.chinacloudapi.cn",
@@ -210,6 +222,7 @@ var (
 		TokenAudience:                "https://management.chinacloudapi.cn/",
 		APIManagementHostNameSuffix:  "azure-api.cn",
 		SynapseEndpointSuffix:        "dev.azuresynapse.azure.cn",
+		DatalakeSuffix:               NotAvailable,
 		ResourceIdentifiers: ResourceIdentifier{
 			Graph:               "https://graph.chinacloudapi.cn/",
 			KeyVault:            "https://vault.azure.cn",
@@ -223,6 +236,7 @@ var (
 			SQLDatabase:         "https://database.chinacloudapi.cn/",
 			CosmosDB:            "https://cosmos.azure.com",
 			ManagedHSM:          NotAvailable,
+			MicrosoftGraph:      "https://microsoftgraph.chinacloudapi.cn",
 		},
 	}
 
@@ -240,6 +254,7 @@ var (
 		GraphEndpoint:                "https://graph.cloudapi.de/",
 		ServiceBusEndpoint:           "https://servicebus.cloudapi.de/",
 		BatchManagementEndpoint:      "https://batch.cloudapi.de/",
+		MicrosoftGraphEndpoint:       NotAvailable,
 		StorageEndpointSuffix:        "core.cloudapi.de",
 		CosmosDBDNSSuffix:            "documents.microsoftazure.de",
 		MariaDBDNSSuffix:             "mariadb.database.cloudapi.de",
@@ -256,6 +271,7 @@ var (
 		TokenAudience:                "https://management.microsoftazure.de/",
 		APIManagementHostNameSuffix:  NotAvailable,
 		SynapseEndpointSuffix:        NotAvailable,
+		DatalakeSuffix:               NotAvailable,
 		ResourceIdentifiers: ResourceIdentifier{
 			Graph:               "https://graph.cloudapi.de/",
 			KeyVault:            "https://vault.microsoftazure.de",
@@ -269,6 +285,7 @@ var (
 			SQLDatabase:         "https://database.cloudapi.de/",
 			CosmosDB:            "https://cosmos.azure.com",
 			ManagedHSM:          NotAvailable,
+			MicrosoftGraph:      NotAvailable,
 		},
 	}
 )
diff --git a/vendor/github.com/jongio/azidext/go/azidext/LICENSE b/vendor/github.com/jongio/azidext/go/azidext/LICENSE
new file mode 100644
index 000000000..21cbc94a6
--- /dev/null
+++ b/vendor/github.com/jongio/azidext/go/azidext/LICENSE
@@ -0,0 +1,7 @@
+Copyright 2020 Jon Gallant
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/vendor/github.com/jongio/azidext/go/azidext/azure_identity_credential_adapter.go b/vendor/github.com/jongio/azidext/go/azidext/azure_identity_credential_adapter.go
new file mode 100644
index 000000000..553b95a27
--- /dev/null
+++ b/vendor/github.com/jongio/azidext/go/azidext/azure_identity_credential_adapter.go
@@ -0,0 +1,118 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package azidext
+
+import (
+	"errors"
+	"net/http"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	"github.com/Azure/go-autorest/autorest"
+)
+
+// NewTokenCredentialAdapter is used to adapt an azcore.TokenCredential to an autorest.Authorizer
+func NewTokenCredentialAdapter(credential azcore.TokenCredential, scopes []string) autorest.Authorizer {
+	tkPolicy := runtime.NewBearerTokenPolicy(credential, scopes, nil)
+	return &policyAdapter{
+		pl: runtime.NewPipeline("azidext", "v0.4.0", runtime.PipelineOptions{
+			PerRetry: []policy.Policy{tkPolicy, nullPolicy{}},
+		}, nil),
+	}
+}
+
+type policyAdapter struct {
+	pl runtime.Pipeline
+}
+
+// WithAuthorization implements the autorest.Authorizer interface for type policyAdapter.
+func (ca *policyAdapter) WithAuthorization() autorest.PrepareDecorator {
+	return func(p autorest.Preparer) autorest.Preparer {
+		return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
+			r, err := p.Prepare(r)
+			if err != nil {
+				return r, err
+			}
+			// create a dummy request
+			req, err := runtime.NewRequest(r.Context(), r.Method, r.URL.String())
+			if err != nil {
+				return r, err
+			}
+			_, err = ca.pl.Do(req)
+			// if the authentication failed due to invalid/missing credentials
+			// return a wrapped error so the retry policy won't kick in.
+			type nonRetriable interface {
+				NonRetriable()
+			}
+			var nre nonRetriable
+			if errors.As(err, &nre) {
+				return r, &tokenRefreshError{
+					inner: err,
+				}
+			}
+			// some other error
+			if err != nil {
+				return r, err
+			}
+			// copy the authorization header to the real request
+			const authHeader = "Authorization"
+			r.Header.Set(authHeader, req.Raw().Header.Get(authHeader))
+			return r, err
+		})
+	}
+}
+
+// DefaultManagementScope is the default credential scope for Azure Resource Management.
+const DefaultManagementScope = "https://management.azure.com//.default"
+
+// DefaultAzureCredentialOptions contains credential and authentication policy options.
+type DefaultAzureCredentialOptions struct {
+	// DefaultCredential contains configuration options passed to azidentity.NewDefaultAzureCredential().
+	// Set this to nil to accept the underlying default behavior.
+	DefaultCredential *azidentity.DefaultAzureCredentialOptions
+
+	// Scopes contains the list of permission scopes required for the token.
+	// Setting this to nil will use the DefaultManagementScope when acquiring a token.
+	Scopes []string
+}
+
+// NewDefaultAzureCredentialAdapter adapts azcore.NewDefaultAzureCredential to an autorest.Authorizer.
+func NewDefaultAzureCredentialAdapter(options *DefaultAzureCredentialOptions) (autorest.Authorizer, error) {
+	if options == nil {
+		options = &DefaultAzureCredentialOptions{
+			Scopes: []string{DefaultManagementScope},
+		}
+	}
+	cred, err := azidentity.NewDefaultAzureCredential(options.DefaultCredential)
+	if err != nil {
+		return nil, err
+	}
+	return NewTokenCredentialAdapter(cred, options.Scopes), nil
+}
+
+// dummy policy to terminate the pipeline
+type nullPolicy struct{}
+
+func (nullPolicy) Do(req *policy.Request) (*http.Response, error) {
+	return &http.Response{StatusCode: http.StatusOK}, nil
+}
+
+// error type returned to prevent the retry policy from retrying the request
+type tokenRefreshError struct {
+	inner error
+}
+
+func (t *tokenRefreshError) Error() string {
+	return t.inner.Error()
+}
+
+func (t *tokenRefreshError) Response() *http.Response {
+	return nil
+}
+
+func (t *tokenRefreshError) Unwrap() error {
+	return t.inner
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 8d96dc54c..ea630ee6b 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -70,11 +70,11 @@ github.com/Azure/go-ansiterm/winterm
 # github.com/Azure/go-autorest v14.2.0+incompatible
 ## explicit
 github.com/Azure/go-autorest
-# github.com/Azure/go-autorest/autorest v0.11.25
+# github.com/Azure/go-autorest/autorest v0.11.27
 ## explicit; go 1.15
 github.com/Azure/go-autorest/autorest
 github.com/Azure/go-autorest/autorest/azure
-# github.com/Azure/go-autorest/autorest/adal v0.9.18
+# github.com/Azure/go-autorest/autorest/adal v0.9.20
 ## explicit; go 1.15
 github.com/Azure/go-autorest/autorest/adal
 # github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
@@ -399,7 +399,7 @@ github.com/docker/distribution/registry/client/auth/challenge
 github.com/docker/distribution/registry/client/transport
 github.com/docker/distribution/registry/storage/cache
 github.com/docker/distribution/registry/storage/cache/memory
-# github.com/docker/docker v20.10.14+incompatible
+# github.com/docker/docker v20.10.24+incompatible
 ## explicit
 github.com/docker/docker/api/types/versions
 # github.com/docker/docker-credential-helpers v0.6.4
@@ -631,6 +631,9 @@ github.com/jewzaam/go-cosmosdb/pkg/gencosmosdb
 # github.com/jmespath/go-jmespath v0.4.0
 ## explicit; go 1.14
 github.com/jmespath/go-jmespath
+# github.com/jongio/azidext/go/azidext v0.4.0
+## explicit; go 1.18
+github.com/jongio/azidext/go/azidext
 # github.com/josharian/intern v1.0.0
 ## explicit; go 1.5
 github.com/josharian/intern


Test patch:
diff --git a/.pipelines/e2e.yml b/.pipelines/e2e.yml
index 687bdf5a5..bd91967a7 100644
--- a/.pipelines/e2e.yml
+++ b/.pipelines/e2e.yml
@@ -110,6 +110,7 @@ jobs:
       . ./hack/e2e/run-rp-and-e2e.sh
       clean_e2e_db
     displayName: Cleanup (Azure)
+    condition: always()
   - template: ./templates/template-az-cli-logout.yml
 
   - task: PublishTestResults@2
diff --git a/pkg/api/validate/dynamic/serviceprincipal_test.go b/pkg/api/validate/dynamic/serviceprincipal_test.go
index b72b60b74..70e895472 100644
--- a/pkg/api/validate/dynamic/serviceprincipal_test.go
+++ b/pkg/api/validate/dynamic/serviceprincipal_test.go
@@ -5,34 +5,36 @@ package dynamic
 
 import (
 	"context"
-	"crypto/hmac"
-	"crypto/sha512"
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
-	"math/rand"
-	"net/url"
 	"testing"
 	"time"
 
-	"github.com/Azure/go-autorest/autorest/adal"
-	"github.com/golang/mock/gomock"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	jwt "github.com/golang-jwt/jwt/v4"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
-	mock_aad "github.com/Azure/ARO-RP/pkg/util/mocks/aad"
 	utilerror "github.com/Azure/ARO-RP/test/util/error"
 )
 
 type tokenRequirements struct {
-	clientID      string
 	clientSecret  string
-	tenantID      string
-	aadEndpoint   string
-	graphEndpoint string
-	resource      string
-	claims        string
-	signMethod    string
+	claims        jwt.MapClaims
+	signingMethod jwt.SigningMethod
+}
+
+type signingMethodFake struct{}
+
+func (m signingMethodFake) Verify(signingString, signature string, key interface{}) error {
+	return nil
+}
+
+func (m signingMethodFake) Sign(signingString string, key interface{}) (string, error) {
+	return "", nil
+}
+
+func (m signingMethodFake) Alg() string {
+	return "fake"
 }
 
 func TestValidateServicePrincipal(t *testing.T) {
@@ -40,128 +42,57 @@ func TestValidateServicePrincipal(t *testing.T) {
 	log := logrus.NewEntry(logrus.StandardLogger())
 
 	for _, tt := range []struct {
-		tr          *tokenRequirements
-		name        string
-		wantErr     string
-		aadMock     func(*mock_aad.MockTokenClient, *tokenRequirements)
-		getTokenErr error
+		tr      *tokenRequirements
+		name    string
+		wantErr string
 	}{
 		{
 			name: "pass: Successful Validation",
-			tr:   newTokenRequirements(),
+			tr: &tokenRequirements{
+				clientSecret:  "my-secret",
+				signingMethod: jwt.SigningMethodHS256,
+			},
 		},
 		{
 			name: "fail: Provided service must not have Application.ReadWrite.OwnedBy permission",
 			tr: &tokenRequirements{
-				clientID:      "my-client",
 				clientSecret:  "my-secret",
-				tenantID:      "my-tenant.example.com",
-				aadEndpoint:   "https://login.microsoftonline.com/",
-				graphEndpoint: "https://graph.windows.net/",
-				resource:      "https://management.azure.com/",
-				claims:        `{ "Roles":["Application.ReadWrite.OwnedBy"] }`,
+				claims:        jwt.MapClaims{"roles": []string{"Application.ReadWrite.OwnedBy"}},
+				signingMethod: jwt.SigningMethodHS256,
 			},
 			wantErr: "400: InvalidServicePrincipalCredentials: properties.servicePrincipalProfile: The provided service principal must not have the Application.ReadWrite.OwnedBy permission.",
 		},
-		{
-			name:        "fail: Provided service must not have Application.ReadWrite.OwnedBy permission",
-			tr:          newTokenRequirements(),
-			getTokenErr: fmt.Errorf("parameter activeDirectoryEndpoint cannot be empty"),
-			wantErr:     "parameter activeDirectoryEndpoint cannot be empty",
-		},
 		{
 			name: "fail: unavailable signing method",
 			tr: &tokenRequirements{
-				clientID:      "my-client",
 				clientSecret:  "my-secret",
-				tenantID:      "my-tenant.example.com",
-				aadEndpoint:   "https://login.microsoftonline.com/",
-				graphEndpoint: "https://graph.windows.net/",
-				resource:      "https://management.azure.com/",
-				claims:        `{ "Roles":["Application.ReadWrite.OwnedBy"] }`,
-				signMethod:    "fake-signing-method",
+				claims:        jwt.MapClaims{"roles": []string{"Application.ReadWrite.OwnedBy"}},
+				signingMethod: signingMethodFake{},
 			},
 			wantErr: "400: InvalidServicePrincipalCredentials: properties.servicePrincipalProfile: signing method (alg) is unavailable.",
 		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
-			controller := gomock.NewController(t)
-			aad := mock_aad.NewMockTokenClient(controller)
-
-			token, err := createToken(tt.tr)
-			if err != nil {
-				t.Errorf("failed to create testing service principal token: %v\n", err)
-			}
-
-			spDynamic, err := NewServicePrincipalValidator(log, &azureclient.PublicCloud, AuthorizerClusterServicePrincipal, aad)
+			spDynamic, err := NewServicePrincipalValidator(log, &azureclient.PublicCloud, AuthorizerClusterServicePrincipal)
 			if err != nil {
 				t.Errorf("failed to create ServicePrincipalDynamicValidator: %v\n", err)
 			}
 
-			aad.EXPECT().GetToken(ctx, log, tt.tr.clientID, tt.tr.clientSecret, tt.tr.tenantID, tt.tr.aadEndpoint, tt.tr.graphEndpoint).MaxTimes(1).Return(token, tt.getTokenErr)
-
-			err = spDynamic.ValidateServicePrincipal(ctx, tt.tr.clientID, tt.tr.clientSecret, tt.tr.tenantID)
+			err = spDynamic.ValidateServicePrincipal(ctx, tt.tr)
 			utilerror.AssertErrorMessage(t, err, tt.wantErr)
 		})
 	}
 }
 
-// createToken manually creates an adal.ServicePrincipalToken
-func createToken(tr *tokenRequirements) (*adal.ServicePrincipalToken, error) {
-	if tr.signMethod == "" {
-		tr.signMethod = "HS256"
-	}
-	claimsEnc := base64.StdEncoding.EncodeToString([]byte(tr.claims))
-	headerEnc := base64.StdEncoding.EncodeToString([]byte(`{ "alg": "` + tr.signMethod + `", "typ": "JWT" }`))
-	signatureEnc := base64.StdEncoding.EncodeToString(
-		hmac.New(sha512.New, []byte(headerEnc+claimsEnc+tr.clientSecret)).Sum(nil),
-	)
-
-	r := rand.New(rand.NewSource(time.Now().UnixMicro()))
-	tk := adal.Token{
-		AccessToken:  headerEnc + "." + claimsEnc + "." + signatureEnc,
-		RefreshToken: fmt.Sprintf("rand-%d", r.Int()),
-		ExpiresIn:    json.Number("300"),
-		Resource:     tr.resource,
-		Type:         "refresh",
-	}
-
-	aadUrl, err := url.Parse(tr.aadEndpoint)
-	if err != nil {
-		return nil, err
-	}
-	authUrl, err := url.Parse("https://login.microsoftonline.com/my-tenant.example.com/oauth2/authorize")
+// GetToken allows tokenRequirements to be used as an azcore.TokenCredential.
+func (tr *tokenRequirements) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
+	token, err := jwt.NewWithClaims(tr.signingMethod, tr.claims).SignedString([]byte(tr.clientSecret))
 	if err != nil {
-		return nil, err
+		return azcore.AccessToken{}, err
 	}
-	tokenUrl, err := url.Parse("https://login.microsoftonline.com/my-tenant.example.com/oauth2/token")
-	if err != nil {
-		return nil, err
-	}
-	if err != nil {
-		return nil, err
-	}
-	deviceCodeUrl, err := url.Parse("https://devicecode.com")
-	if err != nil {
-		return nil, err
-	}
-	return adal.NewServicePrincipalTokenFromManualToken(adal.OAuthConfig{
-		AuthorityEndpoint:  *aadUrl,
-		AuthorizeEndpoint:  *authUrl,
-		TokenEndpoint:      *tokenUrl,
-		DeviceCodeEndpoint: *deviceCodeUrl,
-	}, tr.clientID, tr.resource, tk)
-}
 
-func newTokenRequirements() *tokenRequirements {
-	return &tokenRequirements{
-		clientID:      "my-client",
-		clientSecret:  "my-secret",
-		tenantID:      "my-tenant.example.com",
-		aadEndpoint:   "https://login.microsoftonline.com/",
-		graphEndpoint: "https://graph.windows.net/",
-		resource:      "https://management.azure.com/",
-		claims:        `{}`,
-		signMethod:    "HS256",
-	}
+	return azcore.AccessToken{
+		Token:     token,
+		ExpiresOn: time.Now().Add(10 * time.Minute),
+	}, nil
 }
diff --git a/pkg/cluster/adminupdate_test.go b/pkg/cluster/adminupdate_test.go
index a118b17b5..e7a7c2a3b 100644
--- a/pkg/cluster/adminupdate_test.go
+++ b/pkg/cluster/adminupdate_test.go
@@ -67,9 +67,9 @@ func TestAdminUpdateSteps(t *testing.T) {
 				"[Action ensureDefaults-fm]",
 				"[Action fixupClusterSPObjectID-fm]",
 				"[Action fixInfraID-fm]",
-				"[AuthorizationRefreshingAction [Action ensureResourceGroup-fm]]",
+				"[Action ensureResourceGroup-fm]",
 				"[Action createOrUpdateDenyAssignment-fm]",
-				"[AuthorizationRefreshingAction [Action enableServiceEndpoints-fm]]",
+				"[Action enableServiceEndpoints-fm]",
 				"[Action populateRegistryStorageAccountName-fm]",
 				"[Action migrateStorageAccounts-fm]",
 				"[Action fixSSH-fm]",
@@ -111,9 +111,9 @@ func TestAdminUpdateSteps(t *testing.T) {
 				"[Action ensureDefaults-fm]",
 				"[Action fixupClusterSPObjectID-fm]",
 				"[Action fixInfraID-fm]",
-				"[AuthorizationRefreshingAction [Action ensureResourceGroup-fm]]",
+				"[Action ensureResourceGroup-fm]",
 				"[Action createOrUpdateDenyAssignment-fm]",
-				"[AuthorizationRefreshingAction [Action enableServiceEndpoints-fm]]",
+				"[Action enableServiceEndpoints-fm]",
 				"[Action populateRegistryStorageAccountName-fm]",
 				"[Action migrateStorageAccounts-fm]",
 				"[Action fixSSH-fm]",
@@ -151,9 +151,9 @@ func TestAdminUpdateSteps(t *testing.T) {
 				"[Action ensureDefaults-fm]",
 				"[Action fixupClusterSPObjectID-fm]",
 				"[Action fixInfraID-fm]",
-				"[AuthorizationRefreshingAction [Action ensureResourceGroup-fm]]",
+				"[Action ensureResourceGroup-fm]",
 				"[Action createOrUpdateDenyAssignment-fm]",
-				"[AuthorizationRefreshingAction [Action enableServiceEndpoints-fm]]",
+				"[Action enableServiceEndpoints-fm]",
 				"[Action populateRegistryStorageAccountName-fm]",
 				"[Action migrateStorageAccounts-fm]",
 				"[Action fixSSH-fm]",
@@ -222,9 +222,9 @@ func TestAdminUpdateSteps(t *testing.T) {
 				"[Action ensureDefaults-fm]",
 				"[Action fixupClusterSPObjectID-fm]",
 				"[Action fixInfraID-fm]",
-				"[AuthorizationRefreshingAction [Action ensureResourceGroup-fm]]",
+				"[Action ensureResourceGroup-fm]",
 				"[Action createOrUpdateDenyAssignment-fm]",
-				"[AuthorizationRefreshingAction [Action enableServiceEndpoints-fm]]",
+				"[Action enableServiceEndpoints-fm]",
 				"[Action populateRegistryStorageAccountName-fm]",
 				"[Action migrateStorageAccounts-fm]",
 				"[Action fixSSH-fm]",
diff --git a/pkg/cluster/install_test.go b/pkg/cluster/install_test.go
index 96ff6ce68..9d8507780 100644
--- a/pkg/cluster/install_test.go
+++ b/pkg/cluster/install_test.go
@@ -258,13 +258,12 @@ func TestInstallationTimeMetrics(t *testing.T) {
 			steps: []steps.Step{
 				steps.Action(successfulActionStep),
 				steps.Condition(successfulConditionStep, 30*time.Minute, true),
-				steps.AuthorizationRefreshingAction(nil, steps.Action(successfulActionStep)),
+				steps.Action(successfulActionStep),
 			},
 			wantedMetrics: map[string]int64{
-				"backend.openshiftcluster.install.duration.total.seconds":                             6,
+				"backend.openshiftcluster.install.duration.total.seconds":                             4,
 				"backend.openshiftcluster.install.action.successfulActionStep.duration.seconds":       2,
 				"backend.openshiftcluster.install.condition.successfulConditionStep.duration.seconds": 2,
-				"backend.openshiftcluster.install.refreshing.successfulActionStep.duration.seconds":   2,
 			},
 		},
 		{
@@ -274,13 +273,12 @@ func TestInstallationTimeMetrics(t *testing.T) {
 			steps: []steps.Step{
 				steps.Action(successfulActionStep),
 				steps.Condition(successfulConditionStep, 30*time.Minute, true),
-				steps.AuthorizationRefreshingAction(nil, steps.Action(successfulActionStep)),
+				steps.Action(successfulActionStep),
 			},
 			wantedMetrics: map[string]int64{
-				"backend.openshiftcluster.update.duration.total.seconds":                             9,
+				"backend.openshiftcluster.update.duration.total.seconds":                             6,
 				"backend.openshiftcluster.update.action.successfulActionStep.duration.seconds":       3,
 				"backend.openshiftcluster.update.condition.successfulConditionStep.duration.seconds": 3,
-				"backend.openshiftcluster.update.refreshing.successfulActionStep.duration.seconds":   3,
 			},
 		},
 	} {
diff --git a/pkg/operator/controllers/checkers/serviceprincipalchecker/checker_test.go b/pkg/operator/controllers/checkers/serviceprincipalchecker/checker_test.go
index 1f8c2091d..abb42cfc8 100644
--- a/pkg/operator/controllers/checkers/serviceprincipalchecker/checker_test.go
+++ b/pkg/operator/controllers/checkers/serviceprincipalchecker/checker_test.go
@@ -8,6 +8,9 @@ import (
 	"errors"
 	"testing"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	jwt "github.com/golang-jwt/jwt/v4"
 	"github.com/golang/mock/gomock"
 	azuretypes "github.com/openshift/installer/pkg/types/azure"
 	"github.com/sirupsen/logrus"
@@ -19,6 +22,17 @@ import (
 	utilerror "github.com/Azure/ARO-RP/test/util/error"
 )
 
+type fakeTokenCredential struct{}
+
+func (c fakeTokenCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
+	token, err := jwt.New(jwt.SigningMethodHS256).SignedString([]byte{})
+	if err != nil {
+		return azcore.AccessToken{}, err
+	}
+
+	return azcore.AccessToken{Token: token}, nil
+}
+
 func TestCheck(t *testing.T) {
 	ctx := context.Background()
 	log := logrus.NewEntry(logrus.StandardLogger())
@@ -39,7 +53,7 @@ func TestCheck(t *testing.T) {
 			credentialsExist: true,
 			validator: func(controller *gomock.Controller) dynamic.ServicePrincipalValidator {
 				validator := mock_dynamic.NewMockDynamic(controller)
-				validator.EXPECT().ValidateServicePrincipal(ctx, string(mockCredentials.ClientID), string(mockCredentials.ClientSecret), string(mockCredentials.TenantID))
+				validator.EXPECT().ValidateServicePrincipal(ctx, &fakeTokenCredential{})
 				return validator
 			},
 		},
@@ -48,7 +62,7 @@ func TestCheck(t *testing.T) {
 			credentialsExist: true,
 			validator: func(controller *gomock.Controller) dynamic.ServicePrincipalValidator {
 				validator := mock_dynamic.NewMockDynamic(controller)
-				validator.EXPECT().ValidateServicePrincipal(ctx, string(mockCredentials.ClientID), string(mockCredentials.ClientSecret), string(mockCredentials.TenantID)).
+				validator.EXPECT().ValidateServicePrincipal(ctx, &fakeTokenCredential{}).
 					Return(errors.New("fake validation error"))
 				return validator
 			},
@@ -81,6 +95,9 @@ func TestCheck(t *testing.T) {
 					}
 					return nil, errors.New("fake credentials get error")
 				},
+				getTokenCredential: func(*azureclient.AROEnvironment, *clusterauthorizer.Credentials) (azcore.TokenCredential, error) {
+					return &fakeTokenCredential{}, nil
+				},
 				newSPValidator: func(azEnv *azureclient.AROEnvironment) (dynamic.ServicePrincipalValidator, error) {
 					if validatorMock != nil {
 						return validatorMock, nil
diff --git a/pkg/util/aad/aad_test.go b/pkg/util/aad/aad_test.go
deleted file mode 100644
index 09717237d..000000000
--- a/pkg/util/aad/aad_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package aad
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-	"errors"
-	"net/http"
-	"testing"
-	"time"
-
-	"github.com/golang/mock/gomock"
-	"github.com/sirupsen/logrus"
-
-	"github.com/Azure/ARO-RP/pkg/api"
-	mock_refreshable "github.com/Azure/ARO-RP/pkg/util/mocks/refreshable"
-)
-
-func TestAuthenticateServicePrincipalToken(t *testing.T) {
-	tests := []struct {
-		name         string
-		wantErr      error
-		refreshError error
-	}{
-		{
-			name:         "Calling ServicePrincipal returns error.",
-			refreshError: errors.New("Some refesh error"),
-			wantErr: api.NewCloudError(
-				http.StatusBadRequest,
-				api.CloudErrorCodeInvalidServicePrincipalCredentials,
-				"properties.servicePrincipalProfile",
-				"The provided service principal credentials are invalid."),
-		},
-	}
-
-	mockController := gomock.NewController(t)
-	ctx := context.Background()
-	log := logrus.NewEntry(logrus.StandardLogger())
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			duration := 1 * time.Second
-			authorizer := mock_refreshable.NewMockAuthorizer(mockController)
-
-			authorizer.EXPECT().RefreshWithContext(gomock.Any(), gomock.Any()).Return(false, tt.refreshError).AnyTimes()
-
-			err := authenticateServicePrincipalToken(ctx, log, authorizer, duration)
-			if err != nil && tt.wantErr.Error() != err.Error() {
-				t.Errorf("AuthenticateServicePrincipalToken() error = %v, wantErr = %v", err.Error(), tt.wantErr.Error())
-				return
-			}
-		})
-	}
-}
diff --git a/pkg/util/clusterauthorizer/authorizer_test.go b/pkg/util/clusterauthorizer/authorizer_test.go
index 83c47cddf..1a46f0bef 100644
--- a/pkg/util/clusterauthorizer/authorizer_test.go
+++ b/pkg/util/clusterauthorizer/authorizer_test.go
@@ -5,36 +5,25 @@ package clusterauthorizer
 
 import (
 	"context"
-	"crypto/hmac"
-	"crypto/sha512"
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
-	"math/rand"
-	"net/url"
 	"testing"
 	"time"
 
-	"github.com/Azure/go-autorest/autorest/adal"
-	"github.com/golang/mock/gomock"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+	jwt "github.com/golang-jwt/jwt/v4"
 	"github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
-	mock_aad "github.com/Azure/ARO-RP/pkg/util/mocks/aad"
 	utilerror "github.com/Azure/ARO-RP/test/util/error"
 )
 
 type tokenRequirements struct {
-	clientID     string
-	clientSecret string
-	tenantID     string
-	aadEndpoint  string
-	resource     string
-	claims       string
-	signMethod   string
+	clientSecret  string
+	claims        jwt.MapClaims
+	signingMethod jwt.SigningMethod
 }
 
 var (
@@ -70,12 +59,9 @@ func TestNewAzRefreshableAuthorizer(t *testing.T) {
 		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
-			controller := gomock.NewController(t)
-			aad := mock_aad.NewMockTokenClient(controller)
-
 			clientFake := ctrlfake.NewClientBuilder().WithObjects(tt.secret).Build()
 
-			_, err := NewAzRefreshableAuthorizer(tt.log, tt.azCloudEnv, clientFake, aad)
+			_, err := NewAzRefreshableAuthorizer(tt.log, tt.azCloudEnv, clientFake)
 			utilerror.AssertErrorMessage(t, err, tt.wantErr)
 		})
 	}
@@ -87,19 +73,11 @@ func TestNewRefreshableAuthorizerToken(t *testing.T) {
 	log := logrus.NewEntry(logrus.StandardLogger())
 
 	for _, tt := range []struct {
-		name        string
-		secret      *corev1.Secret
-		tr          *tokenRequirements
-		getTokenErr error
-		wantErr     string
+		name    string
+		secret  *corev1.Secret
+		tr      *tokenRequirements
+		wantErr string
 	}{
-		{
-			name:        "fail: Invalid principal credentials",
-			secret:      newV1CoreSecret(azureSecretName, nameSpace),
-			tr:          newTokenRequirements(),
-			getTokenErr: fmt.Errorf("400: InvalidServicePrincipalCredentials: properties.servicePrincipalProfile: The provided service principal credentials are invalid."),
-			wantErr:     "400: InvalidServicePrincipalCredentials: properties.servicePrincipalProfile: The provided service principal credentials are invalid.",
-		},
 		{
 			name: "fail: Missing client secret",
 			secret: &corev1.Secret{
@@ -115,41 +93,23 @@ func TestNewRefreshableAuthorizerToken(t *testing.T) {
 			wantErr: "azure_client_secret does not exist in the secret",
 		},
 		{
-			name: "fail: invalid signing method",
+			name: "pass: create new bearer authorizer token",
 			tr: &tokenRequirements{
-				clientID:     "my-client",
-				clientSecret: "my-secret",
-				tenantID:     "my-tenant.example.com",
-				aadEndpoint:  "https://login.microsoftonline.com/",
-				resource:     "https://management.azure.com/",
-				claims:       `{}`,
-				signMethod:   "fake-signing-method",
+				clientSecret:  "my-secret",
+				signingMethod: jwt.SigningMethodHS256,
 			},
-			secret:  newV1CoreSecret(azureSecretName, nameSpace),
-			wantErr: "400: InvalidServicePrincipalCredentials: properties.servicePrincipalProfile: the provided service principal is invalid",
-		},
-		{
-			name:   "pass: create new bearer authorizer token",
-			tr:     newTokenRequirements(),
 			secret: newV1CoreSecret(azureSecretName, nameSpace),
 		},
 	} {
 		clientFake := ctrlfake.NewClientBuilder().WithObjects(tt.secret).Build()
 
-		controller := gomock.NewController(t)
-		aad := mock_aad.NewMockTokenClient(controller)
-		if tt.tr != nil {
-			token, err := createToken(tt.tr)
-			if err != nil {
-				t.Errorf("failed to manually create token for mock aad")
-			}
-			aad.EXPECT().GetToken(ctx, log, tt.tr.clientID, tt.tr.clientSecret, tt.tr.tenantID, tt.tr.aadEndpoint, tt.tr.resource).MaxTimes(1).Return(token, tt.getTokenErr)
-		}
-
-		azRefreshAuthorizer, err := NewAzRefreshableAuthorizer(log, &azureclient.PublicCloud, clientFake, aad)
+		azRefreshAuthorizer, err := NewAzRefreshableAuthorizer(log, &azureclient.PublicCloud, clientFake)
 		if err != nil {
 			t.Errorf("failed to create azRefreshAuthorizer, %v", err)
 		}
+		azRefreshAuthorizer.getTokenCredential = func(*azureclient.AROEnvironment, *Credentials) (azcore.TokenCredential, error) {
+			return tt.tr, nil
+		}
 
 		t.Run(tt.name, func(t *testing.T) {
 			token, err := azRefreshAuthorizer.NewRefreshableAuthorizerToken(ctx)
@@ -255,63 +215,17 @@ func TestAzCredentials(t *testing.T) {
 	}
 }
 
-// createToken manually creates an adal.ServicePrincipalToken
-func createToken(tr *tokenRequirements) (*adal.ServicePrincipalToken, error) {
-	if tr.signMethod == "" {
-		tr.signMethod = "HS256"
-	}
-	claimsEnc := base64.StdEncoding.EncodeToString([]byte(tr.claims))
-	headerEnc := base64.StdEncoding.EncodeToString([]byte(`{ "alg": "` + tr.signMethod + `", "typ": "JWT" }`))
-	signatureEnc := base64.StdEncoding.EncodeToString(
-		hmac.New(sha512.New, []byte(headerEnc+claimsEnc+tr.clientSecret)).Sum(nil),
-	)
-
-	r := rand.New(rand.NewSource(time.Now().UnixMicro()))
-	tk := adal.Token{
-		AccessToken:  headerEnc + "." + claimsEnc + "." + signatureEnc,
-		RefreshToken: fmt.Sprintf("rand-%d", r.Int()),
-		ExpiresIn:    json.Number("300"),
-		Resource:     tr.resource,
-		Type:         "refresh",
-	}
-
-	aadUrl, err := url.Parse(tr.aadEndpoint)
-	if err != nil {
-		return nil, err
-	}
-	authUrl, err := url.Parse("https://login.microsoftonline.com/my-tenant.example.com/oauth2/authorize")
-	if err != nil {
-		return nil, err
-	}
-	tokenUrl, err := url.Parse("https://login.microsoftonline.com/my-tenant.example.com/oauth2/token")
-	if err != nil {
-		return nil, err
-	}
+// GetToken allows tokenRequirements to be used as an azcore.TokenCredential.
+func (tr *tokenRequirements) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
+	token, err := jwt.NewWithClaims(tr.signingMethod, tr.claims).SignedString([]byte(tr.clientSecret))
 	if err != nil {
-		return nil, err
+		return azcore.AccessToken{}, err
 	}
-	deviceCodeUrl, err := url.Parse("https://devicecode.com")
-	if err != nil {
-		return nil, err
-	}
-	return adal.NewServicePrincipalTokenFromManualToken(adal.OAuthConfig{
-		AuthorityEndpoint:  *aadUrl,
-		AuthorizeEndpoint:  *authUrl,
-		TokenEndpoint:      *tokenUrl,
-		DeviceCodeEndpoint: *deviceCodeUrl,
-	}, tr.clientID, tr.resource, tk)
-}
 
-func newTokenRequirements() *tokenRequirements {
-	return &tokenRequirements{
-		clientID:     "my-client",
-		clientSecret: "my-secret",
-		tenantID:     "my-tenant.example.com",
-		aadEndpoint:  "https://login.microsoftonline.com/",
-		resource:     "https://management.azure.com/",
-		claims:       `{}`,
-		signMethod:   "HS256",
-	}
+	return azcore.AccessToken{
+		Token:     token,
+		ExpiresOn: time.Now().Add(10 * time.Minute),
+	}, nil
 }
 
 func newV1CoreSecret(azSecretName, ns string) *corev1.Secret {
diff --git a/pkg/util/instancemetadata/prod_test.go b/pkg/util/instancemetadata/prod_test.go
index 744401ada..20ba74036 100644
--- a/pkg/util/instancemetadata/prod_test.go
+++ b/pkg/util/instancemetadata/prod_test.go
@@ -4,8 +4,6 @@ package instancemetadata
 // Licensed under the Apache License 2.0.
 
 import (
-	"context"
-	"encoding/base64"
 	"errors"
 	"fmt"
 	"io"
@@ -14,10 +12,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/golang/mock/gomock"
-
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
-	mock_instancemetadata "github.com/Azure/ARO-RP/pkg/util/mocks/instancemetadata"
 	utilerror "github.com/Azure/ARO-RP/test/util/error"
 )
 
@@ -159,83 +154,3 @@ func TestPopulateInstanceMetadata(t *testing.T) {
 		})
 	}
 }
-
-func TestPopulateTenantIDFromMSI(t *testing.T) {
-	ctx := context.Background()
-
-	for _, tt := range []struct {
-		name         string
-		mocks        func(*mock_instancemetadata.MockServicePrincipalToken)
-		wantTenantID string
-		wantErr      string
-	}{
-		{
-			name: "valid",
-			mocks: func(token *mock_instancemetadata.MockServicePrincipalToken) {
-				token.EXPECT().
-					RefreshWithContext(gomock.Any()).
-					Return(nil)
-
-				token.EXPECT().
-					OAuthToken().
-					Return(base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none"}`)) + "." +
-						base64.RawURLEncoding.EncodeToString([]byte(`{"tid":"rpTenantID"}`)) + ".")
-			},
-			wantTenantID: "rpTenantID",
-		},
-		{
-			name: "fresh error",
-			mocks: func(token *mock_instancemetadata.MockServicePrincipalToken) {
-				token.EXPECT().
-					RefreshWithContext(gomock.Any()).
-					Return(fmt.Errorf("random error"))
-			},
-			wantErr: "random error",
-		},
-		{
-			name: "oauthtoken invalid",
-			mocks: func(token *mock_instancemetadata.MockServicePrincipalToken) {
-				token.EXPECT().
-					RefreshWithContext(gomock.Any()).
-					Return(nil)
-
-				token.EXPECT().
-					OAuthToken().
-					Return("invalid")
-			},
-			wantErr: "the provided service principal is invalid",
-		},
-	} {
-		t.Run(tt.name, func(t *testing.T) {
-			controller := gomock.NewController(t)
-			defer controller.Finish()
-
-			token := mock_instancemetadata.NewMockServicePrincipalToken(controller)
-			if tt.mocks != nil {
-				tt.mocks(token)
-			}
-
-			p := &prod{
-				newServicePrincipalTokenFromMSI: func(msiEndpoint, resource string) (ServicePrincipalToken, error) {
-					if msiEndpoint != "http://169.254.169.254/metadata/identity/oauth2/token" {
-						return nil, fmt.Errorf("unexpected endpoint %q", msiEndpoint)
-					}
-					if resource != azureclient.PublicCloud.ResourceManagerEndpoint {
-						return nil, fmt.Errorf("unexpected resource %q", resource)
-					}
-					return token, nil
-				},
-				instanceMetadata: instanceMetadata{
-					environment: &azureclient.PublicCloud,
-				},
-			}
-
-			err := p.populateTenantIDFromMSI(ctx)
-			utilerror.AssertErrorMessage(t, err, tt.wantErr)
-
-			if p.tenantID != tt.wantTenantID {
-				t.Error(p.tenantID)
-			}
-		})
-	}
-}
diff --git a/pkg/util/steps/runner_test.go b/pkg/util/steps/runner_test.go
index d3db80324..69d6a9e76 100644
--- a/pkg/util/steps/runner_test.go
+++ b/pkg/util/steps/runner_test.go
@@ -9,32 +9,17 @@ import (
 	"testing"
 	"time"
 
-	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/azure"
 	"github.com/golang/mock/gomock"
 	"github.com/onsi/gomega"
 	"github.com/onsi/gomega/types"
 	"github.com/sirupsen/logrus"
 
-	mock_refreshable "github.com/Azure/ARO-RP/pkg/util/mocks/refreshable"
 	utilerror "github.com/Azure/ARO-RP/test/util/error"
 	testlog "github.com/Azure/ARO-RP/test/util/log"
 )
 
-func successfulFunc(context.Context) error { return nil }
-func failingFunc(context.Context) error    { return errors.New("oh no!") }
-func failsWithAzureError(ctx context.Context) error {
-	return autorest.DetailedError{
-		Method:      "GET",
-		PackageType: "TEST",
-		Message:     "oops",
-		StatusCode:  403,
-		Original: &azure.ServiceError{
-			Code:    "AuthorizationFailed",
-			Message: "failed",
-		},
-	}
-}
+func successfulFunc(context.Context) error               { return nil }
+func failingFunc(context.Context) error                  { return errors.New("oh no!") }
 func alwaysFalseCondition(context.Context) (bool, error) { return false, nil }
 func alwaysTrueCondition(context.Context) (bool, error)  { return true, nil }
 func timingOutCondition(ctx context.Context) (bool, error) {
@@ -102,64 +87,6 @@ func TestStepRunner(t *testing.T) {
 			},
 			wantErr: `oh no!`,
 		},
-		{
-			name: "An AuthorizationRefreshingAction that fails but is retried successfully will allow a successful run",
-			steps: func(controller *gomock.Controller) []Step {
-				refreshable := mock_refreshable.NewMockAuthorizer(controller)
-				refreshable.EXPECT().
-					RefreshWithContext(gomock.Any(), gomock.Any()).
-					Return(true, nil)
-
-				errsRemaining := 1
-				action := Action(func(context.Context) error {
-					if errsRemaining > 0 {
-						errsRemaining--
-						return autorest.DetailedError{
-							Method:      "GET",
-							PackageType: "TEST",
-							Message:     "oops",
-							StatusCode:  403,
-							Original: &azure.ServiceError{
-								Code:    "AuthorizationFailed",
-								Message: "failed",
-							},
-						}
-					}
-					return nil
-				})
-
-				errorsOnce := &authorizationRefreshingActionStep{
-					step:         action,
-					authorizer:   refreshable,
-					retryTimeout: 50 * time.Millisecond,
-					pollInterval: 25 * time.Millisecond,
-				}
-
-				return []Step{
-					Action(successfulFunc),
-					errorsOnce,
-					Action(successfulFunc),
-				}
-			},
-			wantEntries: []map[string]types.GomegaMatcher{
-				{
-					"msg":   gomega.Equal("running step [Action github.com/Azure/ARO-RP/pkg/util/steps.successfulFunc]"),
-					"level": gomega.Equal(logrus.InfoLevel),
-				},
-				{
-					"msg":   gomega.MatchRegexp(`running step \[AuthorizationRefreshingAction \[Action github.com/Azure/ARO-RP/pkg/util/steps\.TestStepRunner\..*.\.1]]`),
-					"level": gomega.Equal(logrus.InfoLevel),
-				},
-				{
-					"msg":   gomega.Equal(`TEST#GET: oops: StatusCode=403 -- Original Error: Code="AuthorizationFailed" Message="failed"`),
-					"level": gomega.Equal(logrus.InfoLevel),
-				},
-				{
-					"msg":   gomega.Equal("running step [Action github.com/Azure/ARO-RP/pkg/util/steps.successfulFunc]"),
-					"level": gomega.Equal(logrus.InfoLevel),
-				},
-			},
-		},
 		{
 			name: "A successful condition will allow steps to continue",
 			steps: func(controller *gomock.Controller) []Step {
@@ -212,63 +139,6 @@ func TestStepRunner(t *testing.T) {
 				},
 			},
 		},
-		{
-			name: "AuthorizationRefreshingAction will not refresh once it is timed out",
-			steps: func(controller *gomock.Controller) []Step {
-				// We time out immediately, so we won't actually try and refresh
-				refreshable := mock_refreshable.NewMockAuthorizer(controller)
-				return []Step{
-					Action(successfulFunc),
-					&authorizationRefreshingActionStep{
-						step:         Action(failsWithAzureError),
-						authorizer:   refreshable,
-						retryTimeout: 1 * time.Nanosecond,
-					},
-					Action(successfulFunc),
-				}
-			},
-			wantEntries: []map[string]types.GomegaMatcher{
-				{
-					"msg":   gomega.Equal("running step [Action github.com/Azure/ARO-RP/pkg/util/steps.successfulFunc]"),
-					"level": gomega.Equal(logrus.InfoLevel),
-				},
-				{
-					"msg":   gomega.Equal("running step [AuthorizationRefreshingAction [Action github.com/Azure/ARO-RP/pkg/util/steps.failsWithAzureError]]"),
-					"level": gomega.Equal(logrus.InfoLevel),
-				},
-				{
-					"msg":   gomega.Equal(`step [AuthorizationRefreshingAction [Action github.com/Azure/ARO-RP/pkg/util/steps.failsWithAzureError]] encountered error: TEST#GET: oops: StatusCode=403 -- Original Error: Code="AuthorizationFailed" Message="failed"`),
-					"level": gomega.Equal(logrus.ErrorLevel),
-				},
-			},
-			wantErr: `TEST#GET: oops: StatusCode=403 -- Original Error: Code="AuthorizationFailed" Message="failed"`,
-		},
-		{
-			name: "AuthorizationRefreshingAction will not refresh on a real failure",
-			steps: func(controller *gomock.Controller) []Step {
-				refreshable := mock_refreshable.NewMockAuthorizer(controller)
-				return []Step{
-					Action(successfulFunc),
-					AuthorizationRefreshingAction(refreshable, Action(failingFunc)),
-					Action(successfulFunc),
-				}
-			},
-			wantEntries: []map[string]types.GomegaMatcher{
-				{
-					"msg":   gomega.Equal("running step [Action github.com/Azure/ARO-RP/pkg/util/steps.successfulFunc]"),
-					"level": gomega.Equal(logrus.InfoLevel),
-				},
-				{
-					"msg":   gomega.Equal("running step [AuthorizationRefreshingAction [Action github.com/Azure/ARO-RP/pkg/util/steps.failingFunc]]"),
-					"level": gomega.Equal(logrus.InfoLevel),
-				},
-				{
-					"msg":   gomega.Equal(`step [AuthorizationRefreshingAction [Action github.com/Azure/ARO-RP/pkg/util/steps.failingFunc]] encountered error: oh no!`),
-					"level": gomega.Equal(logrus.ErrorLevel),
-				},
-			},
-			wantErr: `oh no!`,
-		},
 		{
 			name: "A timed out Condition causes a failure",
 			steps: func(controller *gomock.Controller) []Step {
@@ -360,21 +230,11 @@ func TestStepMetricsNameFormatting(t *testing.T) {
 			step: Condition(alwaysTrueCondition, 1*time.Millisecond, true),
 			want: "condition.alwaysTrueCondition",
 		},
-		{
-			desc: "test refreshing step naming",
-			step: AuthorizationRefreshingAction(nil, Action(successfulFunc)),
-			want: "refreshing.successfulFunc",
-		},
 		{
 			desc: "test anonymous action step naming",
 			step: Action(func(context.Context) error { return nil }),
 			want: "action.func1",
 		},
-		{
-			desc: "test anonymous action step naming",
-			step: AuthorizationRefreshingAction(nil, Action(func(context.Context) error { return nil })),
-			want: "refreshing.func2",
-		},
 	} {
 		t.Run(tt.desc, func(t *testing.T) {
 			if got := tt.step.metricsName(); got != tt.want {
diff --git a/test/e2e/cluster.go b/test/e2e/cluster.go
index 9122ed91c..a3301222c 100644
--- a/test/e2e/cluster.go
+++ b/test/e2e/cluster.go
@@ -119,9 +119,9 @@ func createStatefulSet(ctx context.Context, cli kubernetes.Interface) error {
 	oc, _ := clients.OpenshiftClusters.Get(ctx, vnetResourceGroup, clusterName)
 	installVersion, _ := version.ParseVersion(*oc.ClusterProfile.Version)
 
-	defaultStorageClass := "managed-premium"
-	if installVersion.V[0] == 4 && installVersion.V[1] == 11 {
-		defaultStorageClass = "managed-csi"
+	defaultStorageClass := "managed-csi"
+	if installVersion.Lt(version.NewVersion(4, 11)) {
+		defaultStorageClass = "managed-premium"
 	}
 	pvcStorage, err := resource.ParseQuantity("2Gi")
 	if err != nil {
diff --git a/test/e2e/disk_encryption.go b/test/e2e/disk_encryption.go
index 328a14f8c..a2a0b52b5 100644
--- a/test/e2e/disk_encryption.go
+++ b/test/e2e/disk_encryption.go
@@ -74,9 +74,9 @@ var _ = Describe("Disk encryption at rest", func() {
 		Expect(err).NotTo(HaveOccurred())
 
 		installVersion, _ := version.ParseVersion(*oc.ClusterProfile.Version)
-		defaultStorageClass := "managed-premium-encrypted-cmk"
-		if installVersion.V[0] == 4 && installVersion.V[1] == 11 {
-			defaultStorageClass = "managed-csi-encrypted-cmk"
+		defaultStorageClass := "managed-csi-encrypted-cmk"
+		if installVersion.Lt(version.NewVersion(4, 11)) {
+			defaultStorageClass = "managed-premium-encrypted-cmk"
 		}
 
 		By("checking that disk encryption at rest is enabled for masters")
diff --git a/test/e2e/setup.go b/test/e2e/setup.go
index d40acbab6..3a32a4349 100644
--- a/test/e2e/setup.go
+++ b/test/e2e/setup.go
@@ -19,7 +19,8 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 
-	"github.com/Azure/go-autorest/autorest/azure/auth"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	"github.com/jongio/azidext/go/azidext"
 	configclient "github.com/openshift/client-go/config/clientset/versioned"
 	machineclient "github.com/openshift/client-go/machine/clientset/versioned"
 	projectclient "github.com/openshift/client-go/project/clientset/versioned"
@@ -263,11 +264,15 @@ func resourceIDFromEnv() string {
 }
 
 func newClientSet(ctx context.Context) (*clientSet, error) {
-	authorizer, err := auth.NewAuthorizerFromEnvironment()
+	options := _env.Environment().EnvironmentCredentialOptions()
+	tokenCredential, err := azidentity.NewEnvironmentCredential(options)
 	if err != nil {
 		return nil, err
 	}
 
+	scopes := []string{_env.Environment().ResourceManagerScope}
+	authorizer := azidext.NewTokenCredentialAdapter(tokenCredential, scopes)
+
 	configv1, err := kubeadminkubeconfig.Get(ctx, log, _env, authorizer, resourceIDFromEnv())
 	if err != nil {
 		return nil, err