Search code examples
desire2learn

Why am I getting a 403 response when using the Brightspace API?


I'm attempting to use the Brightspace API, but I'm getting a 403 (Forbidden) response.

I've registered my application using the Manage Extensability (/d2l/lp/extensibility/home) page, I've generated a user ID and key from the API Test Tool.

Using all of this, I've installed the D2L.Extensibility.AuthSdk NuGet package in my project. Then, in the relevant class, I've created a property for a UserContext and am initializing it in the constructor like this:

_d2LUserContext = new D2LAppContextFactory()
    .Create(OrionConfiguration.D2LApplicationId, OrionConfiguration.D2LApiKey)
    .CreateUserContext(
        "censored user id",
        "censored user key",
        new HostSpec("https", OrionConfiguration.D2LUrl.Substring(8), 443)
    );

Notes:

  1. The .Substring(8) is because D2LUrl includes the URL scheme
  2. The user id and key were generated this morning, so they have not expired yet

Then, I am trying to call the API. The code for this is split up in a few methods.

private string AuthParam(string path, string method)
{
    return _d2LUserContext
        .CreateAuthenticatedTokens($"/d2l/api/lp/1.2{path}", method)
        .Select(tuple => $"{tuple.Item1}={tuple.Item2}")
        .Aggregate((acc, p) => $"{acc}&{p}");
}

public Task<UserResponse> CreateUser(UserRequest userRequest)
{
    const string path = "/users";
    return _httpUtils.Post<UserResponse>($"{path}/?{AuthParam(path, "POST")}", userRequest);
}

UserRequest is a POC#O (Plain Old C# Object) version of the model the API expects.

Here is the relevant method in the HttpUtils class - this is a wrapper around the HttpClient I wrote to get rid of some of the boilerplate in other classes.

internal async Task<T> Post<T>(string route, dynamic body)
{
    var response = await _httpClient.PostAsync(
        _baseUrl + route, 
        new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json")
    );

    _logger.LogInformation($"POST request to {route}");
    _logger.LogInformation(await response.Content.ReadAsStringAsync());
    return JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
}

Now, putting it all together, when I try to debug these methods getting called, I set a breakpoint the line after my POST request, and we can see I get a 403 403 error in debugger

I'm wondering why this is happening. The user that the key and id were generated from is a super administrator, so this is not a permissions issue.


Solution

  • The actual API route in question has a trailing slash and the trailing slash thus needs putting into the method you have that generates the authentication token/signature. It looks to me like what you're doing is passing a path that does not have this trailing slash, and then putting it into the request as a side effect of when you tack on the query parameters, so you're generating an authentication signature for one API route and then using another in the call.

    Brightspace API routes are quite sensitive to that trailing slash, and unfortunately they're not always applied cleanly or consistently in routes.