box / box-windows-sdk-v2

Windows SDK for v2 of the Box API. The SDK is built upon .NET Framework 4.5

Home Page:https://developer.box.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Server Auth with CCG - initial setup for DI container registration

JanVargovsky opened this issue · comments

Description of the Issue

The code provided in the documentation example is working fine but most modern applications use DI with container registrations that are synchronous. This PR added support for the CCG auth but requires a user token that is available via API call only and this is the root of the issue unless you do the API call to obtain a user token in a synchronous way, there is no way of registering e.g. IBoxClient into a container, so you have to wrap it into an async factory method.

Another gotcha is the token refresh via event, even if would wrap IBoxClient into an async factory method, register a method to the IBoxClient.Auth.SessionAuthenticated event that handles the token refresh (token caching), do some API calls (logic), I should unregister the method to avoid leaks.

Steps to Reproduce

var boxConfig = new BoxConfigBuilder("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
                .Build();
var boxCCG = new BoxCCGAuth(boxConfig);
var userToken = await boxCCG.UserTokenAsync("USER_ID"); //valid for 60 minutes so should be cached and re-used
IBoxClient userClient = boxCCG.UserClient(userToken, "USER_ID");
userClient.Auth.SessionAuthenticated += delegate(object o, SessionAuthenticatedEventArgs e)
{
    string newAccessToken = e.Session.AccessToken;
    // cache the new access token
};

refactored code with token caching and exposing IBoxClient via the async factory method

string? userToken = null;
async ValueTask<IBoxClient> CreateBoxClientAsync()
{
    userToken ??= await boxCCG.UserTokenAsync(userId); //valid for 60 minutes so should be cached and re-used
    var userClient = boxCCG.UserClient(userToken, serviceAccount);
    userClient.Auth.SessionAuthenticated += OnSessionAuthenticated;
    return userClient;
}

void OnSessionAuthenticated(object sender, SessionAuthenticatedEventArgs e)
{
    userToken = e.Session.AccessToken;
}

var userClient = await CreateBoxClientAsync();
// ... do logic ...
// I should still unregister the method, which is too much in my view
userClient.Auth.SessionAuthenticated -= OnSessionAuthenticated;

Expected Behavior

Ability to instantiate IBoxClient without initial user id => user token exchange.

Versions Used

.Net SDK: 4.6.0

This makes sense and would make it much easier to create the client synchronously without explicitly calling for token. Just so you know, contributions to this repo are always welcome!

For a bit hacky workaround, you could try passing a "fake" or expired token (not a null or empty one as the format should be a proper one!) to the IBoxClient instead of calling UserTokenAsync. The first api call should hit 401 and then sdk should call api once again for a new token.

It seems like I've discovered a bug, if I'm using a service account, it's not able to refresh expired (or fake) token. I wanted to create a new custom app because 2FA doesn't work but it's not a problem, will reach your support for whats wrong. I will try it more in detail and optionally fill a PR then.

General question. We have an integration app and we want to upload files as a service account, so its clearly visible in the web Box UI
image
is this the way creating a custom app, auth with CCG and pass service account into the SDK?

Does the UserTokenAsync function return the token in your case? It is also used when refreshing the token. I believe you need the Generate user access tokens scope from the developer console, and your application must be authorized by an administrator in admin console.

We have an integration app and we want to upload files as a service account, so its clearly visible in the web Box UI is this the way creating a custom app, auth with CCG and pass service account into the SDK?

Yes, the SDK can be used to automate things like uploading files so that you don't have to do it through the Box UI. Files uploaded via the SDK should be visible on the site (when appropriate permissions are given etc.).

Does the UserTokenAsync function return the token in your case?

It doesn't. Should I create another issue here or contact your support regarding this? I believe I have everything right, all I need is to make uploads to a folder look like it's been uploaded by an app instead of me as a user.

The setup is the same, I change userId which is a number
image
to the serviceAccount that is automatically generated account/email
image

I've got all the checkboxes in the app scopes enabled, mainly the Generate user access tokens
image

var ad = await boxCCG.AdminTokenAsync(); // ok
var t = await boxCCG.UserTokenAsync(userId); // ok
var t2 = await boxCCG.UserTokenAsync(serviceAccount); // invalid_grant

Exception details

message: The API returned an error [BadRequest] invalid_grant - Grant credentials are invalid
stack trace:

   at Box.V2.Extensions.BoxResponseExtensions.ParseResults[T](IBoxResponse`1 response, IBoxConverter converter)
   at Box.V2.CCGAuth.BoxCCGAuth.<CCGAuthPostAsync>d__8.MoveNext()
   at Box.V2.CCGAuth.BoxCCGAuth.<UserTokenAsync>d__7.MoveNext()
   at Program.<<Main>$>d__0.MoveNext() in ...\Program.cs:line 22

behaves the same with and without enterprise.

Seems like service account ID is just for the UI - for the invite to a folder

In the code, I needed to use admin token/client and final result looks as expected now.

image instead of image.

final code is this:

var boxConfig = new BoxConfigBuilder(clientId, clientSecret)
    .SetEnterpriseId("<enterprise id>")
    .Build();
var boxCCG = new BoxCCGAuth(boxConfig);
var client = boxCCG.AdminClient("fake");
client.Auth.SessionAuthenticated += OnSessionAuthenticated;

as I'm rereading the documentation, it was my misunderstanding. However, I'd like to create a PR to make the token optional or totally remove it since it can be refreshed internally, which option do you like more?

I'm glad the problem has been resolved! I think the preferred way would be to make the token optional to maintain backwards compatibility if possible and don't change this behavior for existing clients.