JWT Oauth 2.0 using powershell
AnsweredFor those of you who are looking for information on how to integrate server-side box authentication using OAuth 2.0 and JWT in powershell and have not been able to find any information or get any legitimate help from Box support, then this post is for you. I spent a lot of time trying to get any kind of help when it came to this, so now that I was able to find the answer myself (no thanks to box support of course), I figured I would post the solution here to help those who find themselves in the position I was in might and give them the info they need.
My solution utilizes the box windows sdk v2, so head on over to nuget's website, download the latest command line nuget executable, and run the below command in a command prompt:
nuget install box.v2
This will download all of the dll files you will need to continue perform the task. Place the folders/files somewhere where you can store them long term, then create a new powershell script with the below content (the below code assumes that you generated your public/private key pair using the box development console and have the downloaded Json file with all the important app information):
[Reflection.Assembly]::LoadFile("Full\Path\to\System.IdentityModel.Tokens.Jwt.5.1.4\lib\net45\System.IdentityModel.Tokens.Jwt.dll") [Reflection.Assembly]::LoadFile("Full\Path\to\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll") [Reflection.Assembly]::LoadFile("Full\Path\to\Box.V2.3.2.0\lib\net45\Box.V2.dll") [Reflection.Assembly]::LoadFile("Full\Path\to\Microsoft.IdentityModel.Logging.1.1.4\lib\net45\Microsoft.IdentityModel.Logging.dll") [Reflection.Assembly]::LoadFile("Full\Path\to\Microsoft.IdentityModel.Tokens.5.1.4\lib\net45\Microsoft.IdentityModel.Tokens.dll") [Reflection.Assembly]::LoadFile("Full\Path\to\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll") $content = Get-content "Full\Path\to\myjsonfile.json" | ConvertFrom-Json $ob = New-Object Box.V2.config.BoxConfig (($content.boxAppSettings).clientID, ($content.boxAppSettings).clientSecret, $content.enterpriseID, (($content.boxAppSettings).appAuth).privateKey, (($content.boxAppSettings).appAuth).passphrase, (($content.boxAppSettings).appAuth).publicKeyID) $ob2 = New-Object Box.V2.JWTAuth.BoxJWTAuth ($ob) $admintok = $ob2.AdminToken $serviceaccount = $ob2.AdminClient($admintok)
And now the variable "service account" is an object which contains methods you can use to perform administrative functions in your box environment (rather than the api calls that most are familiar with). Have a blast.
-
Thanks for posting your code, however when I try to run it I'm not getting an valid object returned for
$ob2 = New-Object Box.V2.JWTAuth.BoxJWTAuth ($ob)
When evaluated $ob2 returns Box.V2.JWTAuth.BoxJWTAuth, and as a result I'm not able to get an AdminToken. $ob evaluates as a complex object with the embedded cert and details from JSON.
While a newby to Box, I'm fairly familiar with PowerShell, and this one has me stumped. The App has been approved by the Box Admin etc.
Appreciate any ideas.
Thanks
-
This doesn't work for me sadly.
First i had to add the -Raw parameter to the Get-Content part.When filling in the paths and trying it out with getting the users, I get the following error message:
Result : Id : 121315 Exception : System.AggregateException: One or more errors occurred. ---> Box.V2.Exceptions.BoxException: The API returned an error [BadRequest] at Box.V2.Extensions.BoxResponseExtensions.ParseResults[T](IBoxResponse`1 response, IBoxConverter converter) at Box.V2.JWTAuth.BoxJWTAuth.JWTAuthPost(String assertion) at Box.V2.JWTAuth.BoxJWTAuth.GetToken(String subType, String subId) at Box.V2.JWTAuth.JWTAuthRepository.d__21.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Box.V2.Managers.BoxResourceManager.d__13`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Box.V2.Managers.BoxResourceManager.d__12`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Box.V2.Managers.BoxResourceManager.d__11`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Box.V2.Managers.BoxUsersManager.d__4.MoveNext() --- End of inner exception stack trace --- ---> (Inner Exception #0) Box.V2.Exceptions.BoxException: The API returned an error [BadRequest] at Box.V2.Extensions.BoxResponseExtensions.ParseResults[T](IBoxResponse`1 response, IBoxConverter converter) at Box.V2.JWTAuth.BoxJWTAuth.JWTAuthPost(String assertion) at Box.V2.JWTAuth.BoxJWTAuth.GetToken(String subType, String subId) at Box.V2.JWTAuth.JWTAuthRepository.d__21.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Box.V2.Managers.BoxResourceManager.d__13`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Box.V2.Managers.BoxResourceManager.d__12`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Box.V2.Managers.BoxResourceManager.d__11`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Box.V2.Managers.BoxUsersManager.d__4.MoveNext()<--- Status : Faulted IsCanceled : False IsCompleted : True CreationOptions : None AsyncState : IsFaulted : True AsyncWaitHandle : System.Threading.ManualResetEvent CompletedSynchronously : False
So after some fiddling around, I found out that the parameter $admintok doesn't actually contain the admin token;
$admintok = $ob2.AdminToken
is no actual API call, it should be
$admintok = $ob2.AdminToken()
But whenever i switch that up, I get the following error message:
Exception calling "AdminToken" with "0" argument(s): "The API returned an error [BadRequest]" At line:10 char:1 + $admintok = $ob2.AdminToken() + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : BoxException
Any ideas on what is going wrong here?
-
The .NET SDK is designed to work in full .NET applications, but unfortunately doesn't work out of the box in PowerShell. Because the SDK's app.config file that binds any version of Newtonsoft.Json to the 10.x version that the SDK installs is not being loaded by PowerShell. Since you're going outside of the SDK's normal mode of operation and loading the assemblies manually, you'll need to ensure that the correct versions are installed and referenced. In the course of our investigation we found some information about how to make PowerShell load the app.config, which some other Powershell users have had luck with.
One other option is to use the Box CLI in your PowerShell script; it handles JWT authentication currently.
-
I have not been able to fix it with loading the config file in PowerShell sadly.
The CLI seems to work like a charm, but I am missing some documentation, for instance when I am trying to create a user;
$adminUsername = "***email address removed for privacy***" $name = "TestUser1" # Search Admin Account $admin = box users search $adminUsername --json $adminDetails = $admin | ConvertFrom-Json $adminId = $adminDetails.entries.id # Get Access Token for Admin Account $token = box tokens get -u $adminID # Create Json body with new user details $body = @{ entries = @( @{ name = $name } ) } | ConvertTo-Json # Create new user box users create --token $token $body
I keep running into the error:
box : A login is required for this command. At line:22 char:1 + box users create --token $token $body + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (A login is required for this command.:String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError
What exactly causes this?
And how can I perform a login? Or solve this?
I thought it just meant no access token is provided (perhaps the service account cannot create users, only the admin user can) so I got the access token of my admin user.
But I keep running into the same issue. -
It works for me.. If you want to work in Admin Context there is a Powershell Module on github PoshBox which works currently, however, if you are trying to use it to upload files, it will upload to the service account and not the user account at this point (put in an issue and might consider working on it later)
Here is how I can get it to work
# Location of the script and its contents $scriptLocation = "c:\scripts\box" # Locate the DLL's required to run $dlls = Get-ChildItem "$scriptLocation\lib" #loop through each DLL and import the DLL required to run foreach($dll in $dlls) { $library = get-childitem $dll.FullName -Recurse | Where-Object {$_ -like "*.dll"} if($library) { Add-type -Path $library.FullName } } # Set the location of the JSON file # $file = "$scriptLocation\test.json" $uplFolder = "C:\temp\testUpload" <# Import the JSON file #> $json = Get-Content $file #Create a Box instance $iBoxConfig = [Box.V2.Config.BoxConfig]::CreateFromJsonString($json) $JWTAuth = [Box.V2.JWTAuth.BoxJWTAuth]::new($iBoxConfig) #Set the Authentication $adminToken = $JWTAuth.AdminToken() $adminclient = $JWTAuth.AdminClient($adminToken) #Need a User Client to upload to user folder and not to the Automation user context with ID #This does not have to have admin permissions on the entire instance #Definitely needs "Generate User Access Tokens" Permission, which is linked to "Manage Users" #Read-Write Permissions as needed. Tested that it can't access other user accounts, but is able to list other users $userid = "0123456789" $ut = $adminclient.Auth.BoxJWTAuth.UserToken($userid) $uc = $adminclient.Auth.BoxJWTAuth.UserClient($ut,$userid) $pagesize = 100 $offset = 0 $folderId = 0 #List Current Contents $uc.FoldersManager.GetFolderItemsAsync($folderID,$pagesize,$offset).GetAwaiter().GetResult().entries.name #Upload some test Files $filesToUpload = (Get-childItem $uplFolder) #Get Files from $uplFolder and upload to Root Folder for the user foreach($file in $filesToUpload){ try{ #Uploading to root folder, which is ID 0, for the account using the User Client $request = @{ name = "$($file.Name)" parent = @{ id = "0" } } $content = Get-Content $file.FullName -Encoding UTF8 $stream = ([IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($content))) #Will Fail if File Already exists since we are not adding versions. #Adding GetResult makes it synchronous essentially. you could just getAwaiter and wait till IsCompleted is True $res = $uc.FilesManager.UploadAsync($request, $stream ).GetAwaiter().GetResult() }catch{Write-Output "Failed to upload $($file.FullName)"} } #Get the number of Items in the Folder $totalItems = $uc.FoldersManager.GetFolderItemsAsync($folderID,$pagesize,$offset).GetAwaiter().GetResult().TotalCount #show uploaded files #How many iterations of paging it will take to get all items assuming nobody adds items to the Box folder while we are in the loop $iters = [math]::Ceiling($totalItems / $pagesize) for ($i=0; $i -lt $iters; $i++){ $offset = $i * $pagesize $uc.FoldersManager.GetFolderItemsAsync($folderID,$pagesize,$offset).GetAwaiter().GetResult().entries.name }
-
Hi !
Welcome to the community and thank you for an awesome first post!
We really appreciate you sharing this solution that you found with the rest of the Box Community! Coming back to let other community members know how you were able to tackle a problem keeps the community thriving.
Thanks again for your contribution and for your time in the community!
Please sign in to leave a comment.
Comments
9 comments