Authentication at the identity client level.
diff --git a/openstack/identity/v3/client.go b/openstack/identity/v3/client.go
index 8c6e3e4..5f25ee6 100644
--- a/openstack/identity/v3/client.go
+++ b/openstack/identity/v3/client.go
@@ -4,11 +4,14 @@
 	"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 gophercloud.ServiceClient
+type Client struct {
+	gophercloud.ServiceClient
+}
 
 // Token models a token acquired from the tokens/ API resource.
 type Token struct {
@@ -19,12 +22,34 @@
 // NewClient creates a new client associated with the v3 identity service of a provider.
 func NewClient(provider *gophercloud.ProviderClient) *Client {
 	return &Client{
-		ProviderClient: *provider,
-		Endpoint:       provider.IdentityEndpoint + "v3/",
+		ServiceClient: gophercloud.ServiceClient{
+			ProviderClient: *provider,
+			Endpoint:       provider.IdentityEndpoint + "v3/",
+		},
 	}
 }
 
 // Authenticate provides the supplied credentials to an identity v3 endpoint and attempts to acquire a token.
 func (c *Client) Authenticate(authOptions gophercloud.AuthOptions) (*Token, error) {
-	return nil, nil
+	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
index 7c57e68..0744e7d 100644
--- a/openstack/identity/v3/client_test.go
+++ b/openstack/identity/v3/client_test.go
@@ -4,17 +4,35 @@
 	"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{
+		IdentityEndpoint: testhelper.Endpoint(),
+	}
+	client := NewClient(provider)
+
+	expected := testhelper.Endpoint() + "v3/"
+	if client.Endpoint != expected {
+		t.Errorf("Expected endpoint to be %s, but was %s", expected, client.Endpoint)
+	}
+}
+
 func TestAuthentication(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", "aaaa1111")
+		w.Header().Add("X-Subject-Token", ID)
 
 		w.WriteHeader(http.StatusCreated)
 		fmt.Fprintf(w, `{ "token": { "expires_at": "2013-02-02T18:30:59.000000Z" } }`)
@@ -25,8 +43,17 @@
 	}
 	client := NewClient(provider)
 
-	expected := testhelper.Endpoint() + "v3/"
-	if client.Endpoint != expected {
-		t.Errorf("Expected endpoint to be %s, but was %s", expected, client.Endpoint)
+	token, err := client.Authenticate(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)
 	}
 }