Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not passing StartUrl to Browser in .net-ios #433

Closed
JimWilcox3 opened this issue May 23, 2024 · 3 comments
Closed

Not passing StartUrl to Browser in .net-ios #433

JimWilcox3 opened this issue May 23, 2024 · 3 comments

Comments

@JimWilcox3
Copy link

I have upgraded my Xamarin apps to .net8.0-ios rather than NetMaui. .net8.0-ios is very much supported now and will be in the future. When I upgrade IdentityModel.OidcClient to version 6.0.0, when it makes the call to open the Browser, it no longer passes a StartUrl. The parameter is null. My Browser is implemented as follows.

 public class ASWebAuthenticationSessionBrowser : IOSBrowserBase
  {
      public ASWebAuthenticationSessionOptions SessionOptions { get; }

      public ASWebAuthenticationSessionBrowser(ASWebAuthenticationSessionOptions sessionOptions = null)
      {
          SessionOptions = sessionOptions;
      }

      /// <inheritdoc/>
      protected override Task<BrowserResult> Launch(BrowserOptions options, CancellationToken cancellationToken = default)
      {
          return Start(options, SessionOptions);
      }

      internal static Task<BrowserResult> Start(BrowserOptions options, ASWebAuthenticationSessionOptions sessionOptions = null)
      {
          var tcs = new TaskCompletionSource<BrowserResult>();

          ASWebAuthenticationSession asWebAuthenticationSession = null;
          asWebAuthenticationSession = new ASWebAuthenticationSession(
              new NSUrl(options.StartUrl),
              new NSUrl(options.EndUrl).Scheme,
              (callbackUrl, error) =>
              {
                  tcs.SetResult(CreateBrowserResult(callbackUrl, error));
                  asWebAuthenticationSession.Dispose();
              });

          if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
          {
              // iOS 13 requires the PresentationContextProvider set
              asWebAuthenticationSession.PresentationContextProvider = new PresentationContextProviderToSharedKeyWindow();
              // PrefersEphemeralWebBrowserSession is only available on iOS 13 and up.
              asWebAuthenticationSession.PrefersEphemeralWebBrowserSession = sessionOptions != null ? sessionOptions.PrefersEphemeralWebBrowserSession : false;
          }

          asWebAuthenticationSession.Start();

          return tcs.Task;
      }

      class PresentationContextProviderToSharedKeyWindow : NSObject, IASWebAuthenticationPresentationContextProviding
      {
          public UIWindow GetPresentationAnchor(ASWebAuthenticationSession session)
          {
              return UIApplication.SharedApplication.KeyWindow;
          }
      }

      private static BrowserResult CreateBrowserResult(NSUrl callbackUrl, NSError error)
      {
          if (error == null)
              return Success(callbackUrl.AbsoluteString);

          if (error.Code == (long)ASWebAuthenticationSessionErrorCode.CanceledLogin)
              return Canceled();

          return UnknownError(error.ToString());
      }
  }
   public abstract class IOSBrowserBase : IBrowser
   {
       public Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
       {
           if (string.IsNullOrWhiteSpace(options.StartUrl))
               throw new ArgumentException("Missing StartUrl", nameof(options));

           if (string.IsNullOrWhiteSpace(options.EndUrl))
               throw new ArgumentException("Missing EndUrl", nameof(options));

           return Launch(options, cancellationToken);
       }

       protected abstract Task<BrowserResult> Launch(BrowserOptions options, CancellationToken cancellationToken = default);

       internal static BrowserResult Canceled()
       {
           return new BrowserResult
           {
               ResultType = BrowserResultType.UserCancel
           };
       }

       internal static BrowserResult UnknownError(string error)
       {
           return new BrowserResult
           {
               ResultType = BrowserResultType.UnknownError,
               Error = error
           };
       }

       internal static BrowserResult Success(string response)
       {
           return new BrowserResult
           {
               Response = response,
               ResultType = BrowserResultType.Success
           };
       }
   }

I Use the following code to invoke a login:

  _options = new OidcClientOptions
  {
      Authority = vm.Config["Settings:Authority"],
      ClientId = vm.Config["Settings:ClientId"],
      Scope = vm.Config["Settings:Scope"],
      RedirectUri = vm.Config["Settings:RedirectUri"],
      PostLogoutRedirectUri = vm.Config["Settings:RedirectUri"],
      Browser = new ASWebAuthenticationSessionBrowser(new ASWebAuthenticationSessionOptions
      {
          PrefersEphemeralWebBrowserSession = true
      })
  };

  var oidcClient = new OidcClient(_options);

  try
  {
      var result = await oidcClient.LoginAsync();

      //Use the following in place of the above if you want to force a login
      //var parm = new Parameters
      //{
      //    { "prompt", "login" }
      //};
      //var result = await oidcClient.LoginAsync(new LoginRequest { FrontChannelExtraParameters = parm });

      if (!result.IsError)
      {
          vm.Tokens.AccessToken = result.AccessToken;
          vm.Tokens.IdentityToken = result.IdentityToken;
          vm.Tokens.RefreshToken = result.RefreshToken;
          vm.Tokens.AccessTokenExpiration = result.AccessTokenExpiration.DateTime.ToUniversalTime();
          vm.Tokens.Error = result.Error;
          //vm.Tokens.User = result.User;

          vm.Logger.LogInformation("{0} logged in!", result.User.Identity.Name);
      }
  }
  catch (Exception ex)
  {
      var s = ex.ToString();
  }

It works fine with version 5.2.1 but breaks in version 6.0.0. Is there anything I can do other than rewrite my app in NetMaui? I do have some paid time left with Duende if I need to use that to resolve this.

Thanks,
Jim

@JimWilcox3
Copy link
Author

In case you need it:

 public class ASWebAuthenticationSessionOptions
 {
     public bool PrefersEphemeralWebBrowserSession { get; set; }
 }

@JimWilcox3
Copy link
Author

JimWilcox3 commented Jul 30, 2024

The quick answer to this is to disable Pushed Authorization as so:

_options = new OidcClientOptions
{
    Authority = vm.Config["Settings:Authority"],
    ClientId = vm.Config["Settings:ClientId"],
    Scope = vm.Config["Settings:Scope"],
    RedirectUri = vm.Config["Settings:RedirectUri"],
    PostLogoutRedirectUri = vm.Config["Settings:RedirectUri"],
    Browser = new ASWebAuthenticationSessionBrowser(new ASWebAuthenticationSessionOptions
    {
        PrefersEphemeralWebBrowserSession = true
    }),
    LoggerFactory = vm.LoggerFactory,
    DisablePushedAuthorization = true
};

The reason being is that my license doesn't include Pushed Authorization. I have talked with the devs on this and they have decided that the discovery document should not advertise PAR if the license doesn't support it. I am guessing that in the next release, you will not need DisablePushedAuthorization = true, but for now, the quick fix is to include it.

Jim

@josephdecock
Copy link
Contributor

Thanks Jim, that's correct.

Two things came out of this investigation:

  1. PAR failure handling in OidcClient could be improved to make it more obvious that PAR is involved. I've created Improve PAR failure error handling #442 to track that.
  2. IdentityServer should not include the PAR endpoint in discovery when it is not actually supported (due to licensing level). I've created https://github.com/DuendeSoftware/product-engineering/issues/75 to track updates there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants