Getting Started with Grunt
.NET
Keep in mind that for the sample below I am using a Console C# application - you can use a similar or somewhat tweaked approach in any .NET application of your choosing (including web apps).
First, let's create a new Xbox authentication client, handily available through the <xref:den.dev.orion.Authentication.XboxAuthenticationClient> class. This class is designed to do the bulk of heavy lifting when it comes to interacting with the Xbox services.
Note
We still need to use the Xbox Live authentication services to access the Halo API since everything in the API is tied to a user's Xbox Live identity. Therefore, for us to access the Halo API you need to first be able to authenticate with Xbox Live services.
To create the client, use:
XboxAuthenticationClient manager = new();
Next, we need to make sure that have a URL which can be used for authentication. To do that, there is another helper method:
var url = manager.GenerateAuthUrl(clientConfig.ClientId, clientConfig.RedirectUrl);
In the context above, clientConfig
is an instance of <xref:den.dev.orion.util.configurationreader> that I am using to read the client ID and client secret from a local JSON file:
var clientConfig = ConfigurationReader.ReadConfiguration<ClientConfiguration>("client.json");
This class is a helper container that allows me to take some JSON data from a file and transform it to a C# object. You don't actually have to use it if you store the client ID and secret in some other way. As you saw above from the GenerateAuthUrl
function, you can pass those strings directly.
Note
To be able to authenticate against the Xbox Live service first, you will need to have an Azure Active Directory application registered in the Azure Portal. The application needs to have access to personal Microsoft accounts and can be created on a Free/Trial Azure subscription.
The application enables the user to log in with their own credentials, and then pass the access information to the application that uses the Halo API - in this context, a Grunt sample app.
Now let's add some other items that we need to proceed with the authentication flow:
HaloAuthenticationClient haloAuthClient = new();
OAuthToken currentOAuthToken = null;
var ticket = new XboxTicket();
var haloTicket = new XboxTicket();
var extendedTicket = new XboxTicket();
var haloToken = new SpartanToken();
In the snippet above we see the following:
- An instance of <xref:den.dev.orion.authentication.haloauthenticationclient> will be used to get the Spartan token, which is the main piece of information we need to be able to get data from the Halo API.
- A placeholder container for <xref:den.dev.orion.models.oauthtoken>, that will store the token we will get from the service.
- Three instances of <xref:den.dev.orion.models.xboxticket> that will contain authorization information.
- A container for our final <xref:den.dev.orion.models.spartantoken>.
Time for the actual logic. In my sample application, I don't want to go through the dance of getting the auth URL, then getting the code, then pasting the code in and waiting for the tokens to process on every run. Instead, I prefer to store the tokens locally and then work with them and refresh them automatically.
To do the above, I start by reading a tokens.json
file that I designated locally for the storage of my authentication tokens:
if (System.IO.File.Exists("tokens.json"))
{
Console.WriteLine("Trying to use local tokens...");
// If a local token file exists, load the file.
currentOAuthToken = ConfigurationReader.ReadConfiguration<OAuthToken>("tokens.json");
}
else
{
currentOAuthToken = RequestNewToken(url, manager, clientConfig);
}
Warning
Anyone that have access to the OAuth tokens for your accounts has full access to said account. Make sure that you store them with caution. Be aware who will be able to see them and when.
If the file exists, I want to read the configuration locally and store the token data in an instance of <xref:den.dev.orion.models.oauthtoken>, as I called out earlier. Otherwise, we're going through the flow of requesting a new token through RequestNewToken
. This function is something I've created specifically for the sample application, and it wraps some nice functionality exposed in Orion:
private static OAuthToken RequestNewToken(string url, XboxAuthenticationClient manager, ClientConfiguration clientConfig)
{
Console.WriteLine("Provide account authorization and grab the code from the URL:");
Console.WriteLine(url);
Console.WriteLine("Your code:");
var code = Console.ReadLine();
var currentOAuthToken = new OAuthToken();
// If no local token file exists, request a new set of tokens.
Task.Run(async () =>
{
currentOAuthToken = await manager.RequestOAuthToken(clientConfig.ClientId, code, clientConfig.RedirectUrl, clientConfig.ClientSecret);
if (currentOAuthToken != null)
{
var storeTokenResult = StoreTokens(currentOAuthToken, "tokens.json");
if (storeTokenResult)
{
Console.WriteLine("Stored the tokens locally.");
}
else
{
Console.WriteLine("There was an issue storing tokens locally. A new token will be requested on the next run.");
}
}
else
{
Console.WriteLine("No token was obtained. There is no valid token to be used right now.");
}
}).GetAwaiter().GetResult();
return currentOAuthToken;
}
What this helper function does is basically ask the user to provide the code from the URL that we created earlier in the very first step. The code is generally appended to the redirect URL for the Azure Active Directory application that you created earlier, and it will show once the user authorizes your application to access Xbox Live through it.
With the code acquired, I then use <xref:den.dev.orion.authentication.xboxauthenticationclient.requestoauthtoken> through the <xref:den.dev.orion.authentication.xboxauthenticationclient> instance we declared earlier and pass the required application information to it. If the token acquisition process is successful, we store the token to tokens.json
, otherwise error out.
In the snippet above, StoreTokens
is yet another helper function I wrote for the sample application. It serializes an <xref:den.dev.orion.models.oauthtoken> object and stores the output JSON into a file:
private static bool StoreTokens(OAuthToken token, string path)
{
string json = JsonSerializer.Serialize(token);
try
{
System.IO.File.WriteAllText(path, json);
return true;
}
catch
{
return false;
}
}
Now, back to continuing the authentication flow. We now need to request the user token, and we do that with the following piece of code:
Task.Run(async () =>
{
ticket = await manager.RequestUserToken(currentOAuthToken.AccessToken);
if (ticket == null)
{
// There was a failure to obtain the user token, so likely we need to refresh.
currentOAuthToken = await manager.RefreshOAuthToken(
clientConfig.ClientId,
currentOAuthToken.RefreshToken,
clientConfig.RedirectUrl,
clientConfig.ClientSecret);
if (currentOAuthToken == null)
{
Console.WriteLine("Could not get the token even with the refresh token.");
currentOAuthToken = RequestNewToken(url, manager, clientConfig);
}
ticket = await manager.RequestUserToken(currentOAuthToken.AccessToken);
}
}).GetAwaiter().GetResult();
<xref:den.dev.orion.Authentication.XboxAuthenticationClient.RequestUserToken(System.String)> will helpfully enable us to exchange the OAuth token for something more useful. In the code snippet above, in case the result is null
, it's very likely that the original OAuth token expired, so we need to refresh it, hence the call to <xref:den.dev.orion.Authentication.XboxAuthenticationClient.RefreshOAuthToken>. If the refresh is unsuccessful, we need to run through the RequestNewToken
flow above, and then go through <xref:den.dev.orion.Authentication.XboxAuthenticationClient.RequestUserToken(System.String)> one more time.
This gets a bit convoluted, but now, assuming that we were successful with the step above, we need to get the Xbox Live security token (XSTS). To do that, we use <xref:den.dev.orion.Authentication.XboxAuthenticationClient.RequestXstsToken(System.String,System.Boolean)> and are exchanging the previous user token for it:
Task.Run(async () =>
{
haloTicket = await manager.RequestXstsToken(ticket.Token);
}).GetAwaiter().GetResult();
Notice that I am calling <xref:den.dev.orion.Authentication.XboxAuthenticationClient.RequestXstsToken(System.String,System.Boolean)> without the optional Boolean argument - this means that by default, I will be using the Halo service relying party. A relying party is a trusted URL that is configured on the developer's side that is used for the creation of a new XSTS token. In the case above, the folks that built Halo already established an appropriate relying party that we can use.
Depending the relying party, the XSTS ticket exchange API will provide some metadata that can be used further in the authorization flow. One piece that is missing from it, however, if I use the Halo relying party, is the Xbox User ID (XUID). To get it, I can tell <xref:den.dev.orion.Authentication.XboxAuthenticationClient.RequestXstsToken(System.String,System.Boolean)> to not use the Halo relying party and instead use the one for Xbox Live services by setting the optional argument to false
:
Task.Run(async () =>
{
extendedTicket = await manager.RequestXstsToken(ticket.Token, false);
}).GetAwaiter().GetResult();
The extended ticket will have my XUID, which I will use later. Now, I finally get the Spartan token:
Task.Run(async () =>
{
haloToken = await haloAuthClient.GetSpartanToken(haloTicket.Token);
Console.WriteLine("Your Halo token:");
Console.WriteLine(haloToken.Token);
}).GetAwaiter().GetResult();
<xref:den.dev.orion.Authentication.HaloAuthenticationClient.GetSpartanToken(System.String)> will take your XSTS ticket and convert it into a reusable Spartan V4 token that can be used with existing Halo API calls. Voila! You are now almost good to go. The last thing is left is for us to configure the <xref:den.dev.orion.Core.HaloInfiniteClient>.
Here is how we do it:
HaloInfiniteClient client = new(haloToken.Token, extendedTicket.DisplayClaims.Xui[0].Xid);
// Test getting the clearance for local execution.
string localClearance = string.Empty;
Task.Run(async () =>
{
var clearance = (await client.SettingsGetClearance("RETAIL", "UNUSED", "222249.22.06.08.1730-0")).Result;
if (clearance != null)
{
localClearance = clearance.FlightConfigurationId;
client.ClearanceToken = localClearance;
Console.WriteLine($"Your clearance is {localClearance} and it's set in the client.");
}
else
{
Console.WriteLine("Could not obtain the clearance.");
}
}).GetAwaiter().GetResult();
First, we instantiate the <xref:den.dev.orion.Core.HaloInfiniteClient> class and set the Spartan V4 token and XUID in the constructor. The XUID argument here is optional, but is helpful to have in the future once you start dealing with APIs that do, in fact, require your XUID to move forward.
Next, we use <xref:den.dev.orion.Core.HaloInfiniteClient.SettingsGetClearance(System.String,System.String,System.String)> to get the argument that will be further used inside the 343-clearance
header in the API client (don't worry - this is handled automatically). To get the clearance, we are using some built-in parameters that are mimicking the game - RETAIL
audience, UNUSED
sandbox, and a 222249.22.06.08.1730-0
build of the executable.
If the clearance acquisition is successful, we can set the clearance in the client as well, otherwise move forward and you will need to set it manually later if you are using a clearance-dependent API call.
You can now get the Halo Infinite data, like this:
// Try getting actual Halo Infinite data.
Task.Run(async () =>
{
var example = await client.StatsGetMatchStats("21416434-4717-4966-9902-af7097469f74");
Console.WriteLine("You have stats.");
}).GetAwaiter().GetResult();
Remember, that you will be getting a <xref:den.dev.orion.Models.HaloApiResultContainer`2> as a return value, that contains the type you're asking for, along with any kinds of error messages that might've arisen during the API call.
Congratulations! You are now ready to use Orion in your .NET application.
Node.js
In development
Python
In development