Questions for BOX App User coding
-
I answered your questions by letter below. Please let me know if my answers make sense?
A. The App User authentication process is slightly different than the standard user authentication process on mobile. For App User authentication, you would have to implement the App User OAuth flow on the server, and then pass the app user access token back to the device.
You can use one of our SDKs like our Java, .NET, or Ruby ones to implement the App User OAuth flow on the server.
Once you get the app user token back to the device, you would pass the app user access token to the fetchAccessTokenWithCompletion method. Here is the iOS documentation for this.
- (void)fetchAccessTokenWithCompletion:(void (^)(NSString *, NSDate, NSError *))completion { // Do things to retrieve access token and access token expiration date. completion(accessToken, accessTokenExpiration, error); }
B. I agree we need to add a concise code sample for authentication on the documentation site.
Here is documentation from our Java SDK that shows how to create an app user and then get an app user access token using the SDK. CreateAppUser is the class that implements this functionality in the Java SDK.
C. Same answer as A
-
Murtza,
One more question... and probably a dumb one.
When you talk about the servier in this configuration are you talking about BOX?
This app resides on the Apple devices and is not hosted anywhere.
BOX is the file destination and repository that communicates with the app.
THanks for your patience and the quick reply.
Appreciate it,
Tom
-
I wanted to note a correction to my earlier answer. You would be implementing authentication using JWT (not App User OAuth as I wrote). The OAuth process is designed to be used with Standard Box Users (Managed Users). The JWT auth process is designed to be used with App Users. Sorry about the confusion!
To answer your second question, there will be three parts for this use case, which includes the mobile app, the Box Platform, and the server. The reason you want to have a server for this use case is to be more secure.
The App User auth token lets you access the App User's files stored in the Box Platform. When you generate the App User auth token using JWT, you will be making a request to the Box Platform API with client secret that is registered to your Box App. If you store the client secret within your app and somebody reverse engineers it, they could potentially create more auth tokens with your client secret. So to be more secure, you would implement the JWT auth process on the server, pass the App User auth token to the mobile app, and then request the App User's files.
-
OK
That explains why the OAuth didn't work last night.
Thanks.
So to speed the process, could you please send some sample code (from any app, or test app or whatever) of constructing the JWT (in 'BOX" terms) and authenticating it.
Here is a sample of our developer's code for the JWT including the original question (C). Please have a look and identify where it is incorrect, and please provide a correct code sample for this function. Thanks much.
C. If not, here is the code from some of the various attempts I have tried. I have imported the JWT pod into the application and tried to use it to create a JWT but when the encodePayload is called, it tries to connect and the Box error screen appears. It isn’t clear from the documentation what I would even do with the JWT if it were created successfully. Do I need to create a BoxParallelOAuth2Session or can I call the [BoxContentClient defaultClient] authenticateInAppWithCompletionBlock method? Again, a simple set of example code would be nice…using our clientID, clientSecret, AppID, etc would be even better.Code that doesn’t work:@try{NSString *jti = [Guid newGuidString];NSDate *exp = [NSDate date];NSDictionary *payload = @{@"iss": @“abcdef", @"sub": @“***@example.com", @"box_sub_type": @"user", @"aud": @"https://api.box.com/oauth2/token", @"jti": jti, @"exp": exp};NSString *secret = @“opqrst"; -
Here's an example from our Ruby SDK that show how to construct the JWT assertion.
def self.jwt_assertion(private_key, iss, sub, box_sub_type, public_key_id) payload = { iss: iss, sub: sub, box_sub_type: box_sub_type, aud: "https://api.box.com/oauth2/token", jti: SecureRandom.hex(64), exp: (Time.now.utc + 10).to_i } additional_headers = {} additional_headers['kid'] = public_key_id unless public_key_id.nil? JWT.encode(payload, private_key, "RS256", additional_headers) end
If you use one of our SDKs, the SDK will handle building the JWT assertion for you. For example with the Ruby SDK, you would just call the get_user_token method, which returns the App User access token and abstracts away the underlying details.
Boxr::get_user_token(user_id, private_key: ENV['JWT_PRIVATE_KEY'], private_key_password: ENV['JWT_PRIVATE_KEY_PASSWORD'], public_key_id: ENV['JWT_PUBLIC_KEY_ID'], client_id: ENV['BOX_CLIENT_ID'], client_secret: ENV['BOX_CLIENT_SECRET'])
-
I'm getting the following error as I try to authenticate:
Terminating app due to uncaught exception 'You cannot use 'defaultClient' if multiple users have established a session.', reason: 'Specify a user through clientForUser:'
I have tried multiple ways to authenticate but here is the latest. It breaks on the bolded line with the error. (NOTE: I'm just using one of the users passed down from Box to ensure that I'm grabbing a valid user for the moment. I'll handle user selection later, if & when I can ever get this to authenticate).
Please advise:
Erroneous code:
[BOXContentClient setClientID:@"abc" clientSecret:@"xyz"];
BOXContentClient *client;
BOXUserMini *currentUser;
for (BOXUser *user in [BOXContentClient users]) {
BOXContentClient *clnt = [BOXContentClient clientForUser:user];
if([clnt.user.name isEqualToString:@"***@example.com"]){
currentUser = clnt.user;
}
}
NSLog(@"CurrentUser: %@", currentUser.name);
[[BOXContentClient clientForUser:currentUser] authenticateWithCompletionBlock:^(BOXUser *user, NSError *error) {
if (error) {
if ([error.domain isEqualToString:BOXContentSDKErrorDomain] && error.code == BOXContentSDKAPIUserCancelledError) {
BOXLog(@"Authentication was cancelled, please try again.");
} else {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
message:@"Login failed, please try again"
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:@"OK", nil];
[alertView show];
}
} else {
BOXContentClient *tmpClient = [BOXContentClient clientForUser:user];
}
}];
-
Just wanted to highlight that we do not recommend developers authenticate App Users from their mobile apps. This is why we do not provide this functionality from our iOS SDK.
If somebody reverse engineers a mobile app that is authenticating App Users directly from the mobile app, they could potentially get access to that developer's Box API client secret and private key. Once somebody has access to these credentials, they can get access to the data stored in that developer's Box Platform instance.
To mitigate this risk, we recommend generating an App User access token on the server and passing this access token to the mobile app.
We have SDKs that can be used to easily generate the App User access token on the server. Here are links to our SDKs:
Please let me know if one the SDKs above will work in your development environment? I am happy to provide sample code in that language.
-
Murtza,
Thanks much for that clarification. This explains a bunch. If we could get the sample code in .NET SDK that would be great
It will probably help us figure out the other end of our difficulty:The problem I’m hitting at the moment is that the very first lines in his AppDelegate.m (the class that actually begins the app…the very first thing that’s run) has a reference call setting the oAuth2Session.clientID and .secretID for the BoxSDK sharedSDK property. There is no sharedSDK property in the BoxContentClient and if I try to set it for the defaultClient for the BoxContentClient, it tells me that I’m trying to assign a value to a read-only property.
If there is any light you could shed on that specific issue, I believe we will be good to go. -
Per Murtza’s statement earlier in the thread:
A. The App User authentication process is slightly different than the standard user authentication process on mobile. For App User authentication, you would have to implement the App User OAuth flow on the server, and then pass the app user access token back to the device.You can use one of our SDKs like our Java, .NET, or Ruby ones to implement the App User OAuth flow on the server.Once you get the app user token back to the device, you would pass the app user access token to the fetchAccessTokenWithCompletion method.This seems to conflict with the Box documentation which makes it appear that the first trip should be made to: https://account.box.com/api/oauth2/authorize.So the question is: to obtain the initial, 30-second-in-duration app token, should the app make the call to the Box URL above or to our server that handles the tokens? -
You would be implementing authentication using JWT (not App User OAuth as I mistakenly wrote in my first response). The JWT auth process is designed to be used with App Users.
I'll first describe how to generate the App User access token using our .NET SDK, and then I'll describe how to use this App User access token in an iOS app to upload a file.
Before we walk through those steps, I wanted to answer your question about the AppDelegate.m file. If you are using the App User access token, you do not need to import the SDK in the AppDelegate.m file or set the API credentials there.
Here are the steps to generate an App User access token with our .NET SDK.
1. Install the core SDK and JWT support packages using Nuget
PM> Install-Package Box.V2 PM> Install-Package Box.V2.JWTAuth
2. The SDK expects your Box API credentials to be defined in the boxConfig variable. This page describes where to get these credentials.
var boxConfig = new BoxConfig(, , , , , ); var boxJWT = new BoxJWTAuth(boxConfig);
3. Query the Box API for an admin token. This admin token will let you create an App User in the next step.
var adminToken = boxJWT.AdminToken();
4. Create a new instance of the admin client using the adminToken from the previous step. Then make an API call to Box to generate a new App User.
var adminClient = boxJWT.AdminClient(adminToken); var userRequest = new BoxUserRequest() { Name = "NAME_FOR_APP_USER", IsPlatformAccessOnly = true }; var appUser = await adminClient.UsersManager.CreateEnterpriseUserAsync(userRequest);
5. Generate an access token for the App User created in the previous step. This access token will be valid for 60 minutes.
var userToken = boxJWT.UserToken(appUser.Id);
Here are the steps to use the App User access token we generated in the previous section in an iOS app where we want to upload a file to Box.
1. Add the Box iOS SDK to your Podfile. Then run the pod install command to install the SDK.
pod 'box-ios-sdk' pod install
2. Import Box iOS SDK in the header file for the class where you will be calling the Box API.
#import
3. This class will be implementing a protocol defined by the iOS SDK, so add this protocol to your class interface.
@interface ViewController : UIViewController
4. Create a method to query your server for an App User access token. Store this access token in a property that you can access from that class.
@property (strong,nonatomic) NSString *accessToken; self.accessToken = [yourCustomMethodToRequestAppUserAccessToken];
5. When you are using the App User access token with the Box iOS SDK, you will be overriding the default authentication defined by the Box iOS SDK. You will do this by initializing the client created by the Box iOS SDK with your own access token. The Box Client will look for the value of the App User access token you get from your server in the fetchAccessTokenWithCompletion method.
- (void)fetchAccessTokenWithCompletion:(void (^)(NSString *, NSDate *, NSError *))completion { completion(self.accessToken, [NSDate dateWithTimeIntervalSinceNow:100], nil); }
6. Now we will create a new Box Client that authenticates with the App User access token from your server. The example method below will take this access token and upload a file to Box and store it in the App User's root folder. Lastly this method will log the file id of the uploaded file.
-(void)uploadFileToBox { BOXContentClient *client = [BOXContentClient clientForNewSession]; [client setAccessTokenDelegate:self]; NSString *localFilePath = [[NSBundle mainBundle] pathForResource:@"file-nae" ofType:@"png"]; BOXFileUploadRequest *uploadRequest = [client fileUploadRequestToFolderWithID:0 fromLocalFilePath:localFilePath]; uploadRequest.fileName = @"file-name.png"; [uploadRequest performRequestWithProgress:^(long long totalBytesTransferred, long long totalBytesExpectedToTransfer) { // Update a progress bar, etc. } completion:^(BOXFile *file, NSError *error) { // Upload has completed. NSLog(@"%@", file.modelID); }]; }
-
Murtza,
Good stuff and thanks
One answered question:
The example has the server piece creating an app user and getting the session. i think we want that left in our app unless they’re treating these app users as a throwaway object that doesn’t persist. if so, then we may have an issue with mapping that app user to appropriate collaboration folders
Please let us know your thoughts on this as soon as you can
-
Just wanted to reiterate that we do not recommend generating the App User access token on a mobile app. For security reasons, the App User access token should be generated on the server and then passed to the mobile app to make requests to the Box API.
App Users and Managed Users can collaborate on Box files and folders. For example, let's say you want to upload a file to an App User's Box account and then add a Managed User as a collaborator. To do this via the Box API, you would first generate an App User access token on the server. Next, using that access token you would upload the file to the App User's Box account. Finally, you would add the Managed Users as a collaborator to that file using the access token.
-
MurtzaThanks for this answerWe solved the authentication issue this weekend
The example you present is exactly the reverse of what we will do with app users and managed users
The managed users will upload files to the company file structure, and the app users will view those files and then send back reports to a universal folder on the company BOX file structure.
Any process for this type of interaction that we should know about when coding the app?
Appreciate it
Tom -
Here are the steps to achieve the use case you described:
1. Generate an Enterprise access token on the server.
2. Create an App User with the Enterprise access token on the server.
3. For this App User to view the Managed User's content, you will need to add the App User as a collaborator with the Viewer permission level to the content needed. To do this, make an API call from the server to the Create Collaboration endpoint using the Enterprise access token. In this API call, you will need to set the As-User header to the user id of the Managed User who owns the content.
4. For this App User to add content to Managed User's folder, you will need to add the App User as a collaborator with the Uploader permission level to the folder where you want to upload content. Similar to step 3, make an API call from the server to the Create Collaboration endpoint using the Enterprise access token. In this API call, you will need to set the As-User header to the user id of the Managed User who owns the content.
5. Generate an App User access token on the server for this App User.
6. Pass this App User access token to your mobile app.
7. With this App User access token, you can now view content on the mobile app from Step 3, and you can also upload content to the folder from step 4.
-
You can invite an App User to collaborate on a folder owned by a Managed User using the Create Collaboration API.
To authenticate this API call, you will need to use an Enterprise access token. You will also need to set the As-User header to the user id of the Managed User who owns the content.
Below is an example API call showing how to do this.
curl https://api.box.com/2.0/collaborations \ -H "Authorization: Bearer ENTERPRISE_ACCESS_TOKEN" \
-H "As-User: MANAGED_USER_ID" \ -d '{"item": { "id": "FOLDER_ID", "type": "folder"}, "accessible_by": { "id": "APP_USER_ID", "type": "user" }, "role": "viewer"}' \ -X POST -
Murtza,
We were finally able to get the authentication piece going but now we're getting weird errors as we try to compile the application. With the application working, I added a new pod into the podfile manifest and ran the pod install command. Everything seemed fine, but when I came back into the application, I now get compiler errors saying that there is a redefinition of all of the variables in the BoxContentSDK pods.
Can you point me towards what might be the issue? I'm only referencing the BoxContentSDK through the pod and do not have any of the linked libraries selected . And I've checked the header search paths and library search paths and don't see any references to a location where the Box SDK would be located on my hard drive.
We are running on a very tight schedule now, due to the time it took us to get authentication working and now we're hitting this issue with Box when we didn't make any changes to anything that should have affected Box.
Ron Icard
-
Using the .NET SDK, I believe I've followed your example (see code below). But when I run it I get "Access denied - insufficient permission" on the last statement. I've allowed all permissions I can find and double-checked the Account ID under settings/profile. Any other incorrect constants would have cause a failure upstream. Any idea what might be the problem?
// get enterprise token var config = new BoxConfig(clientId, clientSecret, enterpriseId, privateKey, privateKeyPw, kid); var jwtAuth = new BoxJWTAuth(config); var adminToken = jwtAuth.AdminToken(); // get enterprise client var adminClient = jwtAuth.AdminClient(adminToken); // create app user var userRequest = new BoxUserRequest() { Name = "test appuser", IsPlatformAccessOnly = true }; var appUser = await adminClient.UsersManager.CreateEnterpriseUserAsync(userRequest); // set up collaboration request var collaborationRequest = new BoxCollaborationRequest { AccessibleBy = new BoxCollaborationUserRequest { Id = appUser.Id, Type = BoxType.user }, Item = new BoxRequestEntity { Type = BoxType.folder, Id = "0" }, Role = "viewer" }; // create impersonation client and collaboration var impClient = JwtAuth.AdminClient(adminToken, accountId); await impClient.CollaborationsManager.AddCollaborationAsync(collaborationRequest);
Thanks.
サインインしてコメントを残してください。
コメント
21件のコメント