OAuth2 client credentials implementations

This is just a collection of functions written in different languages to create an access_token using your client credentials. It is meant to help people who are starting now with the API.

There are very good community created libraries that already deal with this OAuth flow and all the endpoint requests. Use this only if you want to create your own implementation or understand better how it works.

PHP

function createAccessToken($apiKey, $apiSecret, $region = 'us') {
  $curl_handle = curl_init();
  try {
    curl_setopt($curl_handle, CURLOPT_URL, "https://$region.battle.net/oauth/token");
    curl_setopt($curl_handle, CURLOPT_POSTFIELDS, ['grant_type' => 'client_credentials']);
    curl_setopt($curl_handle, CURLOPT_USERPWD, $apiKey . ':' . $apiSecret);
    curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl_handle, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data']);
    $response = curl_exec($curl_handle);
    $status = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE);
      
    if ($status !== 200) {
      throw new Exception('Failed to create client_credentials access token.');
    }
    return json_decode($response)->access_token;
  } finally {
    curl_close($curl_handle);
  }
}

GScript (Google docs/spreadsheets)

function createAccessToken(region, ApiKey, ApiSecret) {
  var formData = {
    'grant_type': 'client_credentials'
  };

  var options = {
    'method': 'post',
    'payload': formData,
    'headers': {
      'Authorization': 'Basic ' + Utilities.base64Encode(ApiKey + ':' + ApiSecret)
    }
  };
  var url = 'https://' + region + '.battle.net/oauth/token';
  var response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText()).access_token;
}

Ruby

def create_access_token(client_id, client_secret, region = 'us')
  uri = URI.parse("https://#{region}.battle.net/oauth/token")

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri.path)
  request.basic_auth(client_id, client_secret)
  request['Content-Type'] = 'application/x-www-form-urlencoded'
  request.set_form_data grant_type: 'client_credentials'

  response = http.request(request)
  JSON.parse(response.body)['access_token']
end

Go

type Token struct {
	AccessToken string `json:"access_token"`
	TokenType   string `json:"token_type"`
	ExpiresIn   int    `json:"expires_in"`
	Scope       string `json:"scope"`
}

func CreateAccessToken(clientID string, clientSecret string, region string) string {
	requestURL := fmt.Sprintf("https://%s.battle.net/oauth/token", region)
	body := strings.NewReader("grant_type=client_credentials")

	request, err := http.NewRequest(http.MethodPost, requestURL, body)
	if err != nil {
		log.Fatal(err)
	}
	request.SetBasicAuth(clientID, clientSecret)
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	httpClient := new(http.Client)
	response, err := httpClient.Do(request)
	if err != nil {
		log.Fatal(err)
	}
	defer response.Body.Close()

	responseData, err := ioutil.ReadAll(response.Body)
	var tokenData Token
	err = json.Unmarshal(responseData, &tokenData)
	if err != nil {
		log.Fatal(err)
	}
	return tokenData.AccessToken
}
7 Likes

Regarding the PHP

curl_setopt($curl_handle, CURLOPT_HTTPHEADER, [‘Content-Type: application/x-www-form-urlencoded’]);

you’ll need to set the content type to multipart/form-data if you are going to use an array otherwise use a string for the CURLOPT_POSTFIELDS; grant_type=client_credentials, other than that very helpful :slight_smile:

1 Like

Nice catch, I’ve been away from PHP until few weeks ago. However the code above did work because the PHP docs says if you pass an array, the header is automatically set to multipart/form-data :smile:

I’m updating the code above as this was not my intention to let PHP guess the header for me.

Python

import requests
from requests.auth import HTTPBasicAuth


def create_access_token(client_id, client_secret, region = 'us'):
    url = "https://%s.battle.net/oauth/token" % region
    body = {"grant_type": 'client_credentials'}
    auth = HTTPBasicAuth(client_id, client_secret)

    response = requests.post(url, data=body, auth=auth)
    return response.json()
2 Likes

NodeJS

