diff --git a/docs/index.md b/docs/index.md
index 6abb76a..fa1b741 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -55,5 +55,5 @@ resource "ipam_ip_range" "sub1" {
 
 ### Authentication
 
-The provider uses application default credentials to authenticate against the backend. Alternatively you can directly provide an identity token via the GCP_IDENTITY_TOKEN environment variable to access the Cloud Run instance, the audience for the identity token should be the domain of the Cloud Run service.
+The provider uses application default credentials to authenticate against the backend. Alternatively you can set GOOGLE_CREDENTIALS variable to point or contain JSON account key or directly provide an identity token via the GCP_IDENTITY_TOKEN environment variable to access the Cloud Run instance, the audience for the identity token should be the domain of the Cloud Run service.
 
diff --git a/go.mod b/go.mod
index ea81c2d..260595a 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,10 @@ module github.com/openx/terraform-provider-gcp-ipam-autopilot
 go 1.18
 
 require (
+	github.com/go-git/go-git/v5 v5.4.2
 	github.com/hashicorp/terraform-plugin-docs v0.13.0
 	github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0
+	github.com/mitchellh/go-homedir v1.1.0
 	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
 	google.golang.org/api v0.34.0
 )
@@ -20,6 +22,8 @@ require (
 	github.com/bgentry/speakeasy v0.1.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/fatih/color v1.13.0 // indirect
+	github.com/go-git/gcfg v1.5.0 // indirect
+	github.com/go-git/go-billy/v5 v5.3.1 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
@@ -71,4 +75,5 @@ require (
 	google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
 	google.golang.org/grpc v1.48.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
+	gopkg.in/warnings.v0 v0.1.2 // indirect
 )
diff --git a/go.sum b/go.sum
index 7ae7fd4..b1c6f0a 100644
--- a/go.sum
+++ b/go.sum
@@ -277,6 +277,7 @@ github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k
 github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/ipam/config.go b/ipam/config.go
new file mode 100644
index 0000000..7241a56
--- /dev/null
+++ b/ipam/config.go
@@ -0,0 +1,115 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ipam
+
+import (
+	"context"
+	"fmt"
+	"log"
+
+	"golang.org/x/oauth2"
+	googleoauth "golang.org/x/oauth2/google"
+	"google.golang.org/api/idtoken"
+	"google.golang.org/api/option"
+)
+
+// Config is used as a general provider config throughout the provider
+type Config struct {
+	Url         string
+	AccessToken string
+	Credentials string
+	context     context.Context
+}
+
+// staticTokenSource is used to be able to identify static token sources without reflection.
+type staticTokenSource struct {
+	oauth2.TokenSource
+}
+
+const (
+	userInfoScope = "https://www.googleapis.com/auth/userinfo.email"
+)
+
+func (c *Config) getToken() (string, error) {
+
+	creds, err := c.GetCredentials([]string{userInfoScope})
+	if err != nil {
+		return "", fmt.Errorf("error calling getCredentials(): %v", err)
+	}
+
+	ctx := context.Background()
+
+	targetAudience := c.Url // "http://ipam-autopilot.com"
+
+	co := []option.ClientOption{}
+	co = append(co, idtoken.WithCredentialsJSON(creds.JSON))
+
+	idTokenSource, err := idtoken.NewTokenSource(ctx, targetAudience, co...)
+
+	if err != nil {
+		return "", fmt.Errorf("unable to retrieve TokenSource: %v", err)
+	}
+	idToken, err := idTokenSource.Token()
+	if err != nil {
+		return "", fmt.Errorf("unable to retrieve Token: %v", err)
+	}
+
+	return idToken.AccessToken, nil
+}
+
+// Get a set of credentials with a given scope (clientScopes) based on the Config object.
+func (c *Config) GetCredentials(clientScopes []string) (googleoauth.Credentials, error) {
+	if c.AccessToken != "" {
+		contents, _, err := pathOrContents(c.AccessToken)
+		if err != nil {
+			return googleoauth.Credentials{}, fmt.Errorf("Error loading access token: %s", err)
+		}
+
+		token := &oauth2.Token{AccessToken: contents}
+
+		log.Printf("[INFO] Authenticating using configured Google JSON 'access_token'...")
+		log.Printf("[INFO]   -- Scopes: %s", clientScopes)
+		return googleoauth.Credentials{
+			TokenSource: staticTokenSource{oauth2.StaticTokenSource(token)},
+		}, nil
+	}
+
+	if c.Credentials != "" {
+		contents, _, err := pathOrContents(c.Credentials)
+		if err != nil {
+			return googleoauth.Credentials{}, fmt.Errorf("error loading credentials: %s", err)
+		}
+
+		creds, err := googleoauth.CredentialsFromJSON(c.context, []byte(contents), clientScopes...)
+		if err != nil {
+			return googleoauth.Credentials{}, fmt.Errorf("unable to parse credentials from '%s': %s", contents, err)
+		}
+
+		log.Printf("[INFO] Authenticating using configured Google JSON 'credentials'...")
+		log.Printf("[INFO]   -- Scopes: %s", clientScopes)
+		return *creds, nil
+	}
+
+	log.Printf("[INFO] Authenticating using DefaultClient...")
+	log.Printf("[INFO]   -- Scopes: %s", clientScopes)
+	defaultTS, err := googleoauth.DefaultTokenSource(context.Background(), clientScopes...)
+	if err != nil {
+		return googleoauth.Credentials{}, fmt.Errorf("Attempted to load application default credentials since neither `credentials` nor `access_token` was set in the provider block.  No credentials loaded. To use your gcloud credentials, run 'gcloud auth application-default login'.  Original error: %w", err)
+	}
+
+	return googleoauth.Credentials{
+		TokenSource: defaultTS,
+	}, err
+}
diff --git a/ipam/config/config.go b/ipam/config/config.go
deleted file mode 100644
index 4683a54..0000000
--- a/ipam/config/config.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package config
-
-// Config is used as a general provider config throughout the provider
-type Config struct {
-	Url string
-}
diff --git a/ipam/provider.go b/ipam/provider.go
index 624f049..cf0aa28 100644
--- a/ipam/provider.go
+++ b/ipam/provider.go
@@ -15,18 +15,20 @@
 package ipam
 
 import (
+	"context"
 	"fmt"
 	"os"
 
-	"github.com/openx/terraform-provider-gcp-ipam-autopilot/ipam/config"
-	"github.com/openx/terraform-provider-gcp-ipam-autopilot/ipam/resources"
-
+	//"github.com/google/martian/v3/log"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+	googleoauth "golang.org/x/oauth2/google"
 )
 
 // Provider for Simple IPAM
 func Provider() *schema.Provider {
-	return &schema.Provider{
+	provider := &schema.Provider{
 		Schema: map[string]*schema.Schema{
 			"url": {
 				Type:        schema.TypeString,
@@ -34,29 +36,99 @@ func Provider() *schema.Provider {
 				Default:     "",
 				Description: "URL where to connect with the IPAM Autopilot backend",
 			},
+			"credentials": {
+				Type:          schema.TypeString,
+				Optional:      true,
+				ValidateFunc:  validateCredentials,
+				ConflictsWith: []string{"access_token"},
+			},
+
+			"access_token": {
+				Type:          schema.TypeString,
+				Optional:      true,
+				ConflictsWith: []string{"credentials"},
+			},
 		},
 		ResourcesMap: map[string]*schema.Resource{
-			"ipam_ip_range":       resources.ResourceIpRange(),
-			"ipam_routing_domain": resources.ResourceRoutingDomain(),
+			"ipam_ip_range":       ResourceIpRange(),
+			"ipam_routing_domain": ResourceRoutingDomain(),
 		},
 		DataSourcesMap: map[string]*schema.Resource{},
-		ConfigureFunc:  providerConfigure,
 	}
+	provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
+		return providerConfigure(ctx, d, provider)
+	}
+
+	return provider
 }
 
-func providerConfigure(d *schema.ResourceData) (interface{}, error) {
+func providerConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Provider) (interface{}, diag.Diagnostics) {
+	config := Config{}
+
 	url := d.Get("url").(string)
 	if url == "" {
 		url = os.Getenv("IPAM_URL")
 	}
 
 	if url == "" {
-		return nil, fmt.Errorf("URL needed to access IPAM Autopilot")
+		return nil, diag.Errorf("URL needed to access IPAM Autopilot")
+	}
+
+	config.Url = url
+
+	// Check for primary credentials in config. Note that if neither is set, ADCs
+	// will be used if available.
+	if v, ok := d.GetOk("access_token"); ok {
+		config.AccessToken = v.(string)
 	}
 
-	config := config.Config{
-		Url: url,
+	if v, ok := d.GetOk("credentials"); ok {
+		config.Credentials = v.(string)
+	}
+
+	// only check environment variables if neither value was set in config- this
+	// means config beats env var in all cases.
+	if config.AccessToken == "" && config.Credentials == "" {
+		config.Credentials = multiEnvSearch([]string{
+			"GOOGLE_CREDENTIALS",
+			"GOOGLE_CLOUD_KEYFILE_JSON",
+			"GCLOUD_KEYFILE_JSON",
+		})
+
+		// Retrieve token if credentials were available in one of ENV variables
+		if config.Credentials != "" {
+
+			token, err := config.getToken()
+			if err != nil {
+				return "", diag.Errorf("unable to retrieve identityToken: %v", err)
+			}
+			config.AccessToken = token
+			return config, nil
+		}
+
+		config.AccessToken = multiEnvSearch([]string{
+			"GOOGLE_OAUTH_ACCESS_TOKEN",
+			"GCP_IDENTITY_TOKEN",
+		})
+
 	}
 
 	return config, nil
 }
+
+func validateCredentials(v interface{}, k string) (warnings []string, errors []error) {
+	if v == nil || v.(string) == "" {
+		return
+	}
+	creds := v.(string)
+	// if this is a path and we can stat it, assume it's ok
+	if _, err := os.Stat(creds); err == nil {
+		return
+	}
+	if _, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(creds)); err != nil {
+		errors = append(errors,
+			fmt.Errorf("JSON credentials are not valid: %s", err))
+	}
+
+	return
+}
diff --git a/ipam/resources/resource_ip_range.go b/ipam/resource_ip_range.go
similarity index 84%
rename from ipam/resources/resource_ip_range.go
rename to ipam/resource_ip_range.go
index 75249fc..a8d5070 100644
--- a/ipam/resources/resource_ip_range.go
+++ b/ipam/resource_ip_range.go
@@ -12,21 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package resources
+package ipam
 
 import (
 	"bytes"
-	"context"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net/http"
-	"os"
 
-	"github.com/openx/terraform-provider-gcp-ipam-autopilot/ipam/config"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-	"golang.org/x/oauth2/google"
-	"google.golang.org/api/idtoken"
 )
 
 func ResourceIpRange() *schema.Resource {
@@ -67,13 +62,14 @@ func ResourceIpRange() *schema.Resource {
 	}
 }
 func resourceCreate(d *schema.ResourceData, meta interface{}) error {
-	config := meta.(config.Config)
+	config := meta.(Config)
 	range_size := d.Get("range_size").(int)
 	parent := d.Get("parent").(string)
 	name := d.Get("name").(string)
 	domain := d.Get("domain").(string)
 	cidr := d.Get("cidr").(string)
 	url := fmt.Sprintf("%s/ranges", config.Url)
+	accessToken := config.AccessToken
 	var postBody []byte
 	var err error
 	if parent == "" {
@@ -100,7 +96,7 @@ func resourceCreate(d *schema.ResourceData, meta interface{}) error {
 	}
 	fmt.Printf("%s", string(postBody))
 	responseBody := bytes.NewBuffer(postBody)
-	accessToken, err := getIdentityToken()
+
 	if err != nil {
 		return fmt.Errorf("unable to retrieve access token: %v", err)
 	}
@@ -140,14 +136,15 @@ func resourceCreate(d *schema.ResourceData, meta interface{}) error {
 }
 
 func resourceRead(d *schema.ResourceData, meta interface{}) error {
-	config := meta.(config.Config)
+	config := meta.(Config)
 	url := fmt.Sprintf("%s/ranges/%s", config.Url, d.Id())
 
 	req, err := http.NewRequest("GET", url, nil)
 	if err != nil {
 		return fmt.Errorf("failed creating request: %v", err)
 	}
-	accessToken, err := getIdentityToken()
+	accessToken := config.AccessToken
+
 	if err != nil {
 		return fmt.Errorf("unable to retrieve access token: %v", err)
 	}
@@ -182,7 +179,7 @@ func resourceRead(d *schema.ResourceData, meta interface{}) error {
 }
 
 func resourceDelete(d *schema.ResourceData, meta interface{}) error {
-	config := meta.(config.Config)
+	config := meta.(Config)
 
 	url := fmt.Sprintf("%s/ranges/%s", config.Url, d.Id())
 
@@ -191,7 +188,9 @@ func resourceDelete(d *schema.ResourceData, meta interface{}) error {
 	if err != nil {
 		return fmt.Errorf("failed creating release request: %v", err)
 	}
-	accessToken, err := getIdentityToken()
+
+	accessToken := config.AccessToken
+
 	if err != nil {
 		return fmt.Errorf("unable to retrieve access token: %v", err)
 	}
@@ -211,33 +210,3 @@ func resourceDelete(d *schema.ResourceData, meta interface{}) error {
 		return fmt.Errorf("failed releasing range status_code=%d, status=%s,body=%s", resp.StatusCode, resp.Status, string(body))
 	}
 }
-
-func getIdentityToken() (string, error) {
-	if os.Getenv("GCP_IDENTITY_TOKEN") != "" {
-		return os.Getenv("GCP_IDENTITY_TOKEN"), nil
-	}
-
-	ctx := context.Background()
-	audience := "http://ipam-autopilot.com"
-	ts, err := idtoken.NewTokenSource(ctx, audience)
-	if err != nil {
-		if err.Error() != `idtoken: credential must be service_account, found "authorized_user"` {
-			return "", err
-		}
-		gts, err := google.DefaultTokenSource(ctx)
-		if err != nil {
-			return "", err
-		}
-		token, err := gts.Token()
-		if err != nil {
-			return "", err
-		}
-		identityToken := token.Extra("id_token").(string)
-		return identityToken, nil
-	}
-	token, err := ts.Token()
-	if err != nil {
-		return "", err
-	}
-	return token.AccessToken, nil
-}
diff --git a/ipam/resources/resource_routing_domain.go b/ipam/resource_routing_domain.go
similarity index 93%
rename from ipam/resources/resource_routing_domain.go
rename to ipam/resource_routing_domain.go
index b8e92a7..011ffc4 100644
--- a/ipam/resources/resource_routing_domain.go
+++ b/ipam/resource_routing_domain.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package resources
+package ipam
 
 import (
 	"bytes"
@@ -22,7 +22,6 @@ import (
 	"net/http"
 	"strings"
 
-	"github.com/openx/terraform-provider-gcp-ipam-autopilot/ipam/config"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
 )
 
@@ -53,7 +52,7 @@ func ResourceRoutingDomain() *schema.Resource {
 }
 
 func routingDomainCreate(d *schema.ResourceData, meta interface{}) error {
-	config := meta.(config.Config)
+	config := meta.(Config)
 	vpcs := d.Get("vpcs").([]interface{})
 	name := d.Get("name").(string)
 	url := fmt.Sprintf("%s/domains", config.Url)
@@ -68,20 +67,19 @@ func routingDomainCreate(d *schema.ResourceData, meta interface{}) error {
 	}
 	fmt.Printf("%s", string(postBody))
 	responseBody := bytes.NewBuffer(postBody)
-	accessToken, err := getIdentityToken()
-	if err != nil {
-		return fmt.Errorf("unable to retrieve access token: %v", err)
-	}
+	accessToken := config.AccessToken
+
 	req, err := http.NewRequest("POST", url, responseBody)
 	if err != nil {
 		return fmt.Errorf("failed creating request: %v", err)
 	}
 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
 	req.Header.Set("Content-Type", "application/json")
+
 	client := &http.Client{}
 	resp, err := client.Do(req)
 	if err != nil {
-		return fmt.Errorf("failed creating range: %v", err)
+		return fmt.Errorf("failed creating domain: %v", err)
 	}
 	defer resp.Body.Close()
 	if resp.StatusCode == 200 {
@@ -109,18 +107,20 @@ func routingDomainCreate(d *schema.ResourceData, meta interface{}) error {
 }
 
 func routingDomainRead(d *schema.ResourceData, meta interface{}) error {
-	config := meta.(config.Config)
+	config := meta.(Config)
 	url := fmt.Sprintf("%s/domains/%s", config.Url, d.Id())
 
 	req, err := http.NewRequest("GET", url, nil)
 	if err != nil {
 		return fmt.Errorf("failed creating request: %v", err)
 	}
-	accessToken, err := getIdentityToken()
+	accessToken := config.AccessToken
+
 	if err != nil {
 		return fmt.Errorf("unable to retrieve access token: %v", err)
 	}
 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
+
 	client := &http.Client{}
 	resp, err := client.Do(req)
 	if err != nil {
@@ -157,7 +157,7 @@ func routingDomainRead(d *schema.ResourceData, meta interface{}) error {
 }
 
 func routingDomainDelete(d *schema.ResourceData, meta interface{}) error {
-	config := meta.(config.Config)
+	config := meta.(Config)
 
 	url := fmt.Sprintf("%s/domains/%s", config.Url, d.Id())
 
@@ -166,7 +166,7 @@ func routingDomainDelete(d *schema.ResourceData, meta interface{}) error {
 	if err != nil {
 		return fmt.Errorf("failed deleting routing domain request: %v", err)
 	}
-	accessToken, err := getIdentityToken()
+	accessToken := config.AccessToken
 	if err != nil {
 		return fmt.Errorf("unable to retrieve access token: %v", err)
 	}
@@ -188,7 +188,7 @@ func routingDomainDelete(d *schema.ResourceData, meta interface{}) error {
 }
 
 func routingDomainUpdate(d *schema.ResourceData, meta interface{}) error {
-	config := meta.(config.Config)
+	config := meta.(Config)
 	vpcs := d.Get("vpcs").([]interface{})
 	name := d.Get("name").(string)
 	url := fmt.Sprintf("%s/domains/%s", config.Url, d.Id())
@@ -203,7 +203,8 @@ func routingDomainUpdate(d *schema.ResourceData, meta interface{}) error {
 	}
 	fmt.Printf("%s", string(postBody))
 	responseBody := bytes.NewBuffer(postBody)
-	accessToken, err := getIdentityToken()
+	accessToken := config.AccessToken
+
 	if err != nil {
 		return fmt.Errorf("unable to retrieve access token: %v", err)
 	}
diff --git a/ipam/utils.go b/ipam/utils.go
new file mode 100644
index 0000000..dfa673c
--- /dev/null
+++ b/ipam/utils.go
@@ -0,0 +1,48 @@
+package ipam
+
+import (
+	"io/ioutil"
+	"os"
+
+	"github.com/mitchellh/go-homedir"
+)
+
+func multiEnvSearch(ks []string) string {
+	for _, k := range ks {
+		if v := os.Getenv(k); v != "" {
+			return v
+		}
+	}
+	return ""
+}
+
+// If the argument is a path, pathOrContents loads it and returns the contents,
+// otherwise the argument is assumed to be the desired contents and is simply
+// returned.
+//
+// The boolean second return value can be called `wasPath` - it indicates if a
+// path was detected and a file loaded.
+func pathOrContents(poc string) (string, bool, error) {
+	if len(poc) == 0 {
+		return poc, false, nil
+	}
+
+	path := poc
+	if path[0] == '~' {
+		var err error
+		path, err = homedir.Expand(path)
+		if err != nil {
+			return path, true, err
+		}
+	}
+
+	if _, err := os.Stat(path); err == nil {
+		contents, err := ioutil.ReadFile(path)
+		if err != nil {
+			return string(contents), true, err
+		}
+		return string(contents), true, nil
+	}
+
+	return poc, false, nil
+}