Use general (Provider|Service)Client structs.
diff --git a/openstack/client.go b/openstack/client.go
index 98092c8..e964d07 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -2,100 +2,110 @@
 
 import (
 	"errors"
+	"fmt"
+	"net/url"
 
 	"github.com/rackspace/gophercloud"
-	identity3 "github.com/rackspace/gophercloud/openstack/identity/v3"
+	tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
 	"github.com/rackspace/gophercloud/openstack/utils"
 )
 
-// Client provides access to service clients for this OpenStack cloud.
-type Client struct {
-	gophercloud.ProviderClient
-}
-
 const (
 	v20 = "v2.0"
 	v30 = "v3.0"
 )
 
-// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by authOptions, acquires a token, and
+// NewClient prepares an unauthenticated ProviderClient instance.
+// Most users will probably prefer using the AuthenticatedClient function instead.
+// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
+// for example.
+func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
+	// Normalize the identity endpoint that's provided by trimming any path, query or fragment from the URL.
+	u, err := url.Parse(endpoint)
+	if err != nil {
+		return nil, err
+	}
+	u.Path, u.RawQuery, u.Fragment = "", "", ""
+	normalized := u.String()
+
+	return &gophercloud.ProviderClient{
+		IdentityEndpoint: normalized,
+		Reauthenticate: func() error {
+			return errors.New("Unable to reauthenticate before authenticating the first time.")
+		},
+	}, nil
+}
+
+// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
 // returns a Client instance that's ready to operate.
 // It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
 // the most recent identity service available to proceed.
-func AuthenticatedClient(authOptions gophercloud.AuthOptions) (*Client, error) {
-	client := NewClient(authOptions)
-	err := client.Authenticate()
+func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
+	client, err := NewClient(options.IdentityEndpoint)
+	if err != nil {
+		return nil, err
+	}
+
+	err = Authenticate(client, options)
 	if err != nil {
 		return nil, err
 	}
 	return client, nil
 }
 
-// NewClient prepares an unauthenticated Client instance.
-// Most users will probably prefer using the AuthenticatedClient function instead.
-// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
-// for example.
-func NewClient(authOptions gophercloud.AuthOptions) *Client {
-	return &Client{
-		ProviderClient: gophercloud.ProviderClient{
-			Options: authOptions,
-		},
-	}
-}
-
 // Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
-func (client *Client) Authenticate() error {
+func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
 	versions := []*utils.Version{
 		&utils.Version{ID: v20, Priority: 20},
 		&utils.Version{ID: v30, Priority: 30},
 	}
 
-	chosen, endpoint, err := utils.ChooseVersion(client.ProviderClient.Options.IdentityEndpoint, versions)
+	chosen, endpoint, err := utils.ChooseVersion(client.IdentityEndpoint, versions)
 	if err != nil {
 		return err
 	}
 
 	switch chosen.ID {
 	case v20:
-		return client.authenticateV2(endpoint)
+		return errors.New("Not implemented yet.")
 	case v30:
-		return client.authenticateV3(endpoint)
+		// Override the generated service endpoint with the one returned by the version endpoint.
+		v3Client := NewIdentityV3(client)
+		v3Client.Endpoint = endpoint
+
+		result, err := tokens3.Create(v3Client, options, nil)
+		if err != nil {
+			return err
+		}
+
+		client.TokenID, err = result.TokenID()
+		if err != nil {
+			return err
+		}
+
+		return nil
 	default:
 		// The switch statement must be out of date from the versions list.
-		return errors.New("Wat")
+		return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
 	}
 }
 
-// AuthenticateV2 acquires a token explicitly from the v2.0 identity API.
-func (client *Client) AuthenticateV2() error {
-	endpoint := client.ProviderClient.Options.IdentityEndpoint + "/v2.0"
-	return client.authenticateV2(endpoint)
-}
+// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
+func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
+	v2Endpoint := client.IdentityEndpoint + "/v2.0"
 
