From 0b36d9942a9c6cfb2cf7203a49cdca84d70abc2d Mon Sep 17 00:00:00 2001
From: Tiago Brenck
Date: Tue, 5 Nov 2019 16:45:29 -0800
Subject: [PATCH 01/32] Onboarding process for multi-tenant
---
.../1-2-AnyOrg/Controllers/HomeController.cs | 26 ++++-
.../Controllers/OnboardingController.cs | 102 ++++++++++++++++++
.../1-2-AnyOrg/DAL/SampleDbContext.cs | 16 +++
.../1-2-AnyOrg/Models/AuthorizedTenant.cs | 20 ++++
1-WebApp-OIDC/1-2-AnyOrg/Startup.cs | 45 ++++++++
.../Utils/UnauthorizedTenantException.cs | 13 +++
.../1-2-AnyOrg/Views/Home/Index.cshtml | 31 +++++-
.../Views/Home/UnauthorizedTenant.cshtml | 10 ++
.../1-2-AnyOrg/Views/Onboarding/SignUp.cshtml | 22 ++++
.../1-2-AnyOrg/Views/Shared/Error.cshtml | 12 +--
.../WebApp-OpenIDConnect-DotNet.csproj | 2 +
11 files changed, 287 insertions(+), 12 deletions(-)
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/Controllers/OnboardingController.cs
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/DAL/SampleDbContext.cs
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/Models/AuthorizedTenant.cs
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/Utils/UnauthorizedTenantException.cs
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/Views/Home/UnauthorizedTenant.cshtml
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/Views/Onboarding/SignUp.cshtml
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Controllers/HomeController.cs b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/HomeController.cs
index 15509246..cbf05823 100644
--- a/1-WebApp-OIDC/1-2-AnyOrg/Controllers/HomeController.cs
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/HomeController.cs
@@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
+using System.Linq;
+using WebApp_OpenIDConnect_DotNet.DAL;
using WebApp_OpenIDConnect_DotNet.Models;
namespace WebApp_OpenIDConnect_DotNet.Controllers
@@ -8,11 +10,31 @@ namespace WebApp_OpenIDConnect_DotNet.Controllers
[Authorize]
public class HomeController : Controller
{
- public HomeController()
+ private readonly SampleDbContext dbContext;
+
+ public HomeController(SampleDbContext dbContext)
{
+ this.dbContext = dbContext;
}
public IActionResult Index()
+ {
+ var authorizedTenants = dbContext.AuthorizedTenants.Where(x => x.TenantId != null && x.AuthorizedOn != null).ToList();
+ return View(authorizedTenants);
+ }
+
+ public IActionResult DeleteTenant(string id)
+ {
+ var tenants = dbContext.AuthorizedTenants.Where(x => x.TenantId == id).ToList();
+ dbContext.RemoveRange(tenants);
+ dbContext.SaveChanges();
+
+ return RedirectToAction("Index");
+ }
+
+ [AllowAnonymous]
+ [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
+ public IActionResult UnauthorizedTenant()
{
return View();
}
@@ -21,7 +43,7 @@ public IActionResult Index()
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
- return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier});
+ return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
\ No newline at end of file
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Controllers/OnboardingController.cs b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/OnboardingController.cs
new file mode 100644
index 00000000..1870c052
--- /dev/null
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/OnboardingController.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication.AzureAD.UI;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using WebApp_OpenIDConnect_DotNet.DAL;
+using WebApp_OpenIDConnect_DotNet.Models;
+
+namespace WebApp_OpenIDConnect_DotNet.Controllers
+{
+ [AllowAnonymous]
+ public class OnboardingController : Controller
+ {
+ private readonly SampleDbContext dbContext;
+ private readonly AzureADOptions azureADOptions;
+
+ public OnboardingController(SampleDbContext dbContext, IOptions azureADOptions)
+ {
+ this.dbContext = dbContext;
+ this.azureADOptions = azureADOptions.Value;
+ }
+
+ [HttpGet]
+ public ActionResult SignUp()
+ {
+ return View();
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public ActionResult Onboard()
+ {
+ // Generate a random value to identify the request
+ string stateMarker = Guid.NewGuid().ToString();
+
+ AuthorizedTenant authorizedTenant = new AuthorizedTenant
+ {
+ CreatedOn = DateTime.Now,
+ TempAuthorizationCode = stateMarker //Use the stateMarker as a tempCode, so we can find this entity on the ProcessCode method
+ };
+
+ dbContext.AuthorizedTenants.Add(authorizedTenant);
+ dbContext.SaveChanges();
+
+ string currentUri = UriHelper.BuildAbsolute(
+ this.Request.Scheme,
+ this.Request.Host,
+ this.Request.PathBase);
+
+ // Create an OAuth2 request, using the web app as the client.This will trigger a consent flow that will provision the app in the target tenant.
+ string authorizationRequest = string.Format(
+ "{0}common/adminconsent?client_id={1}&redirect_uri={2}&state={3}",
+ azureADOptions.Instance,
+ Uri.EscapeDataString(azureADOptions.ClientId),
+ Uri.EscapeDataString(currentUri + "Onboarding/ProcessCode"),
+ Uri.EscapeDataString(stateMarker));
+
+
+ return new RedirectResult(authorizationRequest);
+ }
+
+ // This is the redirect Uri for the admin consent authorization
+ public async Task ProcessCode(string tenant, string error, string error_description, string resource, string state)
+ {
+ if (error != null)
+ {
+ TempData["ErrorMessage"] = error_description;
+ return RedirectToAction("Error", "Home");
+ }
+
+ // Check if tenant is already authorized
+ if(dbContext.AuthorizedTenants.FirstOrDefault(x => x.TenantId == tenant) != null)
+ {
+ return RedirectToAction("Index", "Home");
+ }
+
+ // Find a tenant carrying a TempAuthorizationCode that we previously saved
+ var preAuthorizedTenant = dbContext.AuthorizedTenants.FirstOrDefault(a => a.TempAuthorizationCode == state);
+
+ // If we don't find it, return an error because the state param was not generated from this app
+ if (preAuthorizedTenant == null)
+ {
+ TempData["ErrorMessage"] = "State verification failed.";
+ return RedirectToAction("Error", "Home");
+ }
+ else
+ {
+ // Update the authorized tenant with its Id
+ preAuthorizedTenant.TenantId = tenant;
+ preAuthorizedTenant.AuthorizedOn = DateTime.Now;
+
+ await dbContext.SaveChangesAsync();
+
+ return RedirectToAction("Index", "Home");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/DAL/SampleDbContext.cs b/1-WebApp-OIDC/1-2-AnyOrg/DAL/SampleDbContext.cs
new file mode 100644
index 00000000..73b6dd88
--- /dev/null
+++ b/1-WebApp-OIDC/1-2-AnyOrg/DAL/SampleDbContext.cs
@@ -0,0 +1,16 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using WebApp_OpenIDConnect_DotNet.Models;
+
+namespace WebApp_OpenIDConnect_DotNet.DAL
+{
+ public class SampleDbContext : DbContext
+ {
+ public SampleDbContext(DbContextOptions options) : base(options) { }
+
+ public DbSet AuthorizedTenants { get; set; }
+ }
+}
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Models/AuthorizedTenant.cs b/1-WebApp-OIDC/1-2-AnyOrg/Models/AuthorizedTenant.cs
new file mode 100644
index 00000000..f166703e
--- /dev/null
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Models/AuthorizedTenant.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace WebApp_OpenIDConnect_DotNet.Models
+{
+ public class AuthorizedTenant
+ {
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int Id { get; set; }
+ public string TempAuthorizationCode { get; set; }
+ public string TenantId { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public DateTime? AuthorizedOn { get; set; }
+ }
+}
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Startup.cs b/1-WebApp-OIDC/1-2-AnyOrg/Startup.cs
index 43f1d721..08a18c51 100644
--- a/1-WebApp-OIDC/1-2-AnyOrg/Startup.cs
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Startup.cs
@@ -1,13 +1,24 @@
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
+using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Web;
+using Microsoft.Identity.Web.Resource;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using WebApp_OpenIDConnect_DotNet.DAL;
+using WebApp_OpenIDConnect_DotNet.Utils;
namespace WebApp_OpenIDConnect_DotNet
{
@@ -30,8 +41,42 @@ public void ConfigureServices(IServiceCollection services)
options.MinimumSameSitePolicy = SameSiteMode.None;
});
+ services.AddOptions();
+
+ services.AddDbContext(options => options.UseInMemoryDatabase(databaseName: "MultiTenantOnboarding"));
+
// Sign-in users with the Microsoft identity platform
services.AddMicrosoftIdentityPlatformAuthentication(Configuration);
+
+ services.Configure(AzureADDefaults.OpenIdScheme, options =>
+ {
+ //options.TokenValidationParameters = BuildTokenValidationParameters(options);
+ options.Events.OnTokenValidated = async context =>
+ {
+ string tenantId = context.SecurityToken.Claims.FirstOrDefault(x => x.Type == "tid" || x.Type == "http://schemas.microsoft.com/identity/claims/tenantid")?.Value;
+
+ if (string.IsNullOrWhiteSpace(tenantId))
+ throw new UnauthorizedAccessException("Unable to get tenantId from token.");
+
+ var dbContext = context.HttpContext.RequestServices.GetRequiredService();
+
+ var authorizedTenant = await dbContext.AuthorizedTenants.FirstOrDefaultAsync(t => t.TenantId == tenantId);
+
+ if (authorizedTenant == null)
+ throw new UnauthorizedTenantException("This tenant is not authorized");
+
+ };
+ options.Events.OnAuthenticationFailed = (context) =>
+ {
+ if (context.Exception != null && context.Exception is UnauthorizedTenantException)
+ {
+ context.Response.Redirect("/Home/UnauthorizedTenant");
+ context.HandleResponse(); // Suppress the exception
+ }
+
+ return Task.FromResult(0);
+ };
+ });
services.AddMvc(options =>
{
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Utils/UnauthorizedTenantException.cs b/1-WebApp-OIDC/1-2-AnyOrg/Utils/UnauthorizedTenantException.cs
new file mode 100644
index 00000000..b3e161d6
--- /dev/null
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Utils/UnauthorizedTenantException.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace WebApp_OpenIDConnect_DotNet.Utils
+{
+ public class UnauthorizedTenantException : UnauthorizedAccessException
+ {
+ public UnauthorizedTenantException():base() { }
+ public UnauthorizedTenantException(string message):base(message) { }
+ }
+}
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Views/Home/Index.cshtml b/1-WebApp-OIDC/1-2-AnyOrg/Views/Home/Index.cshtml
index 65f4be16..a26b3dbc 100644
--- a/1-WebApp-OIDC/1-2-AnyOrg/Views/Home/Index.cshtml
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Views/Home/Index.cshtml
@@ -1,4 +1,5 @@
-@{
+@model IEnumerable
+@{
ViewData["Title"] = "Home Page";
}
@@ -6,6 +7,30 @@
ASP.NET Core web app signing-in users in any Azure AD organization
- This sample shows how to build a .NET Core 2.2 MVC Web app that uses OpenID Connect to sign in users. Users can use work and school accounts from any company or organization that has integrated with Azure Active Directory. It leverages the ASP.NET Core OpenID Connect middleware.
+ This sample shows how to build a .NET Core MVC Web app that uses OpenID Connect to sign in users. Users can use work and school accounts from any company or organization that has integrated with Azure Active Directory. It leverages the ASP.NET Core OpenID Connect middleware.
-
\ No newline at end of file
+
+
+
+
Authorized Tenants
+
+ These are the authorized tenants. If you want to repeat the onboarding process, deleted the desired tenants and sign-out.
+
You have signed-in with a user from a Tenant that haven't been authorized by this application yet.
+
The authorization can be done via the onboarding proccess.
+Take me to the onboarding process
+
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Views/Onboarding/SignUp.cshtml b/1-WebApp-OIDC/1-2-AnyOrg/Views/Onboarding/SignUp.cshtml
new file mode 100644
index 00000000..0539b2f2
--- /dev/null
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Views/Onboarding/SignUp.cshtml
@@ -0,0 +1,22 @@
+
+@{
+ ViewData["Title"] = "SignUp";
+}
+
+
Tenant Onboarding
+
+
Instructions
+
+
In this multi-tenant sample, only tenants that are registered on the database will have their tokens validated.
+
This step represents an onboarding process, where we will provide this application's service principle into the new client tenant, using the admin consent endpoint. Then, we save the tenantId on the database to accept tokens issued by them.
+
Click on the button below to prompt the admin consent screen and register your tenant. You must use an admin account on this step.
- Swapping to Development environment will display more detailed information about the error that occurred.
-
-
- Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application.
-
+@if (TempData["ErrorMessage"] != null)
+{
+
@TempData["ErrorMessage"]
+}
+
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/WebApp-OpenIDConnect-DotNet.csproj b/1-WebApp-OIDC/1-2-AnyOrg/WebApp-OpenIDConnect-DotNet.csproj
index e7713b09..19fc641d 100644
--- a/1-WebApp-OIDC/1-2-AnyOrg/WebApp-OpenIDConnect-DotNet.csproj
+++ b/1-WebApp-OIDC/1-2-AnyOrg/WebApp-OpenIDConnect-DotNet.csproj
@@ -4,6 +4,7 @@
netcoreapp2.2aspnet-WebApp_OpenIDConnect_DotNet-81EA87AD-E64D-4755-A1CC-5EA47F49B5D80
+ WebApp_OpenIDConnect_DotNet
@@ -20,6 +21,7 @@
+
From e065eea7c415e10c27e1042b5c15e223f4a9d319 Mon Sep 17 00:00:00 2001
From: Tiago Brenck
Date: Wed, 6 Nov 2019 17:55:39 -0800
Subject: [PATCH 02/32] Create List and Delete TodoItem
---
.../1-2-AnyOrg/BLL/ITodoItemService.cs | 17 ++++
.../1-2-AnyOrg/BLL/TodoItemService.cs | 71 +++++++++++++++++
.../1-2-AnyOrg/Controllers/HomeController.cs | 8 +-
.../Controllers/OnboardingController.cs | 8 +-
.../Controllers/TodoListController.cs | 57 ++++++++++++++
.../1-2-AnyOrg/DAL/SampleDbContext.cs | 1 +
1-WebApp-OIDC/1-2-AnyOrg/Models/TodoItem.cs | 20 +++++
1-WebApp-OIDC/1-2-AnyOrg/Startup.cs | 16 +++-
.../1-2-AnyOrg/Views/Home/Index.cshtml | 31 +++++---
.../1-2-AnyOrg/Views/Shared/_Layout.cshtml | 1 +
.../Views/Shared/_LoginPartial.cshtml | 3 +-
.../1-2-AnyOrg/Views/TodoList/Create.cshtml | 42 ++++++++++
.../1-2-AnyOrg/Views/TodoList/Index.cshtml | 78 +++++++++++++++++++
13 files changed, 334 insertions(+), 19 deletions(-)
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/BLL/ITodoItemService.cs
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/BLL/TodoItemService.cs
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/Controllers/TodoListController.cs
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/Models/TodoItem.cs
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/Views/TodoList/Create.cshtml
create mode 100644 1-WebApp-OIDC/1-2-AnyOrg/Views/TodoList/Index.cshtml
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/BLL/ITodoItemService.cs b/1-WebApp-OIDC/1-2-AnyOrg/BLL/ITodoItemService.cs
new file mode 100644
index 00000000..10cff46f
--- /dev/null
+++ b/1-WebApp-OIDC/1-2-AnyOrg/BLL/ITodoItemService.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using WebApp_OpenIDConnect_DotNet.Models;
+
+namespace WebApp_OpenIDConnect_DotNet.BLL
+{
+ public interface ITodoItemService
+ {
+ Task Create(string text, ClaimsPrincipal loggedUser);
+ Task> List(bool showAll, ClaimsPrincipal loggedUser);
+ Task Get(int id, ClaimsPrincipal loggedUser);
+ Task Delete(int id, ClaimsPrincipal loggedUser);
+ }
+}
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/BLL/TodoItemService.cs b/1-WebApp-OIDC/1-2-AnyOrg/BLL/TodoItemService.cs
new file mode 100644
index 00000000..4fdc184c
--- /dev/null
+++ b/1-WebApp-OIDC/1-2-AnyOrg/BLL/TodoItemService.cs
@@ -0,0 +1,71 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Identity.Web;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using WebApp_OpenIDConnect_DotNet.DAL;
+using WebApp_OpenIDConnect_DotNet.Models;
+
+namespace WebApp_OpenIDConnect_DotNet.BLL
+{
+ public class TodoItemService : ITodoItemService
+ {
+ private readonly SampleDbContext sampleDbContext;
+ public TodoItemService(SampleDbContext sampleDbContext)
+ {
+ this.sampleDbContext = sampleDbContext;
+ }
+
+ public async Task Create(string text, ClaimsPrincipal loggedUser)
+ {
+ TodoItem todoItem = new TodoItem
+ {
+ Text = text,
+ UserName = loggedUser.Identity.Name,
+ AssignedTo = loggedUser.GetObjectId(),
+ TenantId = loggedUser.GetTenantId()
+ };
+
+ sampleDbContext.TodoItems.Add(todoItem);
+ await sampleDbContext.SaveChangesAsync();
+
+ return todoItem;
+ }
+
+ public async Task Delete(int id, ClaimsPrincipal loggedUser)
+ {
+ var todoItem = await Get(id, loggedUser);
+ if (todoItem != null)
+ {
+ sampleDbContext.TodoItems.Remove(todoItem);
+ await sampleDbContext.SaveChangesAsync();
+ }
+ }
+
+ public async Task Get(int id, ClaimsPrincipal loggedUser)
+ {
+ var tenantId = loggedUser.GetTenantId();
+ var userIdentifier = loggedUser.GetObjectId();
+
+ return await sampleDbContext.TodoItems.SingleOrDefaultAsync(x =>
+ x.Id == id
+ && x.TenantId == tenantId
+ && x.AssignedTo == userIdentifier);
+ }
+
+ public async Task> List(bool showAll, ClaimsPrincipal loggedUser)
+ {
+ var tenantId = loggedUser.GetTenantId();
+ var userIdentifier = loggedUser.GetObjectId();
+
+ var filteredResult = sampleDbContext.TodoItems.Where(x => x.TenantId == tenantId);
+
+ if (showAll)
+ return await filteredResult.ToListAsync();
+ else
+ return await filteredResult.Where(x => x.AssignedTo == userIdentifier).ToListAsync();
+ }
+ }
+}
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Controllers/HomeController.cs b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/HomeController.cs
index cbf05823..705ebc72 100644
--- a/1-WebApp-OIDC/1-2-AnyOrg/Controllers/HomeController.cs
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/HomeController.cs
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Identity.Web;
using System.Diagnostics;
using System.Linq;
using WebApp_OpenIDConnect_DotNet.DAL;
@@ -29,7 +30,12 @@ public IActionResult DeleteTenant(string id)
dbContext.RemoveRange(tenants);
dbContext.SaveChanges();
- return RedirectToAction("Index");
+ var signedUserTenant = User.GetTenantId();
+
+ if (id == signedUserTenant)
+ return RedirectToAction("SignOut", "Account", new { area = "AzureAD" });
+ else
+ return RedirectToAction("Index");
}
[AllowAnonymous]
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Controllers/OnboardingController.cs b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/OnboardingController.cs
index 1870c052..a7dba271 100644
--- a/1-WebApp-OIDC/1-2-AnyOrg/Controllers/OnboardingController.cs
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/OnboardingController.cs
@@ -25,14 +25,14 @@ public OnboardingController(SampleDbContext dbContext, IOptions
}
[HttpGet]
- public ActionResult SignUp()
+ public IActionResult SignUp()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
- public ActionResult Onboard()
+ public IActionResult Onboard()
{
// Generate a random value to identify the request
string stateMarker = Guid.NewGuid().ToString();
@@ -60,11 +60,11 @@ public ActionResult Onboard()
Uri.EscapeDataString(stateMarker));
- return new RedirectResult(authorizationRequest);
+ return Redirect(authorizationRequest);
}
// This is the redirect Uri for the admin consent authorization
- public async Task ProcessCode(string tenant, string error, string error_description, string resource, string state)
+ public async Task ProcessCode(string tenant, string error, string error_description, string resource, string state)
{
if (error != null)
{
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Controllers/TodoListController.cs b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/TodoListController.cs
new file mode 100644
index 00000000..d102f481
--- /dev/null
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Controllers/TodoListController.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Identity.Web;
+using WebApp_OpenIDConnect_DotNet.BLL;
+using WebApp_OpenIDConnect_DotNet.Models;
+
+namespace WebApp_OpenIDConnect_DotNet.Controllers
+{
+ [Authorize]
+ public class TodoListController : Controller
+ {
+ private readonly ITodoItemService _todoItemService;
+ public TodoListController(ITodoItemService todoItemService)
+ {
+ _todoItemService = todoItemService;
+ }
+
+ public async Task Index(bool showAllFilter)
+ {
+ @TempData["ShowAllFilter"] = showAllFilter;
+
+ var items = await _todoItemService.List(showAllFilter, User);
+ return View(items);
+ }
+
+ [HttpGet]
+ public IActionResult Create()
+ {
+ var model = new TodoItem()
+ {
+ AssignedTo = User.GetObjectId(),
+ TenantId = User.GetTenantId(),
+ UserName = User.Identity.Name
+ };
+
+ return View(model);
+ }
+
+ [HttpPost]
+ public async Task Create(TodoItem model)
+ {
+ await _todoItemService.Create(model.Text, User);
+ return RedirectToAction("Index");
+ }
+
+ [HttpGet]
+ public async Task Delete(int id)
+ {
+ await _todoItemService.Delete(id, User);
+ return RedirectToAction("Index");
+ }
+ }
+}
\ No newline at end of file
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/DAL/SampleDbContext.cs b/1-WebApp-OIDC/1-2-AnyOrg/DAL/SampleDbContext.cs
index 73b6dd88..121760b6 100644
--- a/1-WebApp-OIDC/1-2-AnyOrg/DAL/SampleDbContext.cs
+++ b/1-WebApp-OIDC/1-2-AnyOrg/DAL/SampleDbContext.cs
@@ -12,5 +12,6 @@ public class SampleDbContext : DbContext
public SampleDbContext(DbContextOptions options) : base(options) { }
public DbSet AuthorizedTenants { get; set; }
+ public DbSet TodoItems { get; set; }
}
}
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Models/TodoItem.cs b/1-WebApp-OIDC/1-2-AnyOrg/Models/TodoItem.cs
new file mode 100644
index 00000000..470382dc
--- /dev/null
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Models/TodoItem.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace WebApp_OpenIDConnect_DotNet.Models
+{
+ public class TodoItem
+ {
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int Id { get; set; }
+ public string Text { get; set; }
+ public string UserName { get; set; }
+ public string AssignedTo { get; set; }
+ public string TenantId { get; set; }
+ }
+}
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Startup.cs b/1-WebApp-OIDC/1-2-AnyOrg/Startup.cs
index 08a18c51..c5d55ec9 100644
--- a/1-WebApp-OIDC/1-2-AnyOrg/Startup.cs
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Startup.cs
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using WebApp_OpenIDConnect_DotNet.BLL;
using WebApp_OpenIDConnect_DotNet.DAL;
using WebApp_OpenIDConnect_DotNet.Utils;
@@ -43,14 +44,16 @@ public void ConfigureServices(IServiceCollection services)
services.AddOptions();
- services.AddDbContext(options => options.UseInMemoryDatabase(databaseName: "MultiTenantOnboarding"));
+ //services.AddDbContext(options => options.UseInMemoryDatabase(databaseName: "MultiTenantOnboarding"));
+ services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("SampleDbConnStr")));
+
+ services.AddScoped();
// Sign-in users with the Microsoft identity platform
services.AddMicrosoftIdentityPlatformAuthentication(Configuration);
-
+
services.Configure(AzureADDefaults.OpenIdScheme, options =>
{
- //options.TokenValidationParameters = BuildTokenValidationParameters(options);
options.Events.OnTokenValidated = async context =>
{
string tenantId = context.SecurityToken.Claims.FirstOrDefault(x => x.Type == "tid" || x.Type == "http://schemas.microsoft.com/identity/claims/tenantid")?.Value;
@@ -91,6 +94,13 @@ public void ConfigureServices(IServiceCollection services)
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
+ using (var serviceScope = app.ApplicationServices.GetService().CreateScope())
+ {
+ var context = serviceScope.ServiceProvider.GetRequiredService();
+ //context.Database.EnsureDeleted();
+ context.Database.EnsureCreated();
+ }
+
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
diff --git a/1-WebApp-OIDC/1-2-AnyOrg/Views/Home/Index.cshtml b/1-WebApp-OIDC/1-2-AnyOrg/Views/Home/Index.cshtml
index a26b3dbc..5b1acb9f 100644
--- a/1-WebApp-OIDC/1-2-AnyOrg/Views/Home/Index.cshtml
+++ b/1-WebApp-OIDC/1-2-AnyOrg/Views/Home/Index.cshtml
@@ -1,4 +1,6 @@
-@model IEnumerable
+@using System.Security.Principal
+@using Microsoft.Identity.Web
+@model IEnumerable
@{
ViewData["Title"] = "Home Page";
}
@@ -9,12 +11,15 @@
This sample shows how to build a .NET Core MVC Web app that uses OpenID Connect to sign in users. Users can use work and school accounts from any company or organization that has integrated with Azure Active Directory. It leverages the ASP.NET Core OpenID Connect middleware.
-
+
Authorized Tenants
- These are the authorized tenants. If you want to repeat the onboarding process, deleted the desired tenants and sign-out.
+ These are the authorized tenants on the database. If you want to repeat the onboarding process, offboard the desired tenant from this list.
+
+
+ Offboarding the signed user's tenant, will trigger a sign-out.