Select to view content in your preferred language

Need an example implementing the new OAuth User Authentication mechanism for Unity

247
8
2 weeks ago
GeoffreyBund
Emerging Contributor

Hi all,

I've recently updated to ArcGIS Unity SDK v2 from v1.6 . And as mentioned in their documentation, the previously working user authentication mechanism I'd implemented is now broken with v2.0. What I'd do usually is:

 Previously I could generate a new ArcGISOAuthAuthorizationCredential object with these tokens. But with v.2 I'm running into an error that seems to be dll related since the RT_GEOAuthAuthorizationCredential_create doesnt seem to exist.

Could someone please help me understand how to proceed with ArcGIS Unity SDK v2.0 after getting these tokens? The sample code, scenes, and documentation provided for v2.0 are, honestly, as confusing as how they have always been previously.

@MichaelBranscomb @RexHansen ?

0 Kudos
8 Replies
AShahbaz
Esri Contributor

Hi Geoffrey,

You are correct, RT_GEOAuthAuthorizationCredential_create doesn't exist anymore. The most straight-forward method for user authentication is to create OAuth User Configuration(s) for the content you are accessing either through the UI or the API (in case of API you can add the configuration to ArcGISAuthenticationManager.OAuthUserConfigurations, as done in the OAuthScene script)

There are two separate scripts that take care of the steps you mentioned above: ArcGISEditorOAuthUserLoginPromptHandler (used for edit-time login) and SampleOAuthUserLoginPromptHandler (used for standalone apps and optionally play-mode in editor).
To enable the latter, you need to add a SampleAuthenticationHandlerInitializer component to the arcgis map.

These scripts will open the browser for login and process the authorization code to create the needed ArcGISCredential object(s). These credentials are stored in ArcGISCredentialStore which are reused for authorizing any other matching content and are automatically refreshed (if applicable).

If you want to implement your own process for handling the login, the easiest way is to modify the HandleOAuthUserLoginPrompt method in SampleOAuthUserLoginPromptHandler to return the response uri string you retrieved using your own method for getting the authorization code (should be something like "<redirect uri>?code=<authorization code>"). That authorization code will be used to add an ArcGISCredential to the ArcGISCredentialStore. If you need to read the generated access and/or refresh token, you can get them through the credential store (find the right credential in store, create an ArcGISOAuthUserCredential using its handle, and perform a GetTokenInfoAsync).

If you do not wish to create oauth user configurations beforehand, there is a way to go around it, but I dont recommend it since you would have to implement additional mechanisms for managing the pending authentication challenges.

One final note: SampleOAuthUserLoginPromptHandler is a sample code. We recommend adjusting it to the security needs of your project.

Hope this helps.

0 Kudos
GeoffreyBund
Emerging Contributor

Hi @AShahbaz the sample code seems incomplete from my POV. I've modified the SampleOAuthUserLoginPromptHandler  method to return a uri of the form <redirect uri>?code=<authorization code>.

But it doesn't automatically seem to add an ArcGISCredential to the ArcGISCredentialStore like you were hinting at. Are there any other steps that I'm missing here? Do I need to create a challenge handler somewhere or initialize ArcGISRuntimeEnvironment.AuthenticationManager.CfredentialStore? 

0 Kudos
AShahbaz
Esri Contributor

Are there any other steps that I'm missing here? Do I need to create a challenge handler somewhere or initialize ArcGISRuntimeEnvironment.AuthenticationManager.CfredentialStore?

Is there any error or warning in the console? Is the HandleOAuthUserLoginPrompt ever executed? It wont reach that point if it doesnt find a matching oauth user configuration. You shouldn't need to initialize anything beside attaching SampleAuthenticationHandlerInitializer somewhere (and possibly modifying it to point to your custom handler if you have created a new one). Can you authenticate with the original SampleOAuthUserLoginPromptHandler or the editor version?

0 Kudos
GeoffreyBund
Emerging Contributor

In my app, I just get something like "A valid API Key or OAuth User Configuration is required for www.arcgis.com".

On the OAuth sample scene, I've tried using attaching the SampleAuthenticationHandlerInitializer component to an ArcGISMap and modified it to call oauthUserLoginPromptHandler.HandleOAuthUserLoginPrompt(authorize_endpoint, "http://localhost:12345/") on Start().  When I hit play on Unity Editor the Application.OpenUrl(authorizeURL) doesn't seem to do anything. The editor just gets stuck on authorizationTask.Wait().  

