Enabling basic authentication in webview without custom WebViewClient in Xamarin Forms - xamarin.forms

I'm using a webview in my Xamarin Forms project with Hybrid Renderer and webview, because I have to inject javascript code inside the page.
In my main project I have a CustomWebview.cs:
namespace ClotureSiadForms.Renderer
{
public class CustomWebView : WebView
{
public string js = "";
public CustomWebView()
{
Navigating+= WebViewNavigating;
Navigated+=WebViewNavigated;
}
private void WebViewNavigated(object sender, WebNavigatedEventArgs args)
{
EvaluateJavaScriptAsync(js);
}
public void WebViewNavigating(object sender, WebNavigatingEventArgs args)
{
if (args.Url.StartsWith("tel:"))
{
var tel = args.Url.Split(':')[1];
args.Cancel = true;
Xamarin.Essentials.PhoneDialer.Open(tel);
}
else if (!args.Url.StartsWith("http") || args.Url.EndsWith(".apk") || args.Url.EndsWith(".pdf") || args.Url.EndsWith(".zip"))
{
args.Cancel = true;
Xamarin.Essentials.Launcher.OpenAsync(args.Url);
}
}
}
}
In my Android project I have a HybridWebViewRenderer.cs:
[assembly: ExportRenderer(typeof(CustomWebView), typeof(HybridWebViewRenderer))]
namespace ClotureSiadForms.Droid.Renderer
{
internal class HybridWebViewRenderer : WebViewRenderer
{
public HybridWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
CustomWebView webview = e.NewElement as CustomWebView;
Control.Settings.JavaScriptEnabled = true;
Control.Settings.DomStorageEnabled = true;
Control.Settings.SavePassword = true;
}
}
}
}
As is, it worked and was able to download files
But as I needed basic authentication, I added a custom webviewclient inside HybridWebViewRenderer.cs:
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
CustomWebView webview = e.NewElement as CustomWebView;
Control.Settings.JavaScriptEnabled = true;
Control.Settings.DomStorageEnabled = true;
Control.Settings.SavePassword = true;
var login = Preferences.Get("login", "");
var password = Preferences.Get("password", "");
Control.SetWebViewClient(new AuthWebViewClient(login, password));
}
}
public class AuthWebViewClient : WebViewClient
{
private string Username;
private string Password;
public AuthWebViewClient(string username, string password)
{
Username = username;
Password = password;
}
public override void OnReceivedHttpAuthRequest(Android.Webkit.WebView view, HttpAuthHandler handler, string host, string realm)
{
handler.Proceed( Username,Password);
}
}
And authentication works, but WebViewNavigating is now called once, then the custom client is set and then WebViewNavigating is never more called.
Then my question is, can't I use basic auth without a custom client or is there a way to keep using my customwebview with the client ?

And authentication works, but WebViewNavigating is now called once, then the custom client is set and then WebViewNavigating is never more called.
I tested the code you provided and added Breakpoint to WebViewNavigating method. Even if you do not add webviewclient, it will only call WebViewNavigating once.
You can put the code in WebViewNavigating to ShouldInterceptRequest:
public class AuthWebViewClient : WebViewClient
{
...
public override WebResourceResponse ShouldInterceptRequest(Android.Webkit.WebView view, IWebResourceRequest request)
{
var url = request.Url;
...
}
}
Whenever the WebView begins loading a new page, it will call ShouldInterceptRequest.

Related

Allow popups to work in Xamarin Forms WebView

