Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/SampleMauiApplication/MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebView.Maui;
using SampleMauiApplication.Data;
using SampleMauiApplication.Services;

namespace SampleMauiApplication;

public static class MauiProgram
{
public static MauiApp CreateMauiApp()
public static string APIUrl = DeviceInfo.Platform == DevicePlatform.Android ? "http://10.0.2.2:5096/api/" : "https://localhost:44334/api/";
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.RegisterBlazorMauiWebView()
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});

//Register needed elements for authentication
builder.Services.AddAuthorizationCore(); // This is the core functionality
builder.Services.AddMauiBlazorWebView();
//Register needed elements for authentication
builder.Services.AddAuthorizationCore(); // This is the core functionality
builder.Services.AddScoped<CustomAuthenticationStateProvider>(); // This is our custom provider
//When asking for the default Microsoft one, give ours!
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthenticationStateProvider>());

builder.Services.AddBlazorWebView();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddScoped(_ => new AuthenticatingService(APIUrl));

return builder.Build();
return builder.Build();
}
}
11 changes: 5 additions & 6 deletions src/SampleMauiApplication/Models/LoginViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,17 @@ public class LoginViewModel

public bool LoginFailureHidden { get; set; } = true;

public bool ValidateLogin(out string jwtToken)
public async Task<string> ValidateLoginAsync()
{
if(Username.Equals("Test") && Password.Equals("Test"))

if (Username.Equals("Test") && Password.Equals("Test"))
{
jwtToken = "123456";
return true;
return "123456";
}

//Not valid
jwtToken = null;
LoginFailureHidden = false;
return false;
return null;
}
}
}
60 changes: 34 additions & 26 deletions src/SampleMauiApplication/Pages/Login.razor
Original file line number Diff line number Diff line change
@@ -1,43 +1,51 @@
@page "/login"
@using SampleMauiApplication.Models
@using SampleMauiApplication.Services
@inject NavigationManager Navigation;
@inject CustomAuthenticationStateProvider AuthStateProvider;
@inject AuthenticatingService authService;

<h3>Login to Access Application</h3>
<h1 style="text-align:center">Log in</h1>
<div class="container">
<section>
<hr />
<p>
<button class="btn btn-primary login-facebook" @onclick=LoginFacebook>Facebook Login</button>
<button class="btn btn-primary login-google" @onclick=LoginGoogle>Google Login</button>
<button class="btn btn-primary login-apple" @onclick=LoginApple>Apple Login</button>
<button class="btn btn-primary login-email" @onclick=LoginEmail>Email Login</button>

<div class="alert alert-info">
This is a dummy login page, providing `Test` for the Username and Password will authenticate you.
</p>
</section>
</div>

<EditForm Model="@loginModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="alert alert-danger" hidden="@loginModel.LoginFailureHidden">
Invalid login attempt, please correct and try again.
</div>
<div class="form-group">
<label>Username</label>
<InputText id="email" @bind-Value="loginModel.Username" class="form-control" />
</div>
<div class="form-group">
<label>Password</label>
<InputText id="password" type="password" @bind-Value="loginModel.Password" class="form-control" />
</div>
<div class="form-group mt-1">
<button type="submit" class="btn btn-primary w-100">Login Now</button>
</div>
</EditForm>