In any case, my understanding from your suggestions has been that HandleOAuthUserLoginPrompt(authorize_endpoint, "http://localhost:12345/") would do everything needed to create and store a credential in the credentialstore. But I dont see how thats possible because its just returning a string. I'm assuming another function needs to be called to process that string somehow. ArcGISAuthenticationManager has a function OnOAuthUserLoginIssued which seems to process the url of the form "<redirect uri>?code=<authorization code>" by using a ArcGISOAuthUserLoginPrompt.Respond function. So, I'm very confused as to how an HandleOAuthUserLoginPrompt function that just returns a string and doesnt invoke any events like OAuthUserLogin is supposed to be the complete solution. Please let me know if my reasoning makes sense. 

private void OnOAuthUserLoginIssued(ArcGISOAuthUserLoginPrompt prompt)
{
var response = prompt.RedirectURL + "?error=No handler provided for OAuthUserLogin.";

if (OAuthUserLoginPromptHandler != null)
{
response = OAuthUserLoginPromptHandler.HandleOAuthUserLoginPrompt(prompt.AuthorizeURL, prompt.RedirectURL);
}

prompt.Respond(response);
}

 

0 Kudos
AShahbaz
Esri Contributor

`OnOAuthUserLoginIssued` is a callback function that is called when authentication is needed for a layer. Its argument, prompt, contains the AuthorizationURL and RedirectURL. The former is the url that is used for logging in (in your case probably something like "https://www.arcgis.com/sharing/rest/oauth2/authorize?...").

From there, `OAuthUserLoginPromptHandler.HandleOAuthUserLoginPrompt(prompt.AuthorizeURL, prompt.RedirectURL)` will be called, which returns a string that should contain "?code=<authorization code>" (or "?error=..." in case of an error). That string is used to respond to the prompt. The authorization code is then extracted from the response and used for generating the access token. So, you are right, calling `HandleOAuthUserLoginPrompt` on its own has no effect, it has to be called through the event that is triggered automatically when needed. To be clear, this is just a short overview of what is happening, you don't need to change anything here. 

What `OAuthUserLoginPromptHandler.HandleOAuthUserLoginPrompt` does gets a bit complicated because we differentiate between edit mode, in-editor play mode, desktop, and mobile.  `OAuthUserLoginPromptHandler` is of an abstract type. Its concrete implementation is assigned based on the current mode:

  • In the simplest case of edit mode, the concrete implementation is inside `ArcGISEditorOAuthUserLoginPromptHandler`, where a browser is opened with AuthorizationURL and an httplistener listens to RedirectUrl to receive the server response url. 
  • For the case of in-editor play mode, the concrete implementation is either SampleOAuthUserLoginPromptHandler (if SampleAuthenticationHandlerInitializer is attached somewhere) or ArcGISEditorOAuthUserLoginPromptHandler (otherwise). The behavior is largely the same in either case.
  • For standalone builds (desktop and mobile) the concrete implementation is SampleOAuthUserLoginPromptHandler (a SampleAuthenticationHandlerInitializer is always required for standalone builds to work). On desktop, the behavior is generally the same as before, on mobile a different approach is used for logging in. 

 

I've tried using attaching the SampleAuthenticationHandlerInitializer component to an ArcGISMap and modified it to call oauthUserLoginPromptHandler.HandleOAuthUserLoginPrompt(authorize_endpoint, "http://localhost:12345/") on Start().

All `SampleAuthenticationHandlerInitializer ` does is to define what concrete handler should be used for the in-editor play mode and standalone builds. You shouldn't call the handler from there. The handler is by default  SampleOAuthUserLoginPromptHandler, but you can assign a different one if you have implemented a separate script.

When I hit play on Unity Editor the Application.OpenUrl(authorizeURL) doesn't seem to do anything. The editor just gets stuck on authorizationTask.Wait().

Could your system have some security restrictions for opening a browser from other apps? There is also a chance that this line is never reached if the authentication configuration is missing or invalid (see below).  For debugging purpose, you could log the authorizeURL to console right before OpenUrl() and manually open it in the browser and see what happens after log in.

In my app, I just get something like "A valid API Key or OAuth User Configuration is required for www.arcgis.com".

