Skip to content

Commit

Permalink
Implement external authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
amphasis committed Dec 13, 2021
1 parent d68598a commit c419eb1
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 12 deletions.
8 changes: 3 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@ name: Deploy ASP.NET Core app to Azure Web App
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

env:
NUGET_USERNAME: amphasis
NUGET_USERNAME: amphasis
NUGET_SOURCE: https://nuget.pkg.github.com/amphasis/index.json
DOTNET_VERSION: '3.1.403'
DOTNET_VERSION: '6.0.100'
AZURE_WEBAPP_NAME: 'leff'
AZURE_WEBAPP_PACKAGE_PATH: '.'
AZURE_WEBAPP_PROJECT_PATH: 'Amphasis.Azure.WebPortal'
AZURE_WEBAPP_PROJECT_PATH: 'Amphasis.Azure.WebPortal'

jobs:
build-and-deploy:
Expand Down
13 changes: 8 additions & 5 deletions Amphasis.Azure.WebPortal/Amphasis.Azure.WebPortal.csproj
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<UserSecretsId>3ad90e84-123b-437a-ad29-dd08e1e3db34</UserSecretsId>
</PropertyGroup>


<ItemGroup>
<PackageReference Include="Amphasis.SimaLand" Version="1.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.9" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.4" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" />
<PackageReference Include="AspNet.Security.OAuth.MailRu" Version="6.0.1" />
<PackageReference Include="AspNet.Security.OAuth.Vkontakte" Version="6.0.1" />
<PackageReference Include="AspNet.Security.OAuth.Yandex" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions Amphasis.Azure.WebPortal/Models/CustomClaims.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Amphasis.Azure.WebPortal.Models
{
public static class CustomClaims
{
public static string UserImageUrl => "urn:amphasis:user:image";
}
}
17 changes: 17 additions & 0 deletions Amphasis.Azure.WebPortal/Pages/Profile.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@page
@using System.Security.Claims
@using Amphasis.Azure.WebPortal.Models
@model Amphasis.Azure.WebPortal.Pages.ProfileModel
@{
ViewData["Title"] = "User Profile";
var name = HttpContext.User.Identity.Name;
var email = HttpContext.User.FindFirst(ClaimTypes.Email)?.Value;
var nameIdentifier = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var userImageUrl = HttpContext.User.FindFirst(CustomClaims.UserImageUrl)?.Value;
}
<h1>@ViewData["Title"]</h1>
<img src="@userImageUrl" />
<p><span>Name:</span> @name</p>
<p><span>E-Mail:</span> @email</p>
<p><span>Name Identifier:</span> @nameIdentifier</p>
<form method="post"><button>Sign Out</button></form>
18 changes: 18 additions & 0 deletions Amphasis.Azure.WebPortal/Pages/Profile.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Amphasis.Azure.WebPortal.Pages
{
[Authorize]
public class ProfileModel : PageModel
{
public void OnGet()
{
}
}
}
16 changes: 14 additions & 2 deletions Amphasis.Azure.WebPortal/Pages/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title>@ViewData["Title"] - Amphasis Web Portal</title>

<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css"/>
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
Expand All @@ -27,6 +27,18 @@
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
@if (Context.User.Identity.IsAuthenticated)
{
<a class="nav-link text-dark" asp-area="" asp-page="/Profile">@Context.User.Identity.Name</a>
}
else
{
<a class="nav-link text-dark" asp-area="" asp-page="/SignIn" asp-route-returnUrl="@[email protected]@Context.Request.QueryString">Sign in</a>
}
</li>
</ul>
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
Expand All @@ -48,7 +60,7 @@

<footer class="border-top footer text-muted">
<div class="container">
&copy; 2019 - AzureTestApp - <a asp-area="" asp-page="/Privacy">Privacy</a>
&copy; 2020 - Leff - <a asp-area="" asp-page="/Privacy">Privacy</a>
</div>
</footer>