This seems like a simple thing - I want to use a WebView to load / run some JavaScript from a 3rd party.
When I load the page I get a message saying "You must allow popups for this to work". I think I need to create a custom WebView (for iOS and Android!?) and I assume I need one in the shared project too?
I added this to my Android project:
using Android.Content;
using MyApp.Custom;
using MyApp.Droid.Custom;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace MyApp.Droid.Custom
{
public class CustomWebViewRenderer : WebViewRenderer
{
public CustomWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
Control.Settings.JavaScriptCanOpenWindowsAutomatically = true;
Control.Settings.JavaScriptEnabled = true;
}
}
}
and this to my shared project
using Xamarin.Forms;
namespace MyKRing.Custom
{
public class CustomWebView : WebView
{
}
}
and then used "CustomWebView" in my XAML.
<custom:CustomWebView x:Name="WebView"
HeightRequest="1000"
Source="{Binding WebViewContent}"
WidthRequest="1000" />
My TestPageViewModel has
public WebViewSource WebViewContent
{
get => _webViewContent;
set => SetProperty(ref _webViewContent, value);
}
public TestPageViewModel()
{
AmountEntryFrameVisible = true;
NuapayFrameVisible = false;
ConfirmLoadCommand = new Command(ExecuteConfirmLoadCommand);
var localHtml = new HtmlWebViewSource();
localHtml.Html = #"<html>
<body>
<p>This is a test</p>
<script src='https://testurl.com/test.js\'></script>
<script>
TestJS.showUI('1234567890', 'https://testurl.com/ui/');
</script>
</body>
</html>";
WebViewContent = localHtml;
}
With the aim of making the JS run when the page is loaded by WebView.
Am I anywhere close? What is wrong with the above, as it just doesn't do anything (that ( can see).
1. Create the CustomWebView and create the ShowDialog method.
public class CustomWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(CustomWebView ),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void ShowDialog(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
}
2. Consume the CustomWebView
<local:CustomWebView x:Name="customWebView " Uri="index.html" />
3. Register the ShowDiaplg action to be invoked from JavaScript
customWebView.RegisterAction(data => DisplayAlert("Alert", DateTime.Now + "/n" + data, "OK"));
4. Create the custom renderer on Android platform.
CustomWebViewRenderer .cs:
[assembly: ExportRenderer(typeof(CustomWebView ), typeof(CustomWebViewRenderer))]
namespace CustomRenderer.Droid
{
public class CustomWebViewRenderer: WebViewRenderer
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge2.showDialog(data);}";
Context _context;
public CustomWebViewRenderer (Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((CustomWebView )Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient2(this, $"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JSBridge2(this), "jsBridge2");
Control.LoadUrl($"file:///android_asset/Content/{((CustomWebView )Element).Uri}");
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((CustomWebView )Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
JavascriptWebViewClient2.cs:
public class JavascriptWebViewClient2 : FormsWebViewClient
{
string _javascript;
public JavascriptWebViewClient2(CustomWebViewRenderer renderer, string javascript) : base(renderer)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
JSBridge2.cs:
public class JSBridge2 : Java.Lang.Object
{
readonly WeakReference<CustomWebViewRenderer > hybridWebViewRenderer;
public JSBridge2(CustomWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<CustomWebViewRenderer >(hybridRenderer);
}
[JavascriptInterface]
[Export("showDialog")]
public void ShowDialog(string data)
{
CustomWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
((CustomWebView)hybridRenderer.Element).ShowDialog(data);
}
//string ret = "Alert :" + DateTime.Now;
//return ret;
}
}
I followed the code sample in the link below. https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/customrenderers-hybridwebview/

Pass JWT token as header in Xamarin WebView

I'm using Xamarin forms WebView control to display a page that uses Authentication JWT token. I could not find any samples that does this either in Microsoft site or any blogs.
Most closest answer I found is to create a renderer for the control in each platform (iOS and Droid). But I'm not sure on which event I should override the request and the format in which the auth header can be passed. Appreciate any help.
You could try the method below:
For Android:
public class FormsWebViewRenderer : ViewRenderer<Xamarin.Forms.WebView, Android.Webkit.WebView>
{
Android.Content.Context _localContext;
public FormsWebViewRenderer(Context context) : base(context)
{
_localContext = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
Dictionary<string, string> headers = new Dictionary<string, string>
{
["A-custom-header"] = "a custom value"
};
Android.Webkit.WebView webView = Control as Android.Webkit.WebView;
if (Control == null) {
webView = new Android.Webkit.WebView(_localContext);
SetNativeControl(webView);
}
webView.Settings.JavaScriptEnabled = true;
webView.Settings.BuiltInZoomControls = true;
webView.Settings.SetSupportZoom(true);
webView.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
webView.ScrollbarFadingEnabled = false;
webView.SetWebViewClient(new FormsWebViewClient(headers));
UrlWebViewSource source = Element.Source as UrlWebViewSource;
webView.LoadUrl(source.Url, headers);
}
}
public class FormsWebViewClient : Android.Webkit.WebViewClient
{
public Dictionary<string, string> headers { get; set; }
public FormsWebViewClient(Dictionary<string, string> requestHeaders)
{
headers = requestHeaders;
}
public override void OnPageStarted(Android.Webkit.WebView view, string url, Android.Graphics.Bitmap favicon)
{
base.OnPageStarted(view, url, favicon);
System.Diagnostics.Debug.WriteLine("Loading website...");
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
System.Diagnostics.Debug.WriteLine("Load finished.");
}
public override void OnReceivedError(Android.Webkit.WebView view, IWebResourceRequest request, WebResourceError error)
{
base.OnReceivedError(view, request, error);
}
}
For ios:
public class FormsWebViewRenderer : ViewRenderer<WebView, UIWebView>
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var webView = Control as UIWebView;
if (webView == null) {
webView = new UIWebView();
SetNativeControl(webView);
}
webView.ScalesPageToFit = true;
webView.LoadStarted += (object sender, System.EventArgs evtArgs) => {
System.Diagnostics.Debug.WriteLine("Loading...");
};
webView.LoadFinished += (object sender, System.EventArgs evtArgs) => {
System.Diagnostics.Debug.WriteLine("Load finished.");
};
if (e.NewElement != null) {
UrlWebViewSource source = (Xamarin.Forms.UrlWebViewSource)Element.Source;
var webRequest = new NSMutableUrlRequest(new NSUrl(source.Url));
var headerKey = new NSString("A-custom-header");
var headerValue = new NSString("a custom value");
var dictionary = new NSDictionary(headerKey, headerValue);
webRequest.Headers = dictionary;
this.Control.LoadRequest(webRequest);
}
}
}

Xamarin Forms WebView CanGoForward property is inaccurate on sites that utilize the History API

I am having an issue that relates to a post on GitHub. User nirbil summarizes it best:
If a website utilizes the historyApi then the WebView's CanGoBack CanGoForward are wrong.
Steps to reproduce -
Set the webview's source to a single page web application
Navigate within the application to a different url (should utilize
the pushstate)
Navigate back -> CanGoForward changes to true
Navigate within the application again by following some link ->
CanGoForward erroneously does not change to false
On windows this behavior can be traced to the webview's renderer - https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.UAP/WebViewRenderer.cs
Here is the original post on GitHub https://github.com/xamarin/Xamarin.Forms/issues/7691
A lot of ideas are suggested on how to solve the problem however, my grasp of Xamarin Forms is somewhat lacking. Any ideas on how to solve the issue?
You could add the source of webview to use renderer. I use the code sample on GitHub. https://github.com/xamarin/Xamarin.Forms/issues/7691
The original:
Set the source of webview.
<WebView x:Name="webView" Source="https://www.google.com" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></WebView>
Now:
This question on GitHub has been collected. You could follow it on GitHub. It would be fixed soon.
I solved it using a custom Xamarin Forms WebView with custom CanGoBack/Forward properties.
You can find the it here:
https://github.com/nirbil/XF.CanGoWebView
Following are the main bits of the solution.
CustomWebView control with new bindable properties:
public class CustomWebView : WebView
{
public CustomWebView() {}
public static BindableProperty CustomCanGoForwardProperty =
BindableProperty.Create(
nameof(CustomCanGoForward),
typeof(bool),
typeof(CustomWebView),
false,
BindingMode.OneWayToSource);
public static BindableProperty CustomCanGoBackProperty =
BindableProperty.Create(
nameof(CustomCanGoBack),
typeof(bool),
typeof(CustomWebView),
defaultValue: false,
BindingMode.OneWayToSource);
public bool CustomCanGoForward
{
get => (bool)GetValue(CustomCanGoForwardProperty);
set => SetValue(CustomCanGoForwardProperty, value);
}
public bool CustomCanGoBack
{
get => (bool)GetValue(CustomCanGoBackProperty);
set => SetValue(CustomCanGoBackProperty, value);
}
}
Custom WebViewRenderer on Android:
public class CustomWebViewRenderer : WebViewRenderer
{
protected override WebViewClient GetWebViewClient()
{
CustomWebViewClient webViewClient = new CustomWebViewClient(this);
webViewClient.AddressChanged += AddressChanged;
return webViewClient;
}
private void AddressChanged(string url)
{
if (Element is CustomWebView customWebView && Control != null)
{
customWebView.CustomCanGoBack = Control.CanGoBack();
customWebView.CustomCanGoForward = Control.CanGoForward();
}
}
}
Android CustomWebViewClient that intercepts changes and alerts the renderer:
public class CustomWebViewClient: FormsWebViewClient
{
public delegate void AddressChangedEventHandler(string url);
public event AddressChangedEventHandler AddressChanged;
public CustomWebViewClient(WebViewRenderer renderer) : base(renderer) {}
public override void DoUpdateVisitedHistory(WebView view, string url, bool isReload)
{
base.DoUpdateVisitedHistory(view, url, isReload);
AddressChanged?.Invoke(view.Url);
}
}
Custom WebViewRenderer on UWP:
public class CustomWebViewRenderer : WebViewRenderer
{
bool _registeredControl = false;
long _goBackRegistrationToken = 0;
long _goForwardRegistrationToken = 0;
protected override void Dispose(bool disposing)
{
UnregisterControl();
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
RegisterControl();
}
private void UnregisterControl()
{
if (Control != null && _registeredControl)
{
_registeredControl = false;
Control.UnregisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoBackProperty, _goBackRegistrationToken);
Control.UnregisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoForwardProperty, _goForwardRegistrationToken);
}
}
private void RegisterControl()
{
if (Control != null && !_registeredControl)
{
_registeredControl = true;
_goBackRegistrationToken = Control.RegisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoBackProperty, new Windows.UI.Xaml.DependencyPropertyChangedCallback(OnWebViewCanGoBackChanged));
_goForwardRegistrationToken = Control.RegisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoForwardProperty, new Windows.UI.Xaml.DependencyPropertyChangedCallback(OnWebViewCanGoForwardChanged));
}
}
private void OnWebViewCanGoBackChanged(Windows.UI.Xaml.DependencyObject sender, Windows.UI.Xaml.DependencyProperty dp)
{
((CustomWebView)Element).CustomCanGoBack = Control.CanGoBack;
}
private void OnWebViewCanGoForwardChanged(Windows.UI.Xaml.DependencyObject sender, Windows.UI.Xaml.DependencyProperty dp)
{
((CustomWebView)Element).CustomCanGoForward = Control.CanGoForward;
}
}