-func (client *Client) authenticateV2(endpoint string) error {
-	return errors.New("Not implemented yet.")
-}
-
-// AuthenticateV3 acquires a token explicitly from the v3.0 identity API.
-func (client *Client) AuthenticateV3() error {
-	endpoint := client.ProviderClient.Options.IdentityEndpoint + "/v3"
-	return client.authenticateV3(endpoint)
-}
-
-func (client *Client) authenticateV3(endpoint string) error {
-	identityClient := identity3.NewClient(&client.ProviderClient, endpoint)
-	token, err := identityClient.GetToken(client.ProviderClient.Options)
-	if err != nil {
-		return err
+	return &gophercloud.ServiceClient{
+		Provider: client,
+		Endpoint: v2Endpoint,
 	}
-
-	client.ProviderClient.TokenID = token.ID
-
-	return nil
 }
 
-// NewIdentityV3 explicitly accesses the v3 identity service.
-func (client *Client) NewIdentityV3() (*identity3.Client, error) {
-	endpoint := client.ProviderClient.Options.IdentityEndpoint + "/v3"
-	return identity3.NewClient(&client.ProviderClient, endpoint), nil
+// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
+func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
+	v3Endpoint := client.IdentityEndpoint + "/v3"
+
+	return &gophercloud.ServiceClient{
+		Provider: client,
+		Endpoint: v3Endpoint,
+	}
 }
diff --git a/openstack/identity/v3/client.go b/openstack/identity/v3/client.go
deleted file mode 100644
index 96e6f5d..0000000
--- a/openstack/identity/v3/client.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package v3
-
-import (
-	"time"
-
-	"github.com/rackspace/gophercloud"
-	"github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
-)
-
-// Client abstracts the connection information necessary to make API calls to Identity v3 resources.
-// It exists mainly to adhere to the IdentityService interface.
-type Client struct {
-	gophercloud.ServiceClient
-}
-
-// Token models a token acquired from the tokens/ API resource.
-type Token struct {
-	ID        string
-	ExpiresAt time.Time
-}
-
-// NewClient creates a new client associated with the v3 identity service of a provider.
-func NewClient(provider *gophercloud.ProviderClient, endpoint string) *Client {
-	return &Client{
-		ServiceClient: gophercloud.ServiceClient{
-			ProviderClient: *provider,
-			Endpoint:       endpoint,
-		},
-	}
-}
-
-// GetToken provides the supplied credentials to an identity v3 endpoint and attempts to acquire a token.
-func (c *Client) GetToken(authOptions gophercloud.AuthOptions) (*Token, error) {
-	c.ServiceClient.ProviderClient.Options = authOptions
-
-	result, err := tokens.Create(&c.ServiceClient, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	tokenID, err := result.TokenID()
-	if err != nil {
-		return nil, err
-	}
-
-	expiresAt, err := result.ExpiresAt()
-	if err != nil {
-		return nil, err
-	}
-
-	return &Token{ID: tokenID, ExpiresAt: expiresAt}, nil
-}
diff --git a/openstack/identity/v3/client_test.go b/openstack/identity/v3/client_test.go
deleted file mode 100644
index d549f09..0000000
--- a/openstack/identity/v3/client_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package v3
-
-import (
-	"fmt"
-	"net/http"
-	"testing"
-	"time"
-
-	"github.com/rackspace/gophercloud"
-	"github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
-	"github.com/rackspace/gophercloud/testhelper"
-)
-
-func TestNewClient(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
-
-	provider := &gophercloud.ProviderClient{}
-	client := NewClient(provider, testhelper.Endpoint()+"v3/")
-
-	expected := testhelper.Endpoint() + "v3/"
-	if client.Endpoint != expected {
-		t.Errorf("Expected endpoint to be %s, but was %s", expected, client.Endpoint)
-	}
-}
-
-func TestGetToken(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
-	const ID = "aaaa1111"
-
-	testhelper.Mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Add("X-Subject-Token", ID)
-
-		w.WriteHeader(http.StatusCreated)
-		fmt.Fprintf(w, `{ "token": { "expires_at": "2013-02-02T18:30:59.000000Z" } }`)
-	})
-
-	provider := &gophercloud.ProviderClient{}
-	client := NewClient(provider, testhelper.Endpoint()+"v3/")
-
-	token, err := client.GetToken(gophercloud.AuthOptions{UserID: "me", Password: "swordfish"})
-	if err != nil {
-		t.Errorf("Unexpected error from authentication: %v", err)
-	}
-
-	if token.ID != ID {
-		t.Errorf("Expected token ID [%s], but got [%s]", ID, token.ID)
-	}
-
-	expectedExpiration, _ := time.Parse(tokens.RFC3339Milli, "2013-02-02T18:30:59.000000Z")
-	if token.ExpiresAt != expectedExpiration {
-		t.Errorf("Expected token expiration [%v], but got [%v]", expectedExpiration, token.ExpiresAt)
-	}
-}
diff --git a/openstack/identity/v3/doc.go b/openstack/identity/v3/doc.go
deleted file mode 100644
index 8a9335a..0000000
--- a/openstack/identity/v3/doc.go
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
-Package v3 implements v3 of the OpenStack identity API.
-*/
-package v3
diff --git a/openstack/identity/v3/services/requests.go b/openstack/identity/v3/services/requests.go
index a752d57..e9384e1 100644
--- a/openstack/identity/v3/services/requests.go
+++ b/openstack/identity/v3/services/requests.go
@@ -19,7 +19,7 @@
 	var resp response
 
 	_, err := perigee.Request("POST", getListURL(client), perigee.Options{
-		MoreHeaders: client.AuthenticatedHeaders(),
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 		ReqBody:     &req,
 		Results:     &resp,
 		OkCodes:     []int{201},
diff --git a/openstack/identity/v3/services/requests_test.go b/openstack/identity/v3/services/requests_test.go
index d2637c4..6592fc6 100644
--- a/openstack/identity/v3/services/requests_test.go
+++ b/openstack/identity/v3/services/requests_test.go
@@ -31,7 +31,7 @@
 	})
 
 	client := gophercloud.ServiceClient{
-		ProviderClient: gophercloud.ProviderClient{
+		Provider: &gophercloud.ProviderClient{
 			TokenID: "1111",
 		},
 		Endpoint: testhelper.Endpoint(),
diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go
index ab84d81..7bdb548 100644
--- a/openstack/identity/v3/tokens/requests.go
+++ b/openstack/identity/v3/tokens/requests.go
@@ -14,13 +14,13 @@
 }
 
 func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
-	h := c.AuthenticatedHeaders()
+	h := c.Provider.AuthenticatedHeaders()
 	h["X-Subject-Token"] = subjectToken
 	return h
 }
 
 // Create authenticates and either generates a new token, or changes the Scope of an existing token.