function createAccessToken(apiKey, apiSecret, region = 'us') {
    return new Promise((resolve, reject) => {
        let credentials = Buffer.from(`${apiKey}:${apiSecret}`);

        const requestOptions = {
            host: `${region}.battle.net`,
            path: '/oauth/token',
            method: 'POST',
            headers: {
                'Authorization': `Basic ${credentials.toString('base64')}`,
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        };

        let responseData = '';

        function requestHandler(res) {
            res.on('data', (chunk) => {
                responseData += chunk;
            });
            res.on('end', () => {
                let data = JSON.parse(responseData);
                resolve(data);
            });
        }

        let request = require('https').request(requestOptions, requestHandler);
        request.write('grant_type=client_credentials');
        request.end();

        request.on('error', (error) => {
            reject(error);
        });
    });
}
4 Likes

I’m trying to get connected to the API for a school project (data visualization) but the project has to be with R. I have my client set-up, but can’t figure out how to get the access token. Can you assist?

Unfortunately no.

You might wanna try asking around the Community Discord Server. Maybe someone over there can help you.

EDIT: I thought it would take longer but it was actually easier than I anticipated. Here is a working function for retrieving the token:

R

require("httr")
require("jsonlite")

createAccessToken <- function(apiKey, apiSecret, region = "us") 
{
  response <- POST(
    paste("https://", region, ".battle.net/oauth/token", sep = ""),
    authenticate(apiKey, apiSecret),
    body = list(grant_type="client_credentials")
  )
  return(fromJSON(content(response, "text"), flatten = TRUE))
}
1 Like

This page on accessing RESTful APIs using R might help:
hxxps://www.programmableweb.com/news/how-to-access-any-restful-api-using-r-language/how-to/2017/07/21

2 Likes

Thank you both! Now if I could get past the

Client error: (403) Forbidden

Problem…

Thanks for these, Schiller :smiley:

I was wondering if you had a PHP example for requesting Battle.net login/authorization for a user. I was trying tonight but had no luck.

I was trying to set it up using curl, as follows:

$curl_handle = curl_init();
try
{
  curl_setopt($curl_handle, CURLOPT_URL, "https://{$realm}.battle.net/oauth/authorize");
  curl_setopt($curl_handle, CURLOPT_POSTFIELDS, ['scope' => 'wow.profile']);
  curl_setopt($curl_handle, CURLOPT_POSTFIELDS, ['redirect_uri' => '<my redirect url>']);
  curl_setopt($curl_handle, CURLOPT_POSTFIELDS, ['response_type' => 'code']);
  curl_setopt($curl_handle, CURLOPT_USERPWD, $ClientID . ':' . $ClientSecret);
  curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($curl_handle, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data']);
  $response = curl_exec($curl_handle);
  $status = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE);
  
  if ($status !== 200)
  {
    throw new Exception('Failed to get Battle.net authorization.');
  }
  return;
}
finally
{
  curl_close($curl_handle);
}

Yes, I originally wrote this function for Feral’s php lib: https://gitlab.com/davidmatthew/blizzard-api-php/-/blob/master/blizzard_api/request_handler.php#L104

There are also helper functions for generating the sign in with bnet link in the same file.

1 Like

Thank you! I’ll check it out and can hopefully adapt it to my site :slight_smile:

Since I already have a lot of auth code in my pages I’d prefer to just adapt it rather than switch entirely to another lib, if I can.

I do not recommend the switching either, it is just a reference for PHP code. Hit me on discord if you need any help.

1 Like

Just checking, is it ok to include your client ID nakedly in the URL you’re using to request authorization? I know the Client ID isn’t the sensitive part, but I’ve never done it that way and wanted to be sure.

You must, client ID is what identify which application is trying to authorize an user. The client secret and access_token on the other hand should never leak to the client side.

1 Like

Thanks :slight_smile: I noticed the authorization token is returned by Blizzard as part of the URL too but I assume that’s ok and I’m not doing it wrong.

The authorization code must be exchanged by your server combined with your secret to actually yield an access_token. The authorization code on itself gives no access to any resource so it is safe.

1 Like

All my changes are now public in WCM and apparently working as intended :slight_smile: I still keep expecting an avalanche of bug reports though :joy:

I just wanted to say thanks again for all your help. Without it, I would have taken weeks or months of trial and error to get it running.