Show login form after pressing F12 Key

I have an App in Winforms. VS 2010 C#.
What i am trying to do is when the user that is logged in. presses the F12 key the Login form shows up and another user enters the username and password and logs in.
I have attached my Login.cs, Program.cs and Form1.cs
In my main form(Form1.cs) when the user presses the F12 key i am able to show the login form but when i enter the username and password nothing happens.
Right now i am capturing the username when the user first time logs in. I also want to capture the new user when presses the F12 key and logs in.
i am showing the username in a label
label1.Text = myuser.getUserName();
I have tried some code in FORM.CS under Keypress event but it doesn't work
//////////**Program.CS**////////////////
namespace BusinessLayer
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
DialogResult result;
var loginForm = new Login();
result = loginForm.ShowDialog();
if (result == DialogResult.OK)
{
// login was successful
Application.Run(new Form1(loginForm.usr));
}
}
}
}
///////////////////////////////////**Login.CS**/////////////////////
namespace BusinessLayer
{
public partial class Login : Form
{
UserName myuser;
public Login()
{
InitializeComponent();
}
private void btnLogin_Click(object sender, EventArgs e)
{
if (CheckPasswordManager.CheckPassword(txtUserID.Text, txtPassword.Text) > 0)
{
usr = new UserName(txtUserID.Text);
DialogResult = DialogResult.OK;
}
else
{
MessageBox.Show("wrong");
}
}
public UserName usr
{
get
{
return myuser;
}
set
{
myuser = value;
}
}
}
}
//////////////////////**Form1.CS**////////////////
namespace BusinessLayer
{
public partial class Form1 : Form
{
UserName myuser;
public Form1(UserName usr)
{
myuser = usr;
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.DataSource = BookingManager.GetList();
label1.Text = myuser.getUserName();
int GetBookEntryID = Int32.Parse(this.dataGridView1.CurrentRow.Cells["booking_entry_id"].Value.ToString());
dataGridView2.DataSource = ProcessManager.GetList(GetBookEntryID);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
String s = e.KeyCode.ToString();
switch (s)
{
case "F12":
var loginForm = new Login();
this.Hide();
var loginForm = new Login();
loginForm.Show();
}
}
}
}
////////////////username.cs
namespace PassUsername
{
public class Username
{
string userName;
public Username(string uName)
{
userName = uName;
}
public string getUserName()
{
return userName;
}
}
}