Expand Down
21 changes: 21 additions & 0 deletions Amphasis.Azure.WebPortal/Pages/SignIn.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@page
@model Amphasis.Azure.WebPortal.Pages.SignInModel
@{
ViewData["Title"] = "Sign in";
}
<div class="text-center">
<h1 class="display-4">@ViewData["Title"]</h1>
<p class="lead">using one of these external providers:</p>
<div class="d-flex justify-content-center">
@foreach (var scheme in Model.AuthenticationSchemes)
{
<form method="post">
<input type="hidden" asp-for="Provider" value="@scheme.Name" />
<input type="hidden" asp-for="ReturnUrl" />
<button class="btn zoom" type="submit">
<img width="144" height="144" src="@scheme.IconUrl" alt="@scheme.DisplayName" title="@scheme.DisplayName" class="rounded" />
</button>
</form>
}
</div>
</div>
80 changes: 80 additions & 0 deletions Amphasis.Azure.WebPortal/Pages/SignIn.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;

namespace Amphasis.Azure.WebPortal.Pages
{
public class SignInModel : PageModel
{
public class AuthenticationSchemeInfo
{
public string Name { get; set; }
public string DisplayName { get; set; }
public string IconUrl { get; set; }
}

private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
private readonly IFileProvider _fileProvider;

public SignInModel(
IAuthenticationSchemeProvider authenticationSchemeProvider,
IOptions<StaticFileOptions> staticFileOptions,
IWebHostEnvironment webHostEnvironment)
{
_authenticationSchemeProvider = authenticationSchemeProvider;
_fileProvider = staticFileOptions.Value?.FileProvider ?? webHostEnvironment.WebRootFileProvider;
}

public IEnumerable<AuthenticationSchemeInfo> AuthenticationSchemes { get; set; }

[FromQuery]
[FromForm]
public string ReturnUrl { get; set; }

[FromForm]
public string Provider { get; set; }

public async Task<ActionResult> OnGet()
{
if (User.Identity.IsAuthenticated) return Redirect(ReturnUrl ?? Url.Page("Index"));

var schemesEnumerable = await _authenticationSchemeProvider.GetAllSchemesAsync();

AuthenticationSchemes = schemesEnumerable
.Where(scheme => !string.IsNullOrEmpty(scheme.DisplayName))
.Select(scheme => new AuthenticationSchemeInfo
{
Name = scheme.Name,
DisplayName = scheme.DisplayName,
IconUrl = GetProviderIconUrl(scheme.Name)
});

return Page();
}

public async Task<ActionResult> OnPost()
{
var schemesEnumerable = await _authenticationSchemeProvider.GetAllSchemesAsync();
var comparer = StringComparer.InvariantCultureIgnoreCase;
var isSupportedProvider = schemesEnumerable.Select(scheme => scheme.Name).Contains(Provider, comparer);
if (!isSupportedProvider) return BadRequest();
var properties = new AuthenticationProperties {RedirectUri = ReturnUrl ?? "/"};
return Challenge(properties, Provider);
}

private string GetProviderIconUrl(string providerSchemeName)
{
string iconPath = $"/external/{providerSchemeName}.png";
var iconFileInfo = _fileProvider.GetFileInfo(iconPath);
return iconFileInfo.Exists ? iconPath : "/external/Default.png";
}
}
}
2 changes: 2 additions & 0 deletions Amphasis.Azure.WebPortal/Pages/SimaLandGoods.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
using System.Threading.Tasks;
using Amphasis.Azure.WebPortal.SimaLand.Services;
using Amphasis.SimaLand.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Amphasis.Azure.WebPortal.Pages
{
[Authorize]
[ResponseCache(Duration = 3600, VaryByQueryKeys = new[] {"pageIndex"})]
public class SimaLandGoodsModel : PageModel
{
Expand Down
72 changes: 72 additions & 0 deletions Amphasis.Azure.WebPortal/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading.Tasks;
using Amphasis.Azure.WebPortal.Models;
using Amphasis.Azure.WebPortal.SimaLand.Models;
using Amphasis.Azure.WebPortal.SimaLand.Services;
using Amphasis.Azure.WebPortal.Yandex.Models;
using Amphasis.SimaLand;
using AspNet.Security.OAuth.MailRu;
using AspNet.Security.OAuth.Vkontakte;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;

namespace Amphasis.Azure.WebPortal
{
Expand Down Expand Up @@ -41,6 +51,64 @@ public void ConfigureServices(IServiceCollection services)
options.MinimumSameSitePolicy = SameSiteMode.None;
});

services
.AddAuthentication(options => options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/SignIn";
options.LogoutPath = "/SignOut";
})
.AddMailRu(options =>
{
_configuration.GetSection("MailRu").Bind(options);

options.Events.OnCreatingTicket += context =>
{
var identity = context.Identity;
var originalImageClaim = identity?.FindFirst(MailRuAuthenticationConstants.Claims.ImageUrl);
if (originalImageClaim == null) return Task.CompletedTask;
identity.RemoveClaim(originalImageClaim);
var newImageClaim = new Claim(CustomClaims.UserImageUrl, originalImageClaim.Value, originalImageClaim.ValueType);
identity.AddClaim(newImageClaim);
return Task.CompletedTask;
};
})
.AddVkontakte(options =>
{
_configuration.GetSection("VK").Bind(options);

options.Events.OnCreatingTicket += context =>
{
var identity = context.Identity;
var originalImageClaim = identity?.FindFirst(VkontakteAuthenticationConstants.Claims.PhotoUrl);
if (originalImageClaim == null) return Task.CompletedTask;
identity.RemoveClaim(originalImageClaim);
var newImageClaim = new Claim(CustomClaims.UserImageUrl, originalImageClaim.Value, originalImageClaim.ValueType);
identity.AddClaim(newImageClaim);
return Task.CompletedTask;
};
})
.AddYandex(options =>
{
_configuration.GetSection("Yandex").Bind(options);

options.Events.OnCreatingTicket += async context =>
{
if (context.Identity == null) return;
var uri = new Uri("https://login.yandex.ru/info");
var authorization = new AuthenticationHeaderValue("OAuth", context.AccessToken);
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = authorization;
using var httpResponseMessage = await httpClient.GetAsync(uri);
httpResponseMessage.EnsureSuccessStatusCode();
var contentString = await httpResponseMessage.Content.ReadAsStringAsync();
var userInfo = JsonConvert.DeserializeObject<YandexUserInfo>(contentString);
var imageUrl = $"https://avatars.yandex.net/get-yapic/{userInfo.DefaultAvatarId}/islands-200";
var imageClaim = new Claim(CustomClaims.UserImageUrl, imageUrl, ClaimValueTypes.String);
context.Identity.AddClaim(imageClaim);
};
});