@code {
private LoginViewModel loginModel = new();
//public string ErrorMessage { get; set; }

private async Task LoginFacebook()
{
await DoLogin("Facebook");
}
private async Task LoginGoogle()
{
await DoLogin("Google");
}
private async Task LoginApple()
{
await DoLogin("Apple");
}
private void LoginEmail()
{
Navigation.NavigateTo("/LoginEmail");
}

private async Task HandleValidSubmit()
private async Task DoLogin(string scheme)
{
//Valiate user acount
var successful = loginModel.ValidateLogin(out string jwtToken);
var jwtToken = await authService.OnAuthenticate(scheme);

//Not successful, don't need to do anything
if (!successful)
if (String.IsNullOrEmpty(jwtToken))
return;

//Call login and recirect
Expand Down
47 changes: 47 additions & 0 deletions src/SampleMauiApplication/Pages/LoginEmail.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@page "/loginemail"
@using SampleMauiApplication.Models
@inject NavigationManager Navigation;
@inject CustomAuthenticationStateProvider AuthStateProvider;

<h3>Login to Access Application</h3>

<div class="alert alert-info">
This is a dummy login page, providing `Test` for the Username and Password will authenticate you.
</div>

<EditForm Model="@loginModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="alert alert-danger" hidden="@loginModel.LoginFailureHidden">
Invalid login attempt, please correct and try again.
</div>
<div class="form-group">
<label>Username</label>
<InputText id="email" @bind-Value="loginModel.Username" class="form-control" />
</div>
<div class="form-group">
<label>Password</label>
<InputText id="password" type="password" @bind-Value="loginModel.Password" class="form-control" />
</div>
<div class="form-group mt-1">
<button type="submit" class="btn btn-primary w-100">Login Now</button>
</div>
</EditForm>

@code {
private LoginViewModel loginModel = new();

private async Task HandleValidSubmit()
{
//Valiate user acount
var jwtToken = await loginModel.ValidateLoginAsync();

//Not successful, don't need to do anything
if (String.IsNullOrEmpty(jwtToken))
return;

//Call login and recirect
await AuthStateProvider.Login(jwtToken);
Navigation.NavigateTo(""); //Root URL
}
}
15 changes: 15 additions & 0 deletions src/SampleMauiApplication/Platforms/Android/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,19 @@
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<queries>
<!-- For opening external websites required for social logins -->
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<!-- MediaPicker -->
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<!-- Maps -->
<intent>
<action android:name="android.intent.action.VIEW" />
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed for social login? (Similar question to the Image Capture intent.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah nope, just the CustomTabsService needed, my bad.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now worries, just want to try and keep this to "MVP" status, especially for permission/intent asks

<data android:scheme="geo" />
</intent>
</queries>
</manifest>
11 changes: 10 additions & 1 deletion src/SampleMauiApplication/Platforms/Android/MainActivity.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Content;

namespace SampleMauiApplication;

[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
public class MainActivity : MauiAppCompatActivity
{

[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]
[IntentFilter(
new[] { Intent.ActionView },
Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataScheme = "myapp")]
public class WebAuthenticationCallbackActivity : Microsoft.Maui.Authentication.WebAuthenticatorCallbackActivity
{
}
}
12 changes: 6 additions & 6 deletions src/SampleMauiApplication/SampleMauiApplication.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
Expand Down Expand Up @@ -52,8 +52,8 @@

<ItemGroup Condition="$(TargetFramework.Contains('-windows'))">
<!-- Required - WinUI does not yet have buildTransitive for everything -->
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.0.0" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.0.0.30" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.0.3" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.0.3.1" />
</ItemGroup>

<PropertyGroup Condition="$(TargetFramework.Contains('-windows'))">
Expand All @@ -66,9 +66,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.AUthorization" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.AUthorization" Version="6.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.5" />
</ItemGroup>

</Project>
73 changes: 73 additions & 0 deletions src/SampleMauiApplication/Services/AuthenticatingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Maui;
using Microsoft.Maui.Authentication;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Devices;

namespace SampleMauiApplication.Services
{
//From this example: https://docs.microsoft.com/en-us/dotnet/maui/platform-integration/communication/authentication?tabs=android
public class AuthenticatingService
{
public string authenticationUrl { get; set; }
public AuthenticatingService(string APIUrl)
{
//points to an api controller like this one: https://github.com/dotnet/maui/blob/main/src/Essentials/samples/Sample.Server.WebAuthenticator/Controllers/MobileAuthController.cs
authenticationUrl = APIUrl + "mobileauth/";
}

public async Task<string> OnAuthenticate(string scheme)
{
try
{
WebAuthenticatorResult r = null;

if (scheme.Equals("Apple", StringComparison.Ordinal)
&& DeviceInfo.Platform == DevicePlatform.iOS
&& DeviceInfo.Version.Major >= 13)
{
// Make sure to enable Apple Sign In in both the
// entitlements and the provisioning profile.
var options = new AppleSignInAuthenticator.Options
{
IncludeEmailScope = true,
IncludeFullNameScope = true,
};
r = await AppleSignInAuthenticator.AuthenticateAsync(options);
}
else
{
var authUrl = new Uri(authenticationUrl + scheme);
var callbackUrl = new Uri("myapp://");

r = await WebAuthenticator.AuthenticateAsync(authUrl, callbackUrl);
}

var authToken = string.Empty;
if (r.Properties.TryGetValue("name", out var name) && !string.IsNullOrEmpty(name))
authToken += $"Name: {name}{Environment.NewLine}";
if (r.Properties.TryGetValue("email", out var email) && !string.IsNullOrEmpty(email))
authToken += $"Email: {email}{Environment.NewLine}";
authToken += r?.AccessToken ?? r?.IdToken;

return authToken;
}
catch (OperationCanceledException)
{
Console.WriteLine("Login canceled.");
return string.Empty;
//ToDo - handle fail
//await DisplayAlertAsync("Login canceled.");
}
catch (Exception ex)
{
Console.WriteLine($"Failed: {ex.Message}");
return string.Empty;
//ToDo - handle fail
//await DisplayAlertAsync($"Failed: {ex.Message}");
}
}
}
}