-func Create(c *gophercloud.ServiceClient, scope *Scope) (gophercloud.AuthResults, error) {
+func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope *Scope) (gophercloud.AuthResults, error) {
 	type domainReq struct {
 		ID   *string `json:"id,omitempty"`
 		Name *string `json:"name,omitempty"`
@@ -67,44 +67,42 @@
 		Auth authReq `json:"auth"`
 	}
 
-	ao := c.Options
-
 	// Populate the request structure based on the provided arguments. Create and return an error
 	// if insufficient or incompatible information is present.
 	var req request
 
 	// Test first for unrecognized arguments.
-	if ao.APIKey != "" {
+	if options.APIKey != "" {
 		return nil, ErrAPIKeyProvided
 	}
-	if ao.TenantID != "" {
+	if options.TenantID != "" {
 		return nil, ErrTenantIDProvided
 	}
-	if ao.TenantName != "" {
+	if options.TenantName != "" {
 		return nil, ErrTenantNameProvided
 	}
 
-	if ao.Password == "" {
-		if c.TokenID != "" {
+	if options.Password == "" {
+		if c.Provider.TokenID != "" {
 			// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
 			// parameters.
-			if ao.Username != "" {
+			if options.Username != "" {
 				return nil, ErrUsernameWithToken
 			}
-			if ao.UserID != "" {
+			if options.UserID != "" {
 				return nil, ErrUserIDWithToken
 			}
-			if ao.DomainID != "" {
+			if options.DomainID != "" {
 				return nil, ErrDomainIDWithToken
 			}
-			if ao.DomainName != "" {
+			if options.DomainName != "" {
 				return nil, ErrDomainNameWithToken
 			}
 
 			// Configure the request for Token authentication.
 			req.Auth.Identity.Methods = []string{"token"}
 			req.Auth.Identity.Token = &tokenReq{
-				ID: c.TokenID,
+				ID: c.Provider.TokenID,
 			}
 		} else {
 			// If no password or token ID are available, authentication can't continue.
@@ -115,60 +113,60 @@
 		req.Auth.Identity.Methods = []string{"password"}
 
 		// At least one of Username and UserID must be specified.
-		if ao.Username == "" && ao.UserID == "" {
+		if options.Username == "" && options.UserID == "" {
 			return nil, ErrUsernameOrUserID
 		}
 
-		if ao.Username != "" {
+		if options.Username != "" {
 			// If Username is provided, UserID may not be provided.
-			if ao.UserID != "" {
+			if options.UserID != "" {
 				return nil, ErrUsernameOrUserID
 			}
 
 			// Either DomainID or DomainName must also be specified.
-			if ao.DomainID == "" && ao.DomainName == "" {
+			if options.DomainID == "" && options.DomainName == "" {
 				return nil, ErrDomainIDOrDomainName
 			}
 
-			if ao.DomainID != "" {
-				if ao.DomainName != "" {
+			if options.DomainID != "" {
+				if options.DomainName != "" {
 					return nil, ErrDomainIDOrDomainName
 				}
 
 				// Configure the request for Username and Password authentication with a DomainID.
 				req.Auth.Identity.Password = &passwordReq{
 					User: userReq{
-						Name:     &ao.Username,
-						Password: ao.Password,
-						Domain:   &domainReq{ID: &ao.DomainID},
+						Name:     &options.Username,
+						Password: options.Password,
+						Domain:   &domainReq{ID: &options.DomainID},
 					},
 				}
 			}
 
-			if ao.DomainName != "" {
+			if options.DomainName != "" {
 				// Configure the request for Username and Password authentication with a DomainName.
 				req.Auth.Identity.Password = &passwordReq{
 					User: userReq{
-						Name:     &ao.Username,
-						Password: ao.Password,
-						Domain:   &domainReq{Name: &ao.DomainName},
+						Name:     &options.Username,
+						Password: options.Password,
+						Domain:   &domainReq{Name: &options.DomainName},
 					},
 				}
 			}
 		}
 
-		if ao.UserID != "" {
+		if options.UserID != "" {
 			// If UserID is specified, neither DomainID nor DomainName may be.
-			if ao.DomainID != "" {
+			if options.DomainID != "" {
 				return nil, ErrDomainIDWithUserID
 			}
-			if ao.DomainName != "" {
+			if options.DomainName != "" {
 				return nil, ErrDomainNameWithUserID
 			}
 
 			// Configure the request for UserID and Password authentication.
 			req.Auth.Identity.Password = &passwordReq{
-				User: userReq{ID: &ao.UserID, Password: ao.Password},
+				User: userReq{ID: &options.UserID, Password: options.Password},
 			}
 		}
 	}
diff --git a/openstack/identity/v3/tokens/requests_test.go b/openstack/identity/v3/tokens/requests_test.go
index 243a32f..97690a6 100644
--- a/openstack/identity/v3/tokens/requests_test.go
+++ b/openstack/identity/v3/tokens/requests_test.go
@@ -16,8 +16,7 @@
 	defer testhelper.TeardownHTTP()
 
 	client := gophercloud.ServiceClient{
-		ProviderClient: gophercloud.ProviderClient{
-			Options: options,
+		Provider: &gophercloud.ProviderClient{
 			TokenID: "12345abcdef",
 		},
 		Endpoint: testhelper.Endpoint(),
@@ -33,7 +32,7 @@
 		fmt.Fprintf(w, `{}`)
 	})
 
-	_, err := Create(&client, scope)
+	_, err := Create(&client, options, scope)
 	if err != nil {
 		t.Errorf("Create returned an error: %v", err)
 	}
@@ -44,16 +43,14 @@
 	defer testhelper.TeardownHTTP()
 
 	client := gophercloud.ServiceClient{
-		ProviderClient: gophercloud.ProviderClient{
-			Options: options,
-		},
+		Provider: &gophercloud.ProviderClient{},
 		Endpoint: testhelper.Endpoint(),
 	}
 	if includeToken {
-		client.TokenID = "abcdef123456"
+		client.Provider.TokenID = "abcdef123456"
 	}
 
-	_, err := Create(&client, scope)
+	_, err := Create(&client, options, scope)
 	if err == nil {
 		t.Errorf("Create did NOT return an error")
 	}
@@ -245,9 +242,7 @@
 	defer testhelper.TeardownHTTP()
 
 	client := gophercloud.ServiceClient{
-		ProviderClient: gophercloud.ProviderClient{
-			Options: gophercloud.AuthOptions{UserID: "me", Password: "shhh"},
-		},
+		Provider: &gophercloud.ProviderClient{},
 		Endpoint: testhelper.Endpoint(),
 	}
 
@@ -258,7 +253,8 @@
 		fmt.Fprintf(w, `{}`)
 	})
 
-	result, err := Create(&client, nil)
+	options := gophercloud.AuthOptions{UserID: "me", Password: "shhh"}
+	result, err := Create(&client, options, nil)
 	if err != nil {
 		t.Errorf("Create returned an error: %v", err)
 	}
@@ -398,7 +394,7 @@
 	defer testhelper.TeardownHTTP()
 
 	client := gophercloud.ServiceClient{
-		ProviderClient: gophercloud.ProviderClient{
+		Provider: &gophercloud.ProviderClient{
 			TokenID: "12345abcdef",
 		},
 		Endpoint: testhelper.Endpoint(),
@@ -435,7 +431,7 @@
 
 func prepareAuthTokenHandler(t *testing.T, expectedMethod string, status int) gophercloud.ServiceClient {
 	client := gophercloud.ServiceClient{
-		ProviderClient: gophercloud.ProviderClient{
+		Provider: &gophercloud.ProviderClient{
 			TokenID: "12345abcdef",
 		},
 		Endpoint: testhelper.Endpoint(),