Added basic MSAL UI to the sample, added SPA sample
This commit is contained in:
parent
3828f2d7c4
commit
293976688a
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using IdentityOIDCWebApplicationSample.Identity.Models;
|
using IdentityOIDCWebApplicationSample.Identity.Models;
|
||||||
|
|
@ -9,8 +10,10 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Identity.Service;
|
using Microsoft.AspNetCore.Identity.Service;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace IdentityOIDCWebApplicationSample.Identity.Controllers
|
namespace IdentityOIDCWebApplicationSample.Identity.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -41,6 +44,7 @@ namespace IdentityOIDCWebApplicationSample.Identity.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
[MsalFilter]
|
||||||
public async Task<IActionResult> Login(string returnUrl = null)
|
public async Task<IActionResult> Login(string returnUrl = null)
|
||||||
{
|
{
|
||||||
// Clear the existing external cookie to ensure a clean login process
|
// Clear the existing external cookie to ensure a clean login process
|
||||||
|
|
@ -50,6 +54,25 @@ namespace IdentityOIDCWebApplicationSample.Identity.Controllers
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
|
||||||
|
sealed class MsalFilterAttribute : Attribute, IResultFilter
|
||||||
|
{
|
||||||
|
public void OnResultExecuted(ResultExecutedContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnResultExecuting(ResultExecutingContext context)
|
||||||
|
{
|
||||||
|
if(context.HttpContext.Request.Headers.TryGetValue(HeaderNames.UserAgent, out var header) &&
|
||||||
|
header.Equals("Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; Tablet PC 2.0)") &&
|
||||||
|
context.Result is ViewResult view &&
|
||||||
|
view.ViewName == null)
|
||||||
|
{
|
||||||
|
view.ViewName = "Login.MSAL";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
|
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
@using System.Collections.Generic
|
||||||
|
@using System.Linq
|
||||||
|
@using Microsoft.AspNetCore.Http
|
||||||
|
@using Microsoft.AspNetCore.Authentication
|
||||||
|
@using Microsoft.AspNetCore.Authentication.OpenIdConnect
|
||||||
|
@model LoginViewModel
|
||||||
|
@inject SignInManager<ApplicationUser> SignInManager
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Log in";
|
||||||
|
Layout = null;
|
||||||
|
}
|
||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>@ViewData["Title"]</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
margin: 0px auto;
|
||||||
|
width: 60%;
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
label{
|
||||||
|
margin-bottom: .8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text], input[type=password] {
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<h1 class="login-title">@ViewData["Title"].</h1>
|
||||||
|
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="login-form">
|
||||||
|
<h2 class="login-option">Use a local account to log in.</h2>
|
||||||
|
<div asp-validation-summary="All" class="text-danger"></div>
|
||||||
|
<p>
|
||||||
|
<span class="form-label">@Html.DisplayNameFor(u => u.Email)</span>
|
||||||
|
<input asp-for="Email" class="form-input" />
|
||||||
|
<span asp-validation-for="Email" class="form-validation"></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="form-label">@Html.DisplayNameFor(u => u.Password)</span>
|
||||||
|
<input asp-for="Password" class="form-input" />
|
||||||
|
<span asp-validation-for="Password" class="form-validation"></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<input asp-for="RememberMe" class="form-input" />
|
||||||
|
<span class="form-label">@Html.DisplayNameFor(u => u.RememberMe)</span>
|
||||||
|
</p>
|
||||||
|
<button type="submit" class="btn btn-default">Log in</button>
|
||||||
|
<p>
|
||||||
|
<a asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]">Register as a new user?</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a asp-action="ForgotPassword">Forgot your password?</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
@*<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<section>
|
||||||
|
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
|
||||||
|
<h4>Use a local account to log in.</h4>
|
||||||
|
<hr />
|
||||||
|
<div asp-validation-summary="All" class="text-danger"></div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="Email" class="col-md-2 control-label"></label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<input asp-for="Email" class="form-control" />
|
||||||
|
<span asp-validation-for="Email" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="Password" class="col-md-2 control-label"></label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<input asp-for="Password" class="form-control" />
|
||||||
|
<span asp-validation-for="Password" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-offset-2 col-md-10">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label asp-for="RememberMe">
|
||||||
|
<input asp-for="RememberMe" />
|
||||||
|
@Html.DisplayNameFor(m => m.RememberMe)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-offset-2 col-md-10">
|
||||||
|
<button type="submit" class="btn btn-default">Log in</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<a asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]">Register as a new user?</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a asp-action="ForgotPassword">Forgot your password?</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<section>
|
||||||
|
<h4>Use another service to log in.</h4>
|
||||||
|
<hr />
|
||||||
|
@{
|
||||||
|
var schemes = await SignInManager.GetExternalAuthenticationSchemesAsync();
|
||||||
|
var loginProviders = schemes.Where(scheme => scheme.DisplayName != OpenIdConnectDefaults.AuthenticationScheme).ToList();
|
||||||
|
if (loginProviders.Count == 0)
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
|
||||||
|
for details on setting up this ASP.NET application to support logging in via external services.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
@foreach (var provider in loginProviders)
|
||||||
|
{
|
||||||
|
<button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.Name</button>
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>*@
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using IdentityOIDCWebApplicationSample.Models;
|
using IdentityOIDCWebApplicationSample.Models;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace IdentityOIDCWebApplicationSample.Controllers
|
namespace IdentityOIDCWebApplicationSample.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -24,9 +25,7 @@ namespace IdentityOIDCWebApplicationSample.Controllers
|
||||||
|
|
||||||
public IActionResult Contact()
|
public IActionResult Contact()
|
||||||
{
|
{
|
||||||
ViewData["Message"] = "Your contact page.";
|
return Ok(new { Message = $"API Called with token {HttpContext.Request.Headers[HeaderNames.Authorization]}" });
|
||||||
|
|
||||||
return View();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult Error()
|
public IActionResult Error()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Calling a Web API as a user authenticated with Msal.js app</title>
|
||||||
|
<style>
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible {
|
||||||
|
visibility: visible
|
||||||
|
}
|
||||||
|
|
||||||
|
.response {
|
||||||
|
border: solid;
|
||||||
|
border-width: thin;
|
||||||
|
background-color: azure;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- bluebird only needed if this page needs to run on Internet Explorer -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js" class="pre"></script>
|
||||||
|
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/0.1.1/js/msal.min.js"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.min.js" class="pre"></script>
|
||||||
|
|
||||||
|
<h2>Getting an access token with Azure AD B2C and calling a Web API</h2>
|
||||||
|
<div>
|
||||||
|
<div id="label">Sign-in with Microsoft Azure AD B2C</div>
|
||||||
|
<button id="auth" onclick="login()">Login</button>
|
||||||
|
<button id="callApiButton" class="hidden" onclick="callApi()">Call Web API</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre class="response"></pre>
|
||||||
|
|
||||||
|
<script class="pre">
|
||||||
|
// The current application coordinates were pre-registered in a B2C tenant.
|
||||||
|
var applicationConfig = {
|
||||||
|
clientID: '4c0e3ab6-3bdc-4eca-80ab-89669d974e13',
|
||||||
|
authority: "https://localhost:44324/tfp/IdentityService/signinsignup",
|
||||||
|
b2cScopes: ["https://localhost/DFC7191F-FF74-42B9-A292-08FEA80F5B20/v2.0/spa/read"],
|
||||||
|
webApi: 'https://localhost:44324/Home/Contact',
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
var clientApplication = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, function (errorDesc, token, error, tokenType) {
|
||||||
|
// Called after loginRedirect or acquireTokenPopup
|
||||||
|
});
|
||||||
|
|
||||||
|
function login() {
|
||||||
|
clientApplication.loginPopup(applicationConfig.b2cScopes).then(function (idToken) {
|
||||||
|
clientApplication.acquireTokenSilent(applicationConfig.b2cScopes).then(function (accessToken) {
|
||||||
|
updateUI();
|
||||||
|
}, function (error) {
|
||||||
|
clientApplication.acquireTokenPopup(applicationConfig.b2cScopes).then(function (accessToken) {
|
||||||
|
updateUI();
|
||||||
|
}, function (error) {
|
||||||
|
logMessage("Error acquiring the popup:\n" + error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}, function (error) {
|
||||||
|
logMessage("Error during login:\n" + error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUI() {
|
||||||
|
var userName = clientApplication.getUser().name;
|
||||||
|
logMessage("User '" + userName + "' logged-in");
|
||||||
|
var authButton = document.getElementById('auth');
|
||||||
|
authButton.innerHTML = 'logout';
|
||||||
|
authButton.setAttribute('onclick', 'logout();');
|
||||||
|
var label = document.getElementById('label');
|
||||||
|
label.innerText = "Hello " + userName;
|
||||||
|
var callWebApiButton = document.getElementById('callApiButton');
|
||||||
|
callWebApiButton.setAttribute('class', 'visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
function callApi() {
|
||||||
|
clientApplication.acquireTokenSilent(applicationConfig.b2cScopes).then(function (accessToken) {
|
||||||
|
callApiWithAccessToken(accessToken);
|
||||||
|
}, function (error) {
|
||||||
|
clientApplication.acquireTokenPopup(applicationConfig.b2cScopes).then(function (accessToken) {
|
||||||
|
callApiWithAccessToken(accessToken);
|
||||||
|
}, function (error) {
|
||||||
|
logMessage("Error acquiring the access token to call the Web api:\n" + error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function callApiWithAccessToken(accessToken) {
|
||||||
|
// Call the Web API with the AccessToken
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: applicationConfig.webApi,
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + accessToken,
|
||||||
|
},
|
||||||
|
}).done(function (data) {
|
||||||
|
logMessage("Web APi returned:\n" + JSON.stringify(data));
|
||||||
|
})
|
||||||
|
.fail(function (jqXHR, textStatus) {
|
||||||
|
logMessage("Error calling the Web api:\n" + textStatus);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
// Removes all sessions, need to call AAD endpoint to do full logout
|
||||||
|
clientApplication.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
function logMessage(s) {
|
||||||
|
document.body.querySelector('.response').appendChild(document.createTextNode('\n' + s));
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue