Any Oauth2/openId library working with Unity/UWP/Hololens2? IdentityModel not working

Hello,

I need to connect with an oauth2 server. I know that I could make the oauth2 request directly by my own with a http request, but, since authentication is a very sensitive topic, I’d rather use a very tested/extensive used library.

I have been using IdentityModel, (GitHub - IdentityModel/IdentityModel: .NET standard helper library for claims-based identity, OAuth 2.0 and OpenID Connect.), and it works pretty nice on Editor and Windows Standalone, but it doesn’t work on UWP/Hololens2.
So I’d like to ask for any suggestion of an oauth2 library that can work on the Unity ecosystem.

Thank you

Further information about the IdentityModel problem. I think it’s probably because some kind of single thread/multithread issue. If that would be the case, the strange thing about that is that it’s already working on editor and windows standalone.

I saw this thread from @bdovaz
Maybe it’s somehow related:

Unity 2021.3.9
IdentityModel 6.0.0
Code

using IdentityModel.Client;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
    public Text ui;

    async void Start()
    {
        await TestAsync();
    }

    public async Task TestAsync()
    {
        IdentityModel.Internal.TaskHelpers.CanConfigureAwaitFalse = false;
        IdentityModel.Internal.TaskHelpers.CanFactoryStartNew = false;
      
        var client = new HttpClient();
        TokenResponse responseToken = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
        {
            Address = "xxxxxxxxx",

            ClientId = "xxxxxxxxxxxxx",
            ClientSecret = "xxxxxxxxxxxx",
            //Scope = "api1",

            UserName = "xxxxxxxxxxxx",
            Password = "xxxxxx"
        });

        if (responseToken.IsError) throw new Exception(responseToken.Error);

        print(responseToken.AccessToken);

        ui.text = responseToken.AccessToken;
    }
}
}

Error:

Exception: An error occurred while sending the request
  at Test.TestAsync () [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext (System.Object stateMachine) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run () [0x00000] in <00000000000000000000000000000000>:0
  at System.Array+InternalEnumerator`1[T].get_Current () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.InvokeAction (System.Object state) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.RunCallback (System.Threading.ContextCallback callback, System.Object state, System.Threading.Tasks.Task& currentTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.Run (System.Threading.Tasks.Task ignored, System.Boolean canInlineContinuationTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishContinuations () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishStageThree () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task`1[TResult].TrySetResult (TResult result) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[TResult].SetResult (TResult result) [0x00000] in <00000000000000000000000000000000>:0
  at IdentityModel.Client.HttpClientTokenRequestExtensions.RequestPasswordTokenAsync (System.Net.Http.HttpMessageInvoker client, IdentityModel.Client.PasswordTokenRequest request, System.Threading.CancellationToken cancellationToken) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext (System.Object stateMachine) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run () [0x00000] in <00000000000000000000000000000000>:0
  at System.Array+InternalEnumerator`1[T].get_Current () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.InvokeAction (System.Object state) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.RunCallback (System.Threading.ContextCallback callback, System.Object state, System.Threading.Tasks.Task& currentTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.Run (System.Threading.Tasks.Task ignored, System.Boolean canInlineContinuationTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishContinuations () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishStageThree () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task`1[TResult].TrySetResult (TResult result) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[TResult].SetResult (TResult result) [0x00000] in <00000000000000000000000000000000>:0
  at IdentityModel.Client.HttpClientTokenRequestExtensions.RequestTokenAsync (System.Net.Http.HttpMessageInvoker client, IdentityModel.Client.ProtocolRequest request, System.Threading.CancellationToken cancellationToken) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext (System.Object stateMachine) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run () [0x00000] in <00000000000000000000000000000000>:0
  at System.Array+InternalEnumerator`1[T].get_Current () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation+<>c.<.cctor>b__7_0 (System.Object state) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.ArraySortHelper`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at UnityEngine.UnitySynchronizationContext+WorkRequest.Invoke () [0x00000] in <00000000000000000000000000000000>:0
  at UnityEngine.UnitySynchronizationContext.Exec () [0x00000] in <00000000000000000000000000000000>:0
  at UnityEngine.UnitySynchronizationContext.ExecuteTasks () [0x00000] in <00000000000000000000000000000000>:0
--- End of stack trace from previous location where exception was thrown ---

  at Test.Start () [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext (System.Object stateMachine) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run () [0x00000] in <00000000000000000000000000000000>:0
  at System.Array+InternalEnumerator`1[T].get_Current () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.InvokeAction (System.Object state) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.RunCallback (System.Threading.ContextCallback callback, System.Object state, System.Threading.Tasks.Task& currentTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.Run (System.Threading.Tasks.Task ignored, System.Boolean canInlineContinuationTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishContinuations () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishStageThree () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishStageTwo () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.Finish (System.Boolean bUserDelegateExecuted) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.TrySetException (System.Object exceptionObject) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[TResult].SetException (System.Exception exception) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException (System.Exception exception) [0x00000] in <00000000000000000000000000000000>:0
  at Test.TestAsync () [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext (System.Object stateMachine) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run () [0x00000] in <00000000000000000000000000000000>:0
  at System.Array+InternalEnumerator`1[T].get_Current () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.InvokeAction (System.Object state) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.RunCallback (System.Threading.ContextCallback callback, System.Object state, System.Threading.Tasks.Task& currentTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.Run (System.Threading.Tasks.Task ignored, System.Boolean canInlineContinuationTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishContinuations () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishStageThree () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task`1[TResult].TrySetResult (TResult result) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[TResult].SetResult (TResult result) [0x00000] in <00000000000000000000000000000000>:0
  at IdentityModel.Client.HttpClientTokenRequestExtensions.RequestPasswordTokenAsync (System.Net.Http.HttpMessageInvoker client, IdentityModel.Client.PasswordTokenRequest request, System.Threading.CancellationToken cancellationToken) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext (System.Object stateMachine) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run () [0x00000] in <00000000000000000000000000000000>:0
  at System.Array+InternalEnumerator`1[T].get_Current () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.InvokeAction (System.Object state) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.AwaitTaskContinuation.RunCallback (System.Threading.ContextCallback callback, System.Object state, System.Threading.Tasks.Task& currentTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.Run (System.Threading.Tasks.Task ignored, System.Boolean canInlineContinuationTask) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishContinuations () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task.FinishStageThree () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.Task`1[TResult].TrySetResult (TResult result) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[TResult].SetResult (TResult result) [0x00000] in <00000000000000000000000000000000>:0
  at IdentityModel.Client.HttpClientTokenRequestExtensions.RequestTokenAsync (System.Net.Http.HttpMessageInvoker client, IdentityModel.Client.ProtocolRequest request, System.Threading.CancellationToken cancellationToken) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext (System.Object stateMachine) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.EqualityComparer`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
  at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run () [0x00000] in <00000000000000000000000000000000>:0
  at System.Array+InternalEnumerator`1[T].get_Current () [0x00000] in <00000000000000000000000000000000>:0
  at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation+<>c.<.cctor>b__7_0 (System.Object state) [0x00000] in <00000000000000000000000000000000>:0
  at System.Collections.Generic.ArraySortHelper`1[T].get_Default () [0x00000] in <00000000000000000000000000000000>:0
  at UnityEngine.UnitySynchronizationContext+WorkRequest.Invoke () [0x00000] in <00000000000000000000000000000000>:0
  at UnityEngine.UnitySynchronizationContext.Exec () [0x00000] in <00000000000000000000000000000000>:0
  at UnityEngine.UnitySynchronizationContext.ExecuteTasks () [0x00000] in <00000000000000000000000000000000>:0
--- End of stack trace from previous location where exception was thrown ---

In case that it could be helpful to someone.

The problem was not the Identity Model library. The problem was using System.Net.Http in uwp/hololens 2

In order to make it work, I had to check this Capabilities:

If you mean using Redirect method for UWP, the app will not pass the review process with such capabilities.
https://afterlogic.com/mailbee-net/docs/OAuth2UWP.html

Answering my own question.

For using OAuth 2.0 Authorization Code Grant (OAuth 2.0 Authorization Code Grant Type),
use this package:
GitHub - cdmvision/authentication-unity: OAuth 2.0 Client for Unity

For using OAuth 2.0 Password Grant (OAuth 2.0 Password Grant Type) you can use my own lightweight implementation

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using UnityEngine;

public class OAuth2PasswordGrantManager : IDisposable
{
    private readonly string authUrl;
    private readonly string clientId;
    private readonly string clientSecret;
    private HttpClient client;

    public OAuth2PasswordGrantManager(string authUrl, string clientId, string clientSecret):
        this(authUrl, clientId, clientSecret, new HttpClientHandler() { UseProxy = false}){}

    internal OAuth2PasswordGrantManager(string authUrl, string clientId, string clientSecret, HttpMessageHandler handler)
    {
        this.authUrl = authUrl;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        client = new HttpClient(handler);
    }

    public async Task<TokenResponse> GetToken(string username, string password)
    {
        //Build the payload
        Dictionary<string, string> payload = new Dictionary<string, string>
        {
            { "grant_type", "password" },
            { "username", username },
            { "password", password },
            { "client_id", clientId },
            { "client_secret", clientSecret }
        };

        return await Post(payload);
    }

    public async Task<TokenResponse> Refresh(string refreshToken)
    {
        //Build the payload
        Dictionary<string, string> payload = new Dictionary<string, string>
        {
            { "grant_type", "refresh_token" },
            { "refresh_token", refreshToken },
            { "client_id", clientId },
            { "client_secret", clientSecret }
        };

        return await Post(payload);
    }

    private async Task<TokenResponse> Post(Dictionary<string, string>  payload)
    {
        HttpResponseMessage response = await client.PostAsync(authUrl, new FormUrlEncodedContent(payload));
        response.EnsureSuccessStatusCode();
        var jsonResponse = await response.Content.ReadAsStringAsync();

        return JsonUtility.FromJson<TokenResponse>(jsonResponse);
    }

    public void Dispose()
    {
        client.Dispose();
    }
}