If you see this warning, the event that I mentioned before will not be triggered, so none of the things that I talked about will happen. This warning usually means that either the OAuth User Configuration that you created doesn't have the correct portal address or one of its required fields (client id or redirect url) are empty. Note that the portal address should include the full url (e.g., https://www.arcgis.com). There is another possibility that the configuration that was added through the API was overwritten by the map component. Generally, this shouldn't happen when using the OAuthSene script, but maybe there is a race condition on the specific platform. For test purposes, you could add the configuration to the map component through the UI rather than the API. 

Here are the steps I would follow for debugging the issue:

  1. Start with a new scene containing a simple map. Add an authentication configuration to the map component through the UI. Add a corresponding layer through the UI and set its authentication type to user authentication (follow the documentation if needed). Verify that you can load the layer during edit mode.
  2. Press play and verify that the authentication still works (by design you will be prompted to log in every time you press play).
  3. Revert any changes to `SampleAuthenticationHandlerInitializer` and `SampleOAuthUserLoginPromptHandler`, attach a `SampleAuthenticationHandlerInitializer` to the map component. Press play and verify that it works. 
  4. Deploy the app to its end-device and verify it works. Note that our our sample code in `SampleOAuthUserLoginPromptHandler` doesn't accept "http://localhost:..." as a redirect url on mobile platforms (ios, android, hololens, visionos). There, we use deep linking for forwarding the server response. So, if you want to complete this steps with our sample code, you need to register a new redirect url for your content on arcgis (e.g., "unity://auth") and follow the unity manual to configure your app to listen to that link. 
  5. If desired, modify the SampleOAuthUserLoginPromptHandler to use your custom mechanism for retrieving the authorization code and test it in the play mode and standalone builds. The custom mechanism will not be used during edit time.
0 Kudos
GeoffreyBund
Emerging Contributor

Hi @AShahbaz , I followed the steps you suggested. I manually created an authentication configuration on a map component. I also realized I was using the wrong url for service url. Instead of the tile server url (e.g. https://tiles.arcgis.com/tiles/ ... /rest/services/SceneXYZ/SceneServer), I had to give the url of the item in the address bar (https://xyz.maps.arcgis.com/home/item.html?id=abcd12345). When I do this for the Oauth sample scene, I'm finally able to open a browser window to log into the arcgis portal and load a private layer. 

But, what should be the procedure for authentication when I'm adding layers during runtime? I don't see SampleOAuthUserLoginPromptHandler.HandleOAuthUserLoginPrompt triggered when I create a layer from a script like in the SampleAPIMapCreator script. For example, when I do the following:

ArcGISLayer newLayer = new ArcGISLayer(source, arcGISLayerType, APIKey);
layerId = exampleArcGISMap.Layers.Add(newLayer);

 I can change the layer authentication type in Editor to "User Authentication". But I don't see a way to do this when creating a layer via script. I'm guessing thats what is needed to trigger the authentication process in SampleOAuthUserLoginPromptHandler? 

0 Kudos
GeoffreyBund
Emerging Contributor

Nevermind. I was mistaken. User login does seem to get triggered when I create the private layer through script in the SampleAPIMapCreator . Maybe there's an implementation issue in my custom script. Thanks for your help so far!

0 Kudos
AShahbaz
Esri Contributor

When adding a layer that needs authentication through API, it's best to use an empty string as the api key (this has the same effect as assigning "User Authentication" to a layer in the editor). There are some circumstances that the oauth workflow will still be triggered if the provided api key doesn't have enough authorization, but an empty string is the best bet, if you intend to load the layer with oauth.

Additionally, there are two timelines that are important here:

  • By the time a layer is being loaded, its oauth configuration must be available, otherwise the oauth workflow wont be triggered. If the configuration is being added through API at runtime, you may need to do something like `mapComponent.View.Map.Layers.At(<index>).RetryLoad()` to force a reload.
  • When the map component initializes (and in some other situations like changing the map mode or authentication settings), the map component will clear/overwrite the `ArcGISAuthenticationManager.OAuthUserConfigurations()`, which removes any configurations added through api. To avoid this, those configurations have to be added after the map has initialized (e.g., do so during `Start()` rather than `Enable()`) and need to be re-added if the map is re-initialized due to some property changes. After adding the configurations, a `RetryLoad()` is likely needed.
0 Kudos