Access Microsoft Azure using an OAuth access token

Posted: , Modified:   Go Microsoft Azure OAuth

Summary

How to use OAuth access tokens received from Device authorization for Microsoft Azure from Swagger clients generated in Access Microsoft Azure from Go? It is depended on formats of functions generated by Swagger. This post introduces two types of functions and how to attach OAuth tokens to them.

If methods receive authorization information

Some methods receive authorization information as a parameter. For example, in Resource API (2016-09-01) generated by the following command:

$ swagger generate client \
  -f https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/arm-resources/resources/2016-09-01/swagger/resources.json \
  -t resource

function ResourceGroupsCreateOrUpdate, which creates or updates a resource group, is defines as follows:

func (a *Client) ResourceGroupsCreateOrUpdate(
    params *ResourceGroupsCreateOrUpdateParams,
    authInfo runtime.ClientAuthInfoWriter
) (*ResourceGroupsCreateOrUpdateOK, *ResourceGroupsCreateOrUpdateCreated, error)

In the above definition, type runtime.ClientAuthInfoWriter is defined in go-openapi/runtime as

import     "github.com/go-openapi/strfmt"

type ClientAuthInfoWriter interface {
    AuthenticateRequest(ClientRequest, strfmt.Registry) error
}

Package go-openapi/runtime/client has function BearerToken, which generates a ClientAuthInfoWriter from an OAuth token (BearerToken); and we can use it to make authInfo parameter like

authInfo = httptransport.BearerToken(token)

where token is a string representing the OAuth token.

Note that, there are many client packages and people usually give an alias httptransport for package go-openapi/runtime/client.

The following example creates a runtime.ClientAuthInfoWriter and calls function ResourceGroupsCreateOrUpdate with an OAuth token:

import (
    // The following packages are generated by Swagger
    "github.com/jkawamoto/roadie/cloud/azure/resource/client"
    "github.com/jkawamoto/roadie/cloud/azure/resource/client/resource_groups"
    "github.com/jkawamoto/roadie/cloud/azure/resource/models"

    httptransport "github.com/go-openapi/runtime/client"
    "github.com/go-openapi/strfmt"
)

const (
    // API version
    ResourceAPIVersion = "2016-09-01"
)

// name is a resource group name to be created.
func CreateOrUpdate(ctx context.Context, subscriptionID, name, token string) error{

    // Create a Resource API client.
    cli := client.NewHTTPClient(strfmt.NewFormats())

    // Set a location.
    location := "westus2"

    // cli has several sub-clients for each API group.
    // We now use a client for ResourceGroups API.
    created, creating, err := cli.ResourceGroups.ResourceGroupsCreateOrUpdate(
        resource_groups.NewResourceGroupsCreateOrUpdateParamsWithContext(ctx).
            WithAPIVersion(ResourceAPIVersion).
            WithSubscriptionID(subscriptionID).
            WithResourceGroupName(name).
            WithParameters(⊧.ResourceGroup{
                Location: &location,
            }), httptransport.BearerToken(token))

    if err != nil {
        return err
    }

    // If the resource group has been created, created has non-nil value,
    // if the resource group is now being created, creating has non-nil value.

    // the rest is omitted.

}

The parameters of ResourceGroupsCreateOrUpdateParams look tricky but they aren’t complicated actually.

If methods don’t receive authorization information

Different from the above method, some methods don’t receive any authoriazation information as parameters. In this case, we need to override transporters and add token information to HTTP requests as a HTTP header.

More precisely, we need to create a runtime.ClientAuthInfoWriter by function BearerToken; and set it to Transport.DefaultAuthentication of an API client made by client.NewHTTPClient(strfmt.NewFormats()), as we can see in the following code:

// Create an API client
cli := client.NewHTTPClient(strfmt.NewFormats())

// Cast the transporter of the client to *httptransport.Runtime;
// and set ClientAuthInfoWriter.
switch transport := cli.Transport.(type) {
case *httptransport.Runtime:

  transport.DefaultAuthentication = httptransport.BearerToken(token)

  // Set the transporter to sub clients.
  cli.ResourceGroups.SetTransport(transport)

}

In the above code, don’t forget to set the transporter not only to the client made by client.NewHTTPClient but also to sub clients using function SetTransport.

The Resource API’s client is defined as

type ResourceManagementClient struct {

  DeploymentOperations *deployment_operations.Client

  Deployments *deployments.Client

  Providers *providers.Client

  ResourceGroups *resource_groups.Client

  Resources *resources.Client

  Tags *tags.Client

  Transport runtime.ClientTransport

}

and has six sub clients. We updated ResourceGroup’s transporter in the above example because we are interested in to call ResourceGroup’s function ResourceGroupsCreateOrUpdateParams. If we want to call other functions, we need to update associated sub clients’ transporters.