Retrieving Custom Attribute on a web page class (.net)

I've create a custom attribute for my web pages... In the base page class I've created I'm trying to pull if that attribute has been set. Unfortunately it does not come back as part of the GetCustomAttributes function. Only if I explicitly use the typeof(myclass) to create the class. I have a feeling it's due to how asp.net classes are generated. Anyone have any suggestions on how to get this to work?
namespace SecuritySample.SecurityCode
{
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public sealed class MHCSecurityAttribute : Attribute
{
private string _permissionSet;
private bool _viewable;
public MHCSecurityAttribute(string permission, bool viewable)
{
_permissionSet = permission;
_viewable = viewable;
}
public string PermissionSetRequired
{
get { return _permissionSet; }
}
public bool Viewable
{
get { return _viewable; }
}
}
}
AdminOnly class
using System;
using SecuritySample.SecurityCode;
namespace SecuritySample
{
[MHCSecurityAttribute("testpermission", false)]
public partial class AdminOnlyPage : BasePage, IMHCSecurityControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
public void DisableControl()
{
Server.Transfer("Error.aspx");
}
public void EnableControl()
{
}
}
}
BasePage class
using System.Web.UI.WebControls;
namespace SecuritySample.SecurityCode
{
public class BasePage : Page
{
private string _user;
protected override void OnLoadComplete(EventArgs e)
{
base.OnLoadComplete(e);
_user = string.Empty;
if (Session.Contents["loggedInUser"] != null)
_user = Session["loggedInUser"].ToString();
// perform security check
// check page level
if (this is IMHCSecurityControl)
{
System.Reflection.MemberInfo info = this.GetType();
object[] attributes = info.GetCustomAttributes(false);
bool authorized = false;
if ((attributes != null) && (attributes.Length > 0))
{
foreach(MHCSecurityAttribute a in attributes)
{
if ((MHCSecurityCheck.IsAuthorized(_user, a.PermissionSetRequired)))
{
((IMHCSecurityControl) this).EnableControl();
authorized = true;
break;
}
}
if (!authorized)
((IMHCSecurityControl)this).DisableControl();
}
}
}
}
}
You have set the AttributeUsage.Inherited member to false. When set to false the attribute is not inherited by classes that inherit from your class (which is how Pages/Controls work in ASP.NET). This is why when you explicitly use typeof(your class name) the attribute is found.

Resources