Added basic MSAL UI to the sample, added SPA sample

This commit is contained in:
Javier Calvarro Nelson 2017-08-22 11:08:13 -07:00
parent 3828f2d7c4
commit 293976688a
4 changed files with 314 additions and 4 deletions

View File

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityOIDCWebApplicationSample.Identity.Models;
@ -9,8 +10,10 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.Service;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace IdentityOIDCWebApplicationSample.Identity.Controllers
{
@ -41,6 +44,7 @@ namespace IdentityOIDCWebApplicationSample.Identity.Controllers
}
[HttpGet]
[MsalFilter]
public async Task<IActionResult> Login(string returnUrl = null)
{
// Clear the existing external cookie to ensure a clean login process
@ -50,6 +54,25 @@ namespace IdentityOIDCWebApplicationSample.Identity.Controllers
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]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)

View File

@ -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>*@

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using IdentityOIDCWebApplicationSample.Models;
using Microsoft.Net.Http.Headers;
namespace IdentityOIDCWebApplicationSample.Controllers
{
@ -24,9 +25,7 @@ namespace IdentityOIDCWebApplicationSample.Controllers
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
return Ok(new { Message = $"API Called with token {HttpContext.Request.Headers[HeaderNames.Authorization]}" });
}
public IActionResult Error()

View File

@ -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>