Re-implement basic identity API for v0.2.0.
diff --git a/openstack/identity/common_test.go b/openstack/identity/common_test.go
index 81d37cc..95a2ccb 100644
--- a/openstack/identity/common_test.go
+++ b/openstack/identity/common_test.go
@@ -88,3 +88,87 @@
         ]
     }
 }`
+
+// Taken from: http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listExtensions_v2.0_extensions_.html#GET_listExtensions_v2.0_extensions_-Request
+const queryResults = `{
+    "extensions":[{
+            "name": "Reset Password Extension",
+            "namespace": "http://docs.rackspacecloud.com/identity/api/ext/rpe/v2.0",
+            "alias": "RS-RPE",
+            "updated": "2011-01-22T13:25:27-06:00",
+            "description": "Adds the capability to reset a user's password. The user is emailed when the password has been reset.",
+            "links":[{
+                    "rel": "describedby",
+                    "type": "application/pdf",
+                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-rpe-20111111.pdf"
+                },
+                {
+                    "rel": "describedby",
+                    "type": "application/vnd.sun.wadl+xml",
+                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-rpe.wadl"
+                }
+            ]
+        },
+        {
+            "name": "User Metadata Extension",
+            "namespace": "http://docs.rackspacecloud.com/identity/api/ext/meta/v2.0",
+            "alias": "RS-META",
+            "updated": "2011-01-12T11:22:33-06:00",
+            "description": "Allows associating arbritrary metadata with a user.",
+            "links":[{
+                    "rel": "describedby",
+                    "type": "application/pdf",
+                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-meta-20111201.pdf"
+                },
+                {
+                    "rel": "describedby",
+                    "type": "application/vnd.sun.wadl+xml",
+                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-meta.wadl"
+                }
+            ]
+        }
+    ],
+    "extensions_links":[]
+}`
+
+// Same as queryResults above, but with a bogus JSON envelop.
+const bogusExtensionsResults = `{
+    "explosions":[{
+            "name": "Reset Password Extension",
+            "namespace": "http://docs.rackspacecloud.com/identity/api/ext/rpe/v2.0",
+            "alias": "RS-RPE",
+            "updated": "2011-01-22T13:25:27-06:00",
+            "description": "Adds the capability to reset a user's password. The user is emailed when the password has been reset.",
+            "links":[{
+                    "rel": "describedby",
+                    "type": "application/pdf",
+                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-rpe-20111111.pdf"
+                },
+                {
+                    "rel": "describedby",
+                    "type": "application/vnd.sun.wadl+xml",
+                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-rpe.wadl"
+                }
+            ]
+        },
+        {
+            "name": "User Metadata Extension",
+            "namespace": "http://docs.rackspacecloud.com/identity/api/ext/meta/v2.0",
+            "alias": "RS-META",
+            "updated": "2011-01-12T11:22:33-06:00",
+            "description": "Allows associating arbritrary metadata with a user.",
+            "links":[{
+                    "rel": "describedby",
+                    "type": "application/pdf",
+                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-meta-20111201.pdf"
+                },
+                {
+                    "rel": "describedby",
+                    "type": "application/vnd.sun.wadl+xml",
+                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-meta.wadl"
+                }
+            ]
+        }
+    ],
+    "extensions_links":[]
+}`
diff --git a/openstack/identity/doc.go b/openstack/identity/doc.go
new file mode 100644
index 0000000..081950d
--- /dev/null
+++ b/openstack/identity/doc.go
@@ -0,0 +1,120 @@
+/*
+The Identity package provides convenient OpenStack Identity V2 API client access.
+This package currently doesn't support the administrative access endpoints, but may appear in the future based on demand.
+
+Authentication
+
+Established convention in the OpenStack community suggests the use of environment variables to hold authentication parameters.
+For example, the following settings would be sufficient to authenticate against Rackspace:
+
+	# assumes Bash shell on a POSIX environment; use SET command for Windows.
+	export OS_AUTH_URL=https://identity.api.rackspacecloud.com/v2.0
+	export OS_USERNAME=xxxx
+	export OS_PASSWORD=yyyy
+
+while you'd need these additional settings to authenticate against, e.g., Nebula One:
+
+	export OS_TENANT_ID=zzzz
+	export OS_TENANT_NAME=wwww
+
+Be sure to consult with your provider to see which settings you'll need to authenticate with.
+
+A skeletal client gets started with Gophercloud by authenticating against his/her provider, like so:
+
+	package main
+
+	import (
+		"fmt"
+		"github.com/rackspace/gophercloud/openstack/identity"
+		"github.com/rackspace/gophercloud/openstack/utils"
+	)
+
+	func main() {
+		// Create an initialized set of authentication options based on available OS_*
+		// environment variables.
+		ao, err := utils.AuthOptions()
+		if err != nil {
+			panic(err)
+		}
+
+		// Attempt to authenticate with them.
+		r, err := identity.Authenticate(ao)
+		if err != nil {
+			panic(err)
+		}
+
+		// With each authentication, you receive a master directory of all the services
+		// your account can access.  This "service catalog", as OpenStack calls it,
+		// provides you the means to exploit other OpenStack services.
+		sc, err := identity.GetServiceCatalog(r)
+		if err != nil {
+			panic(err)
+		}
+
+		// Find the desired service(s) for our application.
+		computeService, err := findService(sc, "compute", ...)
+		if err != nil {
+			panic(err)
+		}
+
+		blockStorage, err := findService(sc, "block-storage", ...)
+		if err != nil {
+			panic(err)
+		}
+
+		// ... etc ...
+	}
+
+NOTE!
+Unlike versions 0.1.x of the Gophercloud API,
+0.2.0 and later will not provide a service look-up mechanism as a built-in feature of the Identity SDK binding.
+The 0.1.x behavior potentially opened its non-US users to legal liability by potentially selecting endpoints in undesirable regions
+in a non-obvious manner if a specific region was not explicitly specified.
+Starting with 0.2.0 and beyond, you'll need to use either your own service catalog query function or one in a separate package.
+This makes it plainly visible to a code auditor that if you indeed desired automatic selection of an arbitrary region,
+you made the conscious choice to use that feature.
+
+Extensions
+
+Some OpenStack deployments may support features that other deployments do not.
+Anything beyond the scope of standard OpenStack must be scoped by an "extension," a named, yet well-known, change to the API.
+Users may invoke IsExtensionAvailable() after grabbing a list of extensions from the server with GetExtensions().
+This of course assumes you know the name of the extension ahead of time.
+
+Here's a simple example of listing all the aliases for supported extensions.
+Once you have an alias to an extension, everything else about it may be queried through accessors.
+
+	package main
+
+	import (
+		"fmt"
+		"github.com/rackspace/gophercloud/openstack/identity"
+		"github.com/rackspace/gophercloud/openstack/utils"
+	)
+
+	func main() {
+		// Create an initialized set of authentication options based on available OS_*
+		// environment variables.
+		ao, err := utils.AuthOptions()
+		if err != nil {
+			panic(err)
+		}
+
+		// Attempt to query extensions.
+		exts, err := identity.GetExtensions(ao)
+		if err != nil {
+			panic(err)
+		}
+
+		// Print out a summary of supported extensions
+		aliases, err := exts.Aliases()
+		if err != nil {
+			panic(err)
+		}
+		fmt.Println("Extension Aliases:")
+		for _, alias := range aliases {
+			fmt.Printf("  %s\n", alias)
+		}
+	}
+*/
+package identity
diff --git a/openstack/identity/errors.go b/openstack/identity/errors.go
index b15a4a2..efa7c85 100644
--- a/openstack/identity/errors.go
+++ b/openstack/identity/errors.go
@@ -2,6 +2,16 @@
 
 import "fmt"
 
-var ErrNotImplemented = fmt.Errorf("Identity feature not yet implemented")
-var ErrEndpoint = fmt.Errorf("Improper or missing Identity endpoint")
-var ErrCredentials = fmt.Errorf("Improper or missing Identity credentials")
+// ErrNotImplemented errors may occur in two contexts:
+// (1) development versions of this package may return this error for endpoints which are defined but not yet completed, and,
+// (2) production versions of this package may return this error when a provider fails to offer the requested Identity extension.
+//
+// ErrEndpoint errors occur when the authentication URL provided to Authenticate() either isn't valid
+// or the endpoint provided doesn't respond like an Identity V2 API endpoint should.
+//
+// ErrCredentials errors occur when authentication fails due to the caller possessing insufficient access privileges.
+var (
+	ErrNotImplemented = fmt.Errorf("Identity feature not yet implemented")
+	ErrEndpoint       = fmt.Errorf("Improper or missing Identity endpoint")
+	ErrCredentials    = fmt.Errorf("Improper or missing Identity credentials")
+)
diff --git a/openstack/identity/extensions.go b/openstack/identity/extensions.go
index 4a861ff..cfd8f24 100644
--- a/openstack/identity/extensions.go
+++ b/openstack/identity/extensions.go
@@ -1,14 +1,18 @@
 package identity
 
 import (
-	"fmt"
 	"github.com/mitchellh/mapstructure"
 )
 
-var (
-	ErrNotFound = fmt.Errorf("Identity extension not found")
-)
-
+// ExtensionDetails provides the details offered by the OpenStack Identity V2 extensions API
+// for a named extension.
+//
+// Name provides the name, presumably the same as that used to query the API with.
+//
+// Updated provides, in a sense, the version of the extension supported.  It gives the timestamp
+// of the most recent extension deployment.
+//
+// Description provides a more customer-oriented description of the extension.
 type ExtensionDetails struct {
 	Name        string
 	Namespace   string
@@ -16,37 +20,71 @@
 	Description string
 }
 
-// ExtensionsDesc structures are returned by the Extensions() function for valid input.
-// This structure implements the ExtensionInquisitor interface.
-type ExtensionsDesc struct {
-	extensions []interface{}
-}
+// ExtensionsResult encapsulates the raw data returned by a call to
+// GetExtensions().  As OpenStack extensions may freely alter the response
+// bodies of structures returned to the client, you may only safely access the
+// data provided through separate, type-safe accessors or methods.
+type ExtensionsResult map[string]interface{}
 
-func Extensions(m map[string]interface{}) *ExtensionsDesc {
-	return &ExtensionsDesc{extensions: m["extensions"].([]interface{})}
-}
-
-func extensionIndexByAlias(e *ExtensionsDesc, alias string) (int, error) {
-	for i, ee := range e.extensions {
-		extensionRecord := ee.(map[string]interface{})
-		if extensionRecord["alias"] == alias {
-			return i, nil
-		}
+// IsExtensionAvailable returns true if and only if the provider supports the named extension.
+func (er ExtensionsResult) IsExtensionAvailable(alias string) bool {
+	e, err := extensions(er)
+	if err != nil {
+		return false
 	}
-	return 0, ErrNotFound
-}
-
-func (e *ExtensionsDesc) IsExtensionAvailable(alias string) bool {
-	_, err := extensionIndexByAlias(e, alias)
+	_, err = extensionIndexByAlias(e, alias)
 	return err == nil
 }
 
-func (e *ExtensionsDesc) ExtensionDetailsByAlias(alias string) (*ExtensionDetails, error) {
+// ExtensionDetailsByAlias returns more detail than the mere presence of an extension by the provider.
+// See the ExtensionDetails structure.
+func (er ExtensionsResult) ExtensionDetailsByAlias(alias string) (*ExtensionDetails, error) {
+	e, err := extensions(er)
+	if err != nil {
+		return nil, err
+	}
 	i, err := extensionIndexByAlias(e, alias)
 	if err != nil {
 		return nil, err
 	}
 	ed := &ExtensionDetails{}
-	err = mapstructure.Decode(e.extensions[i], ed)
+	err = mapstructure.Decode(e[i], ed)
 	return ed, err
 }
+
+func extensionIndexByAlias(records []interface{}, alias string) (int, error) {
+	for i, er := range records {
+		extensionRecord := er.(map[string]interface{})
+		if extensionRecord["alias"] == alias {
+			return i, nil
+		}
+	}
+	return 0, ErrNotImplemented
+}
+
+func extensions(er ExtensionsResult) ([]interface{}, error) {
+	e, ok := er["extensions"]
+	if !ok {
+		return nil, ErrNotImplemented
+	}
+	return e.([]interface{}), nil
+}
+
+// Aliases returns the set of extension handles, or "aliases" as OpenStack calls them.
+// These are not the names of the extensions, but rather opaque, symbolic monikers for their corresponding extension.
+// Use the ExtensionDetailsByAlias() method to query more information for an extension if desired.
+func (er ExtensionsResult) Aliases() ([]string, error) {
+	e, err := extensions(er)
+	if err != nil {
+		return nil, err
+	}
+	aliases := make([]string, len(e))
+	for i, ex := range e {
+		ext := ex.(map[string]interface{})
+		extn, ok := ext["alias"]
+		if ok {
+			aliases[i] = extn.(string)
+		}
+	}
+	return aliases, nil
+}
diff --git a/openstack/identity/extensions_test.go b/openstack/identity/extensions_test.go
index a5eede2..009d29d 100644
--- a/openstack/identity/extensions_test.go
+++ b/openstack/identity/extensions_test.go
@@ -5,48 +5,6 @@
 	"testing"
 )
 
-// Taken from: http://docs.openstack.org/api/openstack-identity-service/2.0/content/GET_listExtensions_v2.0_extensions_.html#GET_listExtensions_v2.0_extensions_-Request
-const queryResults = `{
-    "extensions":[{
-            "name": "Reset Password Extension",
-            "namespace": "http://docs.rackspacecloud.com/identity/api/ext/rpe/v2.0",
-            "alias": "RS-RPE",
-            "updated": "2011-01-22T13:25:27-06:00",
-            "description": "Adds the capability to reset a user's password. The user is emailed when the password has been reset.",
-            "links":[{
-                    "rel": "describedby",
-                    "type": "application/pdf",
-                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-rpe-20111111.pdf"
-                },
-                {
-                    "rel": "describedby",
-                    "type": "application/vnd.sun.wadl+xml",
-                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-rpe.wadl"
-                }
-            ]
-        },
-        {
-            "name": "User Metadata Extension",
-            "namespace": "http://docs.rackspacecloud.com/identity/api/ext/meta/v2.0",
-            "alias": "RS-META",
-            "updated": "2011-01-12T11:22:33-06:00",
-            "description": "Allows associating arbritrary metadata with a user.",
-            "links":[{
-                    "rel": "describedby",
-                    "type": "application/pdf",
-                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-meta-20111201.pdf"
-                },
-                {
-                    "rel": "describedby",
-                    "type": "application/vnd.sun.wadl+xml",
-                    "href": "http://docs.rackspacecloud.com/identity/api/ext/identity-meta.wadl"
-                }
-            ]
-        }
-    ],
-    "extensions_links":[]
-}`
-
 func TestIsExtensionAvailable(t *testing.T) {
 	// Make a response as we'd expect from the IdentityService.GetExtensions() call.
 	getExtensionsResults := make(map[string]interface{})
@@ -56,7 +14,7 @@
 		return
 	}
 
-	e := Extensions(getExtensionsResults)
+	e := ExtensionsResult(getExtensionsResults)
 	for _, alias := range []string{"RS-RPE", "RS-META"} {
 		if !e.IsExtensionAvailable(alias) {
 			t.Errorf("Expected extension %s present.", alias)
@@ -78,7 +36,7 @@
 		return
 	}
 
-	e := Extensions(getExtensionsResults)
+	e := ExtensionsResult(getExtensionsResults)
 	ed, err := e.ExtensionDetailsByAlias("RS-META")
 	if err != nil {
 		t.Error(err)
@@ -106,3 +64,48 @@
 		}
 	}
 }
+
+func TestMalformedResponses(t *testing.T) {
+	getExtensionsResults := make(map[string]interface{})
+	err := json.Unmarshal([]byte(bogusExtensionsResults), &getExtensionsResults)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	e := ExtensionsResult(getExtensionsResults)
+
+	_, err = e.ExtensionDetailsByAlias("RS-META")
+	if err == nil {
+		t.Error("Expected ErrNotImplemented at least")
+		return
+	}
+	if err != ErrNotImplemented {
+		t.Error("Expected ErrNotImplemented")
+		return
+	}
+
+	if e.IsExtensionAvailable("anything at all") {
+		t.Error("No extensions are available with a bogus result.")
+		return
+	}
+}
+
+func TestAliases(t *testing.T) {
+	getExtensionsResults := make(map[string]interface{})
+	err := json.Unmarshal([]byte(queryResults), &getExtensionsResults)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	e := ExtensionsResult(getExtensionsResults)
+	aliases, err := e.Aliases()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if len(aliases) != len(e) {
+		t.Error("Expected one alias name per extension")
+		return
+	}
+}
diff --git a/openstack/identity/requests.go b/openstack/identity/requests.go
index 0012a17..dbd367e 100644
--- a/openstack/identity/requests.go
+++ b/openstack/identity/requests.go
@@ -4,42 +4,56 @@
 	"github.com/racker/perigee"
 )
 
+// AuthResults encapsulates the raw results from an authentication request.
+// As OpenStack allows extensions to influence the structure returned in
+// ways that Gophercloud cannot predict at compile-time, you should use
+// type-safe accessors to work with the data represented by this type,
+// such as ServiceCatalog() and Token().
 type AuthResults map[string]interface{}
 
-// AuthOptions lets anyone calling Authenticate() supply the required access credentials.
-// At present, only Identity V2 API support exists; therefore, only Username, Password,
-// and optionally, TenantId are provided.  If future Identity API versions become available,
-// alternative fields unique to those versions may appear here.
+// AuthOptions lets anyone calling Authenticate() supply the required access
+// credentials.  At present, only Identity V2 API support exists; therefore,
+// only Username, Password, and optionally, TenantId are provided.  If future
+// Identity API versions become available, alternative fields unique to those
+// versions may appear here.
+//
+// Endpoint specifies the HTTP endpoint offering the Identity V2 API.
+// Required.
+//
+// Username is required if using Identity V2 API.  Consult with your provider's
+// control panel to discover your account's username.
+//
+// At most one of Password or ApiKey is required if using Identity V2 API.
+// Consult with your provider's control panel to discover your account's
+// preferred method of authentication.
+//
+// The TenantId and TenantName fields are optional for the Identity V2 API.
+// Some providers allow you to specify a TenantName instead of the TenantId.
+// Some require both.  Your provider's authentication policies will determine
+// how these fields influence authentication.
+//
+// AllowReauth should be set to true if you grant permission for Gophercloud to
+// cache your credentials in memory, and to allow Gophercloud to attempt to
+// re-authenticate automatically if/when your token expires.  If you set it to
+// false, it will not cache these settings, but re-authentication will not be
+// possible.  This setting defaults to false.
 type AuthOptions struct {
-	// Endpoint specifies the HTTP endpoint offering the Identity V2 API.
-	// Required.
-	Endpoint string
-
-	// Username is required if using Identity V2 API.
-	// Consult with your provider's control panel to discover your
-	// account's username.
-	Username string
-
-	// At most one of Password or ApiKey is required if using Identity V2 API.
-	// Consult with your provider's control panel to discover your
-	// account's preferred method of authentication.
+	Endpoint         string
+	Username         string
 	Password, ApiKey string
-
-	// The TenantId field is optional for the Identity V2 API.
-	TenantId string
-
-	// The TenantName can be specified instead of the TenantId
-	TenantName string
-
-	// AllowReauth should be set to true if you grant permission for Gophercloud to cache
-	// your credentials in memory, and to allow Gophercloud to attempt to re-authenticate
-	// automatically if/when your token expires.  If you set it to false, it will not cache
-	// these settings, but re-authentication will not be possible.  This setting defaults
-	// to false.
-	AllowReauth bool
+	TenantId         string
+	TenantName       string
+	AllowReauth      bool
 }
 
+// Authenticate passes the supplied credentials to the OpenStack provider for authentication.
+// If successful, the caller may use Token() to retrieve the authentication token,
+// and ServiceCatalog() to retrieve the set of services available to the API user.
 func Authenticate(options AuthOptions) (AuthResults, error) {
+	type AuthContainer struct {
+		Auth auth `json:"auth"`
+	}
+
 	var ar AuthResults
 
 	if options.Endpoint == "" {
@@ -50,7 +64,8 @@
 		return nil, ErrCredentials
 	}
 
-	err := perigee.Post(options.Endpoint, perigee.Options{
+	url := options.Endpoint + "/tokens"
+	err := perigee.Post(url, perigee.Options{
 		ReqBody: &AuthContainer{
 			Auth: getAuthCredentials(options),
 		},
@@ -59,9 +74,9 @@
 	return ar, err
 }
 
-func getAuthCredentials(options AuthOptions) Auth {
+func getAuthCredentials(options AuthOptions) auth {
 	if options.ApiKey == "" {
-		return Auth{
+		return auth{
 			PasswordCredentials: &struct {
 				Username string `json:"username"`
 				Password string `json:"password"`
@@ -73,7 +88,7 @@
 			TenantName: options.TenantName,
 		}
 	} else {
-		return Auth{
+		return auth{
 			ApiKeyCredentials: &struct {
 				Username string `json:"username"`
 				ApiKey   string `json:"apiKey"`
@@ -87,17 +102,19 @@
 	}
 }
 
-// AuthContainer provides a JSON encoding wrapper for passing credentials to the Identity
-// service.  You will not work with this structure directly.
-type AuthContainer struct {
-	Auth Auth `json:"auth"`
-}
-
-// Auth provides a JSON encoding wrapper for passing credentials to the Identity
-// service.  You will not work with this structure directly.
-type Auth struct {
+type auth struct {
 	PasswordCredentials interface{} `json:"passwordCredentials,omitempty"`
 	ApiKeyCredentials   interface{} `json:"RAX-KSKEY:apiKeyCredentials,omitempty"`
 	TenantId            string      `json:"tenantId,omitempty"`
 	TenantName          string      `json:"tenantName,omitempty"`
 }
+
+func GetExtensions(options AuthOptions) (ExtensionsResult, error) {
+	var exts ExtensionsResult
+
+	url := options.Endpoint + "/extensions"
+	err := perigee.Get(url, perigee.Options{
+		Results: &exts,
+	})
+	return exts, err
+}
diff --git a/openstack/identity/service_catalog.go b/openstack/identity/service_catalog.go
index af6bbdb..035f671 100644
--- a/openstack/identity/service_catalog.go
+++ b/openstack/identity/service_catalog.go
@@ -2,16 +2,43 @@
 
 import "github.com/mitchellh/mapstructure"
 
-type ServiceCatalogDesc struct {
+// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
+// OpenStack extensions may alter the structure of the service catalog in ways unpredictable to Go at compile-time,
+// so this structure serves as a convenient anchor for type-safe accessors and methods.
+type ServiceCatalog struct {
 	serviceDescriptions []interface{}
 }
 
+// CatalogEntry provides a type-safe interface to an Identity API V2 service
+// catalog listing.  Each class of service, such as cloud DNS or block storage
+// services, will have a single CatalogEntry representing it.
+//
+// Name will contain the provider-specified name for the service.
+//
+// If OpenStack defines a type for the service, this field will contain that
+// type string.  Otherwise, for provider-specific services, the provider may
+// assign their own type strings.
+//
+// Endpoints will let the caller iterate over all the different endpoints that
+// may exist for the service.
+//
+// Note: when looking for the desired service, try, whenever possible, to key
+// off the type field.  Otherwise, you'll tie the representation of the service
+// to a specific provider.
 type CatalogEntry struct {
 	Name      string
 	Type      string
 	Endpoints []Endpoint
 }
 
+// Endpoint represents a single API endpoint offered by a service.
+// It provides the public and internal URLs, if supported, along with a region specifier, again if provided.
+// The significance of the Region field will depend upon your provider.
+//
+// In addition, the interface offered by the service will have version information associated with it
+// through the VersionId, VersionInfo, and VersionList fields, if provided or supported.
+//
+// In all cases, fields which aren't supported by the provider and service combined will assume a zero-value ("").
 type Endpoint struct {
 	TenantId    string
 	PublicURL   string
@@ -22,20 +49,27 @@
 	VersionList string
 }
 
-func ServiceCatalog(ar AuthResults) (*ServiceCatalogDesc, error) {
+// GetServiceCatalog acquires the service catalog from a successful authentication's results.
+func GetServiceCatalog(ar AuthResults) (*ServiceCatalog, error) {
 	access := ar["access"].(map[string]interface{})
 	sds := access["serviceCatalog"].([]interface{})
-	sc := &ServiceCatalogDesc{
+	sc := &ServiceCatalog{
 		serviceDescriptions: sds,
 	}
 	return sc, nil
 }
 
-func (sc *ServiceCatalogDesc) NumberOfServices() int {
+// NumberOfServices yields the number of services the caller may use.  Note
+// that this does not necessarily equal the number of endpoints available for
+// use.
+func (sc *ServiceCatalog) NumberOfServices() int {
 	return len(sc.serviceDescriptions)
 }
 
-func (sc *ServiceCatalogDesc) CatalogEntries() ([]CatalogEntry, error) {
+// CatalogEntries returns a slice of service catalog entries.
+// Each entry corresponds to a specific class of service offered by the API provider.
+// See the CatalogEntry structure for more details.
+func (sc *ServiceCatalog) CatalogEntries() ([]CatalogEntry, error) {
 	var err error
 	ces := make([]CatalogEntry, sc.NumberOfServices())
 	for i, sd := range sc.serviceDescriptions {
diff --git a/openstack/identity/service_catalog_test.go b/openstack/identity/service_catalog_test.go
index 82e4d9a..f810609 100644
--- a/openstack/identity/service_catalog_test.go
+++ b/openstack/identity/service_catalog_test.go
@@ -13,7 +13,7 @@
 		return
 	}
 
-	sc, err := ServiceCatalog(authResults)
+	sc, err := GetServiceCatalog(authResults)
 	if err != nil {
 		panic(err)
 	}
diff --git a/openstack/identity/token.go b/openstack/identity/token.go
index aaea184..d50cce0 100644
--- a/openstack/identity/token.go
+++ b/openstack/identity/token.go
@@ -4,40 +4,69 @@
 	"github.com/mitchellh/mapstructure"
 )
 
-type TenantDesc struct {
-	Id   string
-	Name string
+// Token provides only the most basic information related to an authentication token.
+//
+// Id provides the primary means of identifying a user to the OpenStack API.
+// OpenStack defines this field as an opaque value, so do not depend on its content.
+// It is safe, however, to compare for equality.
+//
+// Expires provides a timestamp in ISO 8601 format, indicating when the authentication token becomes invalid.
+// After this point in time, future API requests made using this authentication token will respond with errors.
+// Either the caller will need to reauthenticate manually, or more preferably, the caller should exploit automatic re-authentication.
+// See the AuthOptions structure for more details.
+//
+// TenantId provides the canonical means of identifying a tenant.
+// As with Id, this field is defined to be opaque, so do not depend on its content.
+// It is safe, however, to compare for equality.
+//
+// TenantName provides a human-readable tenant name corresponding to the TenantId.
+type Token struct {
+	Id, Expires          string
+	TenantId, TenantName string
 }
 
-type TokenDesc struct {
-	Id_      string `mapstructure:"Id"`
-	Expires_ string `mapstructure:"Expires"`
-	Tenant   TenantDesc
-}
+// GetToken, if successful, yields an unpacked collection of fields related to the user's access credentials, called a "token."
+// See the Token structure for more details.
+func GetToken(m AuthResults) (*Token, error) {
+	type (
+		Tenant struct {
+			Id   string
+			Name string
+		}
 
-func Token(m AuthResults) (*TokenDesc, error) {
-	accessMap := m["access"].(map[string]interface{})
-	tokenMap := accessMap["token"].(map[string]interface{})
-	td := &TokenDesc{}
-	err := mapstructure.Decode(tokenMap, td)
+		TokenDesc struct {
+			Id      string `mapstructure:"id"`
+			Expires string `mapstructure:"expires"`
+			Tenant
+		}
+	)
+
+	accessMap, err := getSubmap(m, "access")
 	if err != nil {
 		return nil, err
 	}
+	tokenMap, err := getSubmap(accessMap, "token")
+	if err != nil {
+		return nil, err
+	}
+	t := &TokenDesc{}
+	err = mapstructure.Decode(tokenMap, t)
+	if err != nil {
+		return nil, err
+	}
+	td := &Token{
+		Id:         t.Id,
+		Expires:    t.Expires,
+		TenantId:   t.Tenant.Id,
+		TenantName: t.Tenant.Name,
+	}
 	return td, nil
 }
 
-func (td *TokenDesc) Id() string {
-	return td.Id_
-}
-
-func (td *TokenDesc) Expires() string {
-	return td.Expires_
-}
-
-func (td *TokenDesc) TenantId() string {
-	return td.Tenant.Id
-}
-
-func (td *TokenDesc) TenantName() string {
-	return td.Tenant.Name
+func getSubmap(m map[string]interface{}, name string) (map[string]interface{}, error) {
+	entry, ok := m[name]
+	if !ok {
+		return nil, ErrNotImplemented
+	}
+	return entry.(map[string]interface{}), nil
 }
diff --git a/openstack/identity/token_test.go b/openstack/identity/token_test.go
index 4fdf953..5e96496 100644
--- a/openstack/identity/token_test.go
+++ b/openstack/identity/token_test.go
@@ -13,13 +13,13 @@
 		return
 	}
 
-	tok, err := Token(authResults)
+	tok, err := GetToken(authResults)
 	if err != nil {
 		t.Error(err)
 		return
 	}
-	if tok.Id() != "ab48a9efdfedb23ty3494" {
-		t.Errorf("Expected token \"ab48a9efdfedb23ty3494\"; got \"%s\" instead", tok.Id())
+	if tok.Id != "ab48a9efdfedb23ty3494" {
+		t.Errorf("Expected token \"ab48a9efdfedb23ty3494\"; got \"%s\" instead", tok.Id)
 		return
 	}
 }