var razorPagesBuilder = services.AddRazorPages();
services.AddControllers();

Expand Down Expand Up @@ -69,6 +137,10 @@ public void Configure(IApplicationBuilder applicationBuilder, IWebHostEnvironmen
applicationBuilder.UseCookiePolicy();

applicationBuilder.UseRouting();

applicationBuilder.UseAuthentication();
applicationBuilder.UseAuthorization();

applicationBuilder.UseEndpoints(builder =>
{
builder.MapRazorPages();
Expand Down
47 changes: 47 additions & 0 deletions Amphasis.Azure.WebPortal/Yandex/Models/YandexUserInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Amphasis.Azure.WebPortal.Yandex.Models
{
public class YandexUserInfo
{
[JsonProperty("id")]
public string Id { get; set; }

[JsonProperty("login")]
public string Login { get; set; }

[JsonProperty("client_id")]
public string ClientId { get; set; }

[JsonProperty("display_name")]
public string DisplayName { get; set; }

[JsonProperty("real_name")]
public string RealName { get; set; }

[JsonProperty("first_name")]
public string FirstName { get; set; }

[JsonProperty("last_name")]
public string LastName { get; set; }

[JsonProperty("sex")]
public string Sex { get; set; }

[JsonProperty("default_email")]
public string DefaultEmail { get; set; }

[JsonProperty("emails")]
public IList<string> Emails { get; set; }

[JsonProperty("birthday")]
public string Birthday { get; set; }

[JsonProperty("default_avatar_id")]
public string DefaultAvatarId { get; set; }

[JsonProperty("is_avatar_empty")]
public bool IsAvatarEmpty { get; set; }
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed Amphasis.Azure.WebPortal/wwwroot/mask.png
Binary file not shown.

0 comments on commit c419eb1

Please sign in to comment.