How can I create a login page in Xamarin.Forms? - xamarin

How can I create a login page using Xamarin.Forms?
I'm debugging on Android device and
on the page I try to query the given username and password from an MSSQL database in MSSQL
and if the login be successful, the page will navigate to a new page, else will show authentication failed.
How can I do this in Xamarin.Forms?

(Xamarin LoginFlow Example linked at bottom of answer)
You would have multiple Xamarin.Forms pages and use a NavigationPage to push or pop them from the (lifecycle) stack.
Ref: https://developer.xamarin.com/api/type/Xamarin.Forms.NavigationPage/
Think of each Page a complete self-contained mini application. So the Login page might handle the getting the UserID/Password interactively from the user, performing a authorization check via a server via a Rest api that performs your SQL query. If login is successful it pushes a new Forms-based page on the NavigationPage stack. i.e.
SplashScreen -> LoginPage -> MainPagePage
LoginFlow Xamarin example
This sample demonstrates how to manipulate the navigation stack in order to only display the main page of the application once the user has successfully logged in.
For more information about the sample see Hierarchical Navigation.

What you need is the following functionalities:
Web APIs (Web Service for Login - Hosted on internet or local network - Connected with your MSSql Server)
A Login Screen in Xamarin.Forms Application (You can design it on your own or use the above mentioned demo)
On Login Screen's 'Login' button click, you need to call the Login Web Service from Xamarin.Forms application.
This requires networking (Internet Permission on Android Project, through Manifest.xml). You can use Microsoft.Net.Http for Web Service Calls.
(Look at this example http://www.c-sharpcorner.com/UploadFile/nirmal.hota/consuming-restful-web-service-in-xamarin-forms-project-using/ )
If your server responses with JSON and you need to parse JSON then you can use Json.Net (Newtonsoft.Json).
Note: What I am trying to tell you is that you need to use Web Services that are connected to the database and hosted on a server. From Xamarin.Forms application, you have to call this web-service with appropriate parameters. This requires networking, so you can use Microsoft.Net.Http (or there are other plugins also available). To get response from the Web Service, if it is in Json then you can use Newtonsoft.Json to parse Json.

There is a full example of an app with login page: xamarin-forms-samples/LoginDemo/ provided by Xamarin.

So lets say you have a LoginController on asp.net web api (if you don't know how to create an asp.net web api project etc. this is not the place to learn it:) )
[RoutePrefix("v1/login")]
public class LoginController : ApiController
{
[Route("")]
[HttpPost] //I'm using post!!!!!! you may use get etc.
public IHttpActionResult Login(UserLoginData request)
{
var userData = CheckFromDb(request);
return Json(userData);
}
}
It checks your request from db (UserLoginData is a simple class lets say holds username and password), than if user exists you return another class (lets say it is UserData hat holds name,surname, birthday etc.). If it can not find login it may return null. It's up to you.
So it will be available on your host machine like
localhost:34252/v1/login
(34252 your port-may change for you)
So you have to call it from the device (xamarin.forms) code like this
public string Post(string url, UserLoginData userLoginData)
{
//url is login url as defined above and userLoginData holds your
//user interface (textbox) data :)
using (var client = new HttpClient())
{
client.Timeout = new TimeSpan(0, 0, 30);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
StringContent content;
content = new StringContent(JsonConvert.SerializeObject(userLoginData), Encoding.UTF8, "application/json");
HttpResponseMessage response = client.PostAsync(url, content).Result;
if (!response.IsSuccessStatusCode)
{
throw new Exception("call failed");
}
//this is your response as json
var responseString = response.Content.ReadAsStringAsync().Result;
//you can convert it and check to a specific class if you need
var userData = JsonConvert.DeserializeObject<UserData>(responseString);
}
}
So UserLoginData and UserData classes both must exists on both sides. You can use Newtonsoft.Json library for json parsing (jsonconvert is it's method). Hope this helps you
And android device simulator networking is a bit tricky. You may not connect to localhost directy. It may take an address like 10.0.2.* for androidemulator or 10.71.34.* for xamarin simulator. Please check your network cards for ip addresses and findthe correct one via "cmd.exe" and "ipconfig /all" command.

Related

.Net MAUI WebAuthenticator CallbackUrl for social logins

I'm following this guide to setup authenticating with social logins using MSAL and Web Authenticator. The example call looks like this:
WebAuthenticatorResult authResult = await WebAuthenticator.Default.AuthenticateAsync(
new Uri("https://example.com/mobileauth/Microsoft"),
new Uri("myapp://"));
But what should the second URI parameter be? In the guide it says:
The URI the flow is expected to ultimately call back to, that is registered to your app.
So how do I register a URI like that to my app?? I've tried following this guide and have 'registered' my app in azure active directory but facebook/google etc won't accept urls of the form "myapp://" as valid redirect URIs... What am I missing?
Update
So the first half of the flow is working but I keep getting errors from the OAuth providers (the green highlight in the diagram isn't working).
This from Google:
And this from Facebook:
But I've added all these valid callback URLs:
I'm afraid that example is broken.
The second URI parameter represents where your app is listening to get the authentication result.
Actually, myapp:// is not a valid URI. You should try with something like myapp://callback.
Finally got to the bottom of it thanks to this old Xamarin issue: https://github.com/xamarin/Essentials/issues/1224#issuecomment-618192336
You have to set the "CallbackPath" property in the API config like so:
.AddGoogle(g => {
g.ClientId = "{Client ID}";
g.ClientSecret = "{Client Secret}";
g.CallbackPath = "/mobileauth"; //mobileauth is api controller
g.SaveTokens = true; })
And then tell the provider of that redirect e.g. adding "https://{API-URL}/mobileauth" in google console.

Microsoft Teams App - Add Authentication and Authorization for Task/Fetch Card Action

Located in the BotBuilder-Samples GitHub repo: https://github.com/microsoft/BotBuilder-Samples
There is a sample app: 54.teams-task-module. This app demonstrates a task/fetch action with a Url to a Custom Form which is rendered by a Razor Page.
https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/csharp_dotnetcore/54.teams-task-module
In the Bot, the OnTeamsTaskModuleFetchAsync method is overridden to return a TaskModuleResponse which tells the system to fetch the URL passed back to Teams in the response.
https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/csharp_dotnetcore/54.teams-task-module/Bots/TeamsTaskModuleBot.cs
protected override Task<TaskModuleResponse> OnTeamsTaskModuleFetchAsync(ITurnContext<IInvokeActivity> turnContext, TaskModuleRequest taskModuleRequest, CancellationToken cancellationToken)
{
var asJobject = JObject.FromObject(taskModuleRequest.Data);
var value = asJobject.ToObject<CardTaskFetchValue<string>>()?.Data;
var taskInfo = new TaskModuleTaskInfo();
switch (value)
{
case TaskModuleIds.YouTube:
taskInfo.Url = taskInfo.FallbackUrl = _baseUrl + "/" + TaskModuleIds.YouTube;
SetTaskInfo(taskInfo, TaskModuleUIConstants.YouTube);
break;
case TaskModuleIds.CustomForm:
taskInfo.Url = taskInfo.FallbackUrl = _baseUrl + "/" + TaskModuleIds.CustomForm;
SetTaskInfo(taskInfo, TaskModuleUIConstants.CustomForm);
break;
case TaskModuleIds.AdaptiveCard:
taskInfo.Card = CreateAdaptiveCardAttachment();
SetTaskInfo(taskInfo, TaskModuleUIConstants.AdaptiveCard);
break;
default:
break;
}
return Task.FromResult(taskInfo.ToTaskModuleResponse());
}
I have enabled developer tools in Teams and watched the network requests, as well as overridden every method I can find to try find an extensibility point to inject some sort of token into the request so that the URL can be secured from public anonymous access.
Question: The only way to provide authorization on the Razor Page I see right now is passing the token on the query string and using a custom authorization handler to process the token.
Is there a better way to inject a token or any other info into the task/fetch request so that the request can be authenticated and authorized?
To be clear on this, authentication -is- possible, but only for web pages (Adaptive Cards don't need it). This auth would rely on the standard SSO Teams offers for Task Modules as well as Tabs. See here for intro guidance: https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/authentication/auth-aad-sso?tabs=dotnet, especially:
The SSO API also works in task modules that embed web content.
So really your question kind of becomes "how do I do SSO for web content in Teams". Here's a great video, which includes a link to text (blog) version of the content: https://devblogs.microsoft.com/microsoft365dev/lets-decode-single-sign-on-sso-in-microsoft-teams-tabs/. Here's a working sample, with both Node and DotNet backend options: https://adoption.microsoft.com/sample-solution-gallery/pnp-sp-dev-teams-sample-tab-sso. Note that the samples and docs generally focus on doing an OnBehalfOf (OBO) operation to call Graph, but the principle remains the same - you get a JWT token that you can pass back to your backend, which you can then validate. You can also, from the token, get user info for the logged in user.
From my comments: Looking at it as "Web inside Adaptive" and revisiting the sample project and your information it does seem the "CustomForm" razor page is initializing the Teams JavaScript SDK.
This DOES mean I can authenticate this content using the SSO as you mentioned.
I had only thought it would work in a TAB, not inside a bot card.Solved, follow the tabs javascript SDK guidance.

MVC Prompt Query Parameter

I want to override Azure AD SSO login for a MVC web application. I don't want to log other applications out in the process, but require login for security purposes. I am using OAuth2.0 with OIDC via Owin for authentication.
I am trying to use the prompt=login query parameter, which should theoretically do the trick. I found a Github reference to this being recently made available in Core but cannot trace how to do it in MVC5.2
Is it possible to do it in the Application Builder? I tried adding .WithExtraQueryParameters ("prompt=login") to the ConfidentialClientApplicationBuilder when getting the access code. No luck.
Is there another workaround if the code doesn't come out-of-the-box?
EDIT: PublicClientApplication allows .WithPrompt(Prompt.Login) while ConfidentialClientApplication does not (also does not allow AcquireTokenInteractive) This is a web app so it needs the confidential builder. Tested using the Public builder and it logs in successfully. But I get an ActiveX instantiation error 'not in a single-threaded apartment'(?) Strange, unless that is how the token is being delivered perhaps. I also tested by changing to multitenant in Azure and by toggling Public client on and off.
Any ideas?
You could use ForceLogin to add paramter to ConfidentialClientApplicationBuilder.
ForceLogin: enables the application developer to have the user prompted for credentials by the service even if this would not be needed. This can be useful if Acquiring a token fails, to let the user re-sign-in. This is done by sending prompt=login to the identity provider. Again, we've seen it used in some security focused applications where the organization governance demands that the user re-logs-in each time they access specific parts of an application.
So, use the code as below:
result = await app.AcquireTokenInteractive(scopes)
.WithPrompt(Prompt.ForceLogin)
.ExecuteAsync();
The Modifier .WithExtraQueryParameters will not help you. You need to use .WithPrompt instead.Please refer article.
Example:
await PublicClientApplication
.AcquireTokenInteractive(scopes, null)
.WithAccount(CurrentUser)
.WithPrompt(Prompt.ForceLogin) // use login for your case
.ExecuteAsync();
I eventually resolved this as follows:
Under Notifications in the ConfigureAuth(IAppBuilder app) add a reference to a new task:
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
Then add the task:
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification
<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
// Forces the user to login.
if (notification.ProtocolMessage.Prompt == null)
{
notification.ProtocolMessage.Prompt = "login";
}
return Task.FromResult(0);
}
Hope that helps the next person with this issue.

parse.com: cloud code to return a PFFile

Background...
I'm exploring Parse.com as a back end for an iOS app that also has an html/web browser interface for some users (either via javascript client or asp.net client - to be determined). The web users are 'an audience' for the data/files the app users prepare in the app. They are not the same people.
I need to lock down access to objects in the database (no public access read or write) so I plan to set up an admin user belonging to an administrators role and create an app_users role applying class-level permissions to the various classes accordingly.
Then for my iOS app, using the anonymous users add them to the app_Users role, setting up a default ACL for object level permissions and interact with the data model accordingly.
The app creates PDF files and stores as PFFile objects and I want these to have no public read or write access too. these docs are what will be accessible via the web client.
So...
I don't think i want to use PFUsers for each potential user accessing via a web client -don't want it to over engineered. So I figured send params to Cloud Code (with useMasterKey()) to first return a list of file meta data to present to the user - this works well - I can return the PFFile url or objectId, doc name, file type and size...
The challenge...
Next I'd need to build a Cloud Code function which given objectId or a url will fetch the PDF file and return it in a way my web page can display it to the user.
I've seen a few examples in the Networking section of the docs looks like it might be possible but I can seem to join the dots.
Hope that makes sense - any thoughts?
Edit: Added Code
The code I've been looking at works for text/html - is it possible to response a PDF or binary
Parse.Cloud.httpRequest({
url:'example.com/file.pdf',
 success: function(httpResponse) {
console.log(httpResponse.text);
},
error: function(httpResponse) {
console.error('Request failed: ' + httpResponse.status);
});

Logout from access control service with custom STS

I'm using Windows azure access control service with custom STS. I can login to my application through ACS, but I have trouble with logout function. I've tried this code in my application.
WSFederationAuthenticationModule fam = FederatedAuthentication.WSFederationAuthenticationModule;
try
{
FormsAuthentication.SignOut();
}
finally
{
fam.SignOut(true);
}
Page.Response.Redirect("default.aspx");
But it seems that it logout the user from ACS but not from the custom STS. What should I do to logout from STS. Where could be the problem in the appliacation (RP), ACS or in STS?
I think that ACS should ask custom STS to logout the user, but it seems it doesnt do that. What I am missing?
I have created a helper method for doing FederatedSignout, with comments in the code for what I discovered along the way (hth)
public static void FederatedSignOut(string reply = null)
{
WSFederationAuthenticationModule fam = FederatedAuthentication.WSFederationAuthenticationModule;
// Native FederatedSignOut doesn't seem to have a way for finding/registering realm for singout, get it from the FAM
string wrealm = string.Format("wtrealm={0}", fam.Realm);
// Create basic url for signout (wreply is set by native FederatedSignOut)
string signOutUrl = WSFederationAuthenticationModule.GetFederationPassiveSignOutUrl(fam.Issuer, null, wrealm);
// Check where to return, if not set ACS will use Reply address configured for the RP
string wreply = !string.IsNullOrEmpty(reply) ? reply : (!string.IsNullOrEmpty(fam.Reply) ? fam.Reply : null);
WSFederationAuthenticationModule.FederatedSignOut(new Uri(signOutUrl), !string.IsNullOrEmpty(wreply) ? new Uri(wreply) : null);
// Remarks! Native FederatedSignout has an option for setting signOutUrl to null, even if the documentation tells otherwise.
// If set to null the method will search for signoutUrl in Session token, but I couldn't find any information about how to set this. Found some Sharepoint code that use this
// Michele Leroux Bustamante had a code example (from 2010) that also uses this form.
// Other examples creates the signout url manually and calls redirect.
// FAM has support for wsignoutcleanup1.0 right out of the box, there is no need for code to handle this.
// That makes it even harder to understand why there are no complete FederatedSignOut method in FAM
// When using native FederatedSignOut() no events for signout will be called, if you need this use the FAM SignOut methods instead.
}
This code is used in a standard RP library we created for Web SSO with ACS.
The December 2012 update of ACS includes support for federated single sign-out:
Using the WS-Federation protocol. Web applications that use ACS to
enable single sign-on (SSO) with identity providers using the
WS-Federation protocol can now take advantage of single sign out
capabilities. When a user signs out of a web application, ACS can
automatically sign the user out of the identity provider and out of
other relying party applications that use the same identity provider.
This feature is enable for WS-Federation identity providers, including
Active Directory Federation Services 2.0 and Windows Live ID
(Microsoft account). To enable single sign out, ACS performs the
following tasks for WS-Federation protocol endpoints:
ACS recognizes wsignoutcleanup1.0 messages from identity providers
and responds by sending wsignoutcleanup1.0 messages to relying party
applications.
ACS recognizes wsignout1.0 and wreply messages from relying party
applications and responds by sending wsignout1.0 messages to identity
providers and wsignoutcleanup1.0 messages to relying party
applications.
From the Code Sample: ASP.NET MVC 4 with Federated Sign-out, implement an Action like this to sign out from ACS:
(Note that Windows Identity Foundation is now incorporated into .NET 4.5 Framework, that's why the new namespaces below)
using System.IdentityModel.Services;
using System.IdentityModel.Services.Configuration;
public ActionResult Logout()
{
// Load Identity Configuration
FederationConfiguration config = FederatedAuthentication.FederationConfiguration;
// Get wtrealm from WsFederationConfiguation Section
string wtrealm = config.WsFederationConfiguration.Realm;
string wreply;
// Construct wreply value from wtrealm (This will be the return URL to your app)
if (wtrealm.Last().Equals('/'))
{
wreply = wtrealm + "Logout";
}
else
{
wreply = wtrealm + "/Logout";
}
// Read the ACS Ws-Federation endpoint from web.Config
// something like "https://<your-namespace>.accesscontrol.windows.net/v2/wsfederation"
string wsFederationEndpoint = ConfigurationManager.AppSettings["ida:Issuer"];
SignOutRequestMessage signoutRequestMessage = new SignOutRequestMessage(new Uri(wsFederationEndpoint));
signoutRequestMessage.Parameters.Add("wreply", wreply);
signoutRequestMessage.Parameters.Add("wtrealm", wtrealm);
FederatedAuthentication.SessionAuthenticationModule.SignOut();
string signoutUrl = signoutRequestMessage.WriteQueryString();
return this.Redirect(signoutUrl);
}

Resources