Modern Authentication With Azure Active Directory For Web Applications PDF
Modern Authentication With Azure Active Directory For Web Applications PDF
6388”
Authentication with
cloud or web environment • For architects, application designers,
developer leads, and security consul-
Active Directory has been transformed to reflect the cloud revolu- tants involved in authentication, access
tion, modern protocols, and today’s newest SaaS paradigms. This control, or personalization
is an authoritative, deep-dive guide to building Active Directory • For security and protocol experts who
Professional
• Work with the Azure AD representation of apps and their
relationships
Get code samples, including
• Provide fine-grained app access control via roles, groups, and
permissions complete apps, at:
• Consume and expose Web APIs protected by Azure AD http://aka.ms/modauth/files
• Understand new authentication protocols without reading complex Bertocci
spec documents
MicrosoftPressStore.com
Vittorio Bertocci
First Printing
Microsoft Press books are available through booksellers and distributors worldwide. If you need support related
to this book, email Microsoft Press Support at mspinput@microsoft.com. Please tell us what you think of this
book at http://aka.ms/tellpress.
This book is provided “as-is” and expresses the author’s views and opinions. The views, opinions and information
expressed in this book, including URL and other Internet website references, may change without notice.
Some examples depicted herein are provided for illustration only and are fctitious. No real association or
connection is intended or should be inferred.
Microsoft and the trademarks listed at www.microsoft.com on the “Trademarks” webpage are trademarks of the
Microsoft group of companies. All other marks are property of their respective owners.
Prerequisites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Microsoft Azure subscription . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Visual Studio 2015 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Claims-based identity. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Identity providers: DCs for the Internet . . . . . . . . . . . . . . . . . . . . . . . . 17
Tokens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Trust and claims . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .20
Claims-oriented protocols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .20
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .67
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .87
vi Contents
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .105
Contents vii
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .136
TokenValidationParameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .167
Valid values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .168
Validation flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .169
Validators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .169
Miscellany . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .172
viii Contents
Groups. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .219
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .221
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .272
Contents ix
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .292
Index 295
x Contents
The purpose of an application is to take input from users or other applications and
produce output that will be consumed by those same users or applications or by other
ones. That’s true of a website that gains input from a click on a link and sends back
the content of the requested page as output; a middle tier that processes database
requests queued from a front end, executing them by sending input to a database; or a
cloud service that gets input from a mobile application to look up nearby friends. Given
this, a fundamental question faced in the design of every application is, Who is sending
the input and should the application process it to produce the resulting output? Put
another way: every application must decide on an identity system that represents users
and other applications, a means by which to validate an application’s or user’s claimed
identity, and a way to determine what outputs the user or application is allowed to
produce.
These decisions will determine how easily users and applications can interact with
an application, what functionality they can take advantage of to secure and manage
their identities and credentials, and how much work the application developer must
do to enable these capabilities, which are known as authentication and authorization.
The ideal answers make it possible for users and applications to use their preferred
identities, whether from Facebook, Gmail, or their enterprise; for the application to
easily configure the access rights for authorized users; and for the application to rely on
other services as much as possible to do the heavy lifting. Identity and access control,
while key to an application’s utility, are not the core value an application delivers, so
developers shouldn’t spend any more time on this area than they have to. Why create
a database of users and worry about which algorithm to use to encrypt the users’
passwords if you can take advantage of a service that’s built for doing just that, with
industry-leading security and management?
Microsoft Azure Active Directory (Azure AD) is arguably the heart of Microsoft’s
cloud platform. All Microsoft cloud services, including Microsoft Azure, Microsoft Xbox
Live, and Microsoft Office 365, use Azure AD as their identity provider. And because
Azure AD is a public cloud service, application developers can also take advantage of
its capabilities. If an application relies on Azure AD as its identity provider, it can rely
on Azure AD APIs to provision users, rely on Azure AD to manage their passwords,
and even give users the ability to use multifactor authentication (MFA) to securely
authenticate to the application. For application developers wanting to integrate with
businesses, including the many that are already using Azure AD, Azure AD has the most
flexible and comprehensive support of any service for integrating Active Directory and
LDAP identities. Fueled by enterprise adoption of Office 365, Azure AD is already a
xi
Using Azure AD for the most common scenarios is easy, thanks to the open source
developer libraries, tooling, and guidance available on Microsoft Azure’s GitHub orga-
nization. Going beyond the basics, however, requires a good understanding of modern
authentication flows—specifically OAuth2 and OpenID Connect—and concepts such
as a relying party and tokens, federation, role-based access control, a provisioned ap-
plication, and service principles. If you’re new to these protocols and terms, the learning
curve can seem daunting. Even if you’re not, knowing the most efficient way to use
Azure AD and its unique capabilities is important, and it’s worthwhile understanding
what’s available to you.
There’s no better book than Modern Authentication with Azure Active Directory for
Web Applications to help you make your application take full advantage of Azure AD.
I’ve known Vittorio Bertocci since I started in Azure five years ago, and I’ve watched his
always popular and highly rated Microsoft TechEd, Build, and Microsoft Ignite confer-
ence presentations to catch up with the latest developments in Azure AD. He’s a master
educator and one of Microsoft’s foremost experts on identity and access control.
This book will guide you through the essentials of authentication protocols, decipher
the disparate terminology applied to the subject, tell you how to get started with Azure
AD, and then present concrete examples of applications that use Azure AD for their
authentication and authorization, including how they work in hybrid scenarios with
Active Directory Federation Services (ADFS). With the information and insights Vittorio
shares, you’ll be able to efficiently create modern cloud applications that give users and
administrators the flexibility and security of Microsoft’s cloud and the convenience of
using their preferred identities.
Mark Russinovich
Chief Technology Officer, Microsoft Azure
xii Foreword
It’s never a good idea to use the word “modern” in the title of a book.
I guarantee that the content in this book will get old faster than those old volumes—
cloud and development technologies evolve at a crazy pace—and yet I could not resist
referring to the main subject of the book as “modern authentication.”
As the landscape evolves, Active Directory evolves with it. When Microsoft itself
introduced one of the most important SaaS products on the planet, Office 365, it felt
firsthand how cloud-based workloads call for new ways of managing user access and
application portfolios. To confront that challenge Microsoft developed Azure Active
Directory (Azure AD), a reimagined Active Directory that takes advantage of all the new
protocols, artifacts, and practices that I’ve grouped under the modern authentication
umbrella. Once it was clear that Azure AD was a Good Thing, it went on to become the
main authentication service for all of Microsoft’s cloud services, including Intune, Power
BI, and Azure itself. But the real raison d’etre of this book is that Microsoft opened
Azure AD to every developer and organization so that it could be used for obtain-
ing tokens to invoke Microsoft APIs and to handle authentication for your own web
applications and web APIs.
xiii
The text is meant to help you achieve expert-level understanding of the protocols
and technologies involved in implementing modern authentication for a web app.
Substantial space is reserved for architectural pattern descriptions, protocol consider-
ations, and other abstract concerns that are necessary for correctly contextualizing the
more hands-on advice that follows.
Most of the practical content in this book is about cloud and hybrid scenarios
addressed via Azure AD. At the time of writing, the version of ADFS supporting modern
authentication for web apps is still in technical preview; however, on-premises-only
scenarios are covered whenever the relevant features are already available in the
preview.
Developers who worked with Windows Identity Foundation will find the text useful
for transferring their skills to the new platform, and they’ll pick up some new tricks
along the way. The coverage of how the OWIN middleware works is deeper than
xiv Introduction
This book also comes in handy for security experts coming from a classic back-
ground and looking to understand modern protocols and approaches to authenti-
cation—the principles and protocols I describe can be applied well beyond Active
Directory and ASP.NET. Security architects considering Azure AD for their solutions can
use this book to understand how Azure AD operates. Protocol experts who want to
understand how Azure AD and ADFS use OpenID Connect and OAuth2 will find plenty
to mull over as well.
Assumptions
This book is for senior professionals well versed in development, distributed architec-
tures, and web-based solutions. You need to be familiar with HTTP trappings and have
at least a basic understanding of networking concepts. All sample code is presented
in C#, and all walk-throughs are based on Visual Studio. Azure AD and ADFS can be
made use of from any programming stack and operating system; however, if you don’t
understand C# syntax and basic constructs (LINQ, etc.), it will be difficult for you to
apply the coding advice in this book to your platform of choice. For good background,
I’d recommend John Sharp’s Microsoft Visual C# Step by Step, Eighth Edition (Microsoft
Press, 2015).
Above all, this book assumes that you are strongly motivated to become an expert
in modern authentication techniques and Azure AD development. The text does not
take any shortcuts: you should not expect a light read; most chapters require significant
focus and time investment.
This book is also not especially good as a lookup reference. The text covers a lot
of ground, including information that isn’t included in the documentation at this
Introduction xv
Finally, this book won’t be of much help if you are developing mobile, native, and
rich-client applications. I originally intended to cover those types of applications, too,
but the size of the book would have nearly doubled, so I had to cut them from this
edition.
If this book has a natural fault line in its organization, it lies between the first four
chapters and the last six. The first group provides context, and the later chapters dive
deeply into the protocols, code, libraries, and features of Active Directory. Here’s a
quick description of each chapter’s focus:
■ Chapter 1, “Your first Active Directory app,” is a soft introduction to the topic,
giving you a brief glimpse of what you can achieve with Azure AD. It mostly
provides instructions on how to use Visual Studio tools to create a web app
that’s integrated with Azure AD. Instant gratification.
xvi Introduction
■ Chapter 5, “Getting started with web sign-on and Active Directory,” provides
a walk-through of how to create from scratch a web app that can sign in with
Azure AD. Starting with the vanilla MVC templates, you learn about the NuGets
packages you need to add, what app provisioning steps you need to follow in
the Azure portal, and what code you need to write to perform key authentica-
tion tasks.
Introduction xvii
■ The appendix, “Further reading,” provides you with pointers to online content
describing ancillary topics and offerings that are still too new to be fully fleshed
out in the book but are interesting and relevant to the subject of modern
authentication.
System requirements
You will need the following software if you want to follow the code walk-throughs in
this book:
■ Any Windows version that can run Visual Studio 2015 or later.
■ Visual Studio 2015, any edition (technically, apart from Chapter 1, Visual
Studio 2015 isn’t a hard requirement; Visual Studio 2013 will work with just
a few adjustments).
xviii Introduction
You can find the code I use in the book on my GitHub, at the following address:
http://aka.ms/modauth/files
Once again, don’t expect too much handholding: the code is provided mostly for
reference. (Microsoft’s fficial step-by-step samples and quick starts are provided at
http://aka.ms/aaddev.) If a sample requires extra steps to fully demonstrate a scenario
(for example, the presence in your tenant of an admin and at least a nonadmin user),
I’ve assumed that you’ll get that information by reading the book and don’t repeat it
in the sample’s readme. The code provided at http://aka.ms/modauth/files is meant to
support and complement the reading of the book rather than as a standalone asset.
Introduction xix
This book has been a labor of love, written during nights, weekends, and occasional
time off. I have willingly put that yolk on myself, but my wife, Iwona Bialynicka-Birula,
did not . . . she endured nearly one year of missed hikes, social jet lag, and a silence
curfew “because I have to write.” Thank you for your patience, darling—as I promised in
the acknowledgments for Programming Windows Identity Foundation back in 2011: No
more books for a few years!
This book would not have happened at all if Devon Musgrave, my acquisitions
editor, would not have relentlessly pursued it, granting me a level of trust and freedom
I am not sure I fully deserve. Thank you, Devon!
John Pierce has been an absolutely incredible project editor, driving everything
from editing to project management to illustrations. He has this magic ability of turning
my broken English into correct sentences while preserving my original intent. I wish
every technical writer would have the good fortune of working with somebody as
gifted as John. Rob Nance and Carrie Wicks also made significant contributions to
producing this book.
I will be forever grateful to Mark Russinovich for the fantastic foreword he wrote
for the book and for the kind words he offered about me. I am truly humbled to have
my book begin with the words of a legend in software engineering.
Big thanks to my management chain for supporting this side project. Alex Simons,
Eric Doerr, Stuart Kwan—thank you! I never quite managed to write on Fridays, but it
was a great attempt.
I need to call out Stuart for a special thanks—from welcoming me to the product
team to mentoring me through the transition from evangelism to product manage-
ment. A large part of whatever success I have achieved is thanks to our work together.
Thank you!
Rich Randall, the development lead on the Azure AD developer experience team,
is my partner in crime and recipient of my utmost respect and admiration. Without
his amazing work, none of the libraries described in this book would be around. And
without the contribution of Afshin Sephetri, Kanishk Panwar, Brent Schmaltz,
Tushar Gupta, Wei Jia, Sasha Tokarev, Ryan Pangrle, Chris Chartier, and
xx Introduction
Danny Strockis has been on the PM team for a relatively short time, but his contri-
butions are already monumental. Ariel Gordon, responsible for designing many of the
experiences that the Azure AD users go through every day, is a source of never-ending
insights. Dushyant Gill drove the authorization features in Azure AD, and he patiently
explained those to me every single time I barged into his office.
Igor Sakhnov, developer manager for Azure AD authentication, and his then-PM
counterpart David Howell have my gratitude for trusting us on the decision to move
the web authentication stack to OWIN. It worked out pretty well!
Speaking of OWIN. Chris Ross, Tushar Gupta, Brent Schmaltz, Daniel Roth,
Louis Dejardin, Eilon Lipton, and Barry Dorrans all did a fantastic job, both in
developing and driving the libraries and in handling my mercurial outbursts. Dan, I told
you we’d get there! Special thanks to Chris Ross and Tushar Gupta for reviewing
Chapter 7 in record time.
I started working with Scott Hunter on ASP.NET tooling and templates back in
2012 and loved every second. The man cares deeply about customers, understands the
importance of identity, and is a force to reckon with. It is thanks to him and to my good
friends Pranav Rastogi, Brady Gaster, and Dan Roth that web apps in Visual Studio
can be enabled for Azure AD in just a few clicks.
In my opinion, Visual Studio 2015 has the most sophisticated identity management
features in all of Microsoft’s rich clients, and that’s largely thanks to the relentless work
that Anthony Cangialosi, Ji Eun Kwon, and all the Visual Studio and Visual Studio
Online gang poured into it. That made it possible for many other teams to build on
that core and deliver first-class identity support in Visual Studio for Azure, Office 365,
and more. Among others, we have Chakkaradeep (Chaks) Chinnakonda Chandran,
Dan Seefeldt, Steve Harter, Xiaoying Guo, Yuval Mazor, Sean Laberee, and Paul
Yuknewicz to thank for that.
The guys working on the directory data model, portal, and Graph API are also
amazing in all sorts of ways: Dan Kershaw, Edward Wu, Yi Li, Dmitry Pugachev,
Introduction xxi
Besides doing a fantastic job with ADFS in Windows Server 2016, Samuel Devasa-
hayam, Mahesh Unnikrishnan, Jen Field, Jim Uphaus, and Saket Kataruka from the
ADFS team were of great help for Chapter 10.
The people on the partner teams are the ones who keep things real: they won’t be
satisfied until the services and libraries address their scenarios, and in so doing they
push the services to excellence. Mat Velloso from Evangelism; Rob Howard, Matthias
Leibmann, Yina Arenas, and Tim McConnell from Office 365; Shriram Natarajan
(Shri) and Pavel Tsurbeleu from Azure Stack; Dave Brankin, David Messner, Yugang
Wang, and George Moore from Azure; and Hadeel Elbitar from Power BI are all
people who keep asking the right questions and offer priceless insights. Thank you
guys!
The identirati community plays a key role in moving modern authentication forward,
divining what the industry wants and translating it into the form of RFC stone tablets.
I am super grateful to John Bradley for our beer-fueled chats every time we meet at
the Cloud Identity Summit and to the excellent Brian Campbell and, well, Canadian
Paul Madsen for the friendly banter; to Bob Blakley and Ian Glazer for never failing
to inspire; and to our own Mike Jones and Anthony Nadalin for being dependable,
in-house protocol oracles. Although I cannot stop myself from reminding Tony that it is
imperative that he work on his focus—he’ll know what that means.
Last but not least, I want to thank the readers of my blog, my Twitter followers, the
people I engage with on StackOverflow, and the people I meet at conferences during
my sessions and afterward. It is your passion, your desire to know more and be more
effective, and, yes, your affection,that made me decide to invest time in writing this
book. Thank you for your incredible energy. This book is for you.
xxii Introduction
If you discover an error that is not already listed, please submit it to us at the same
page.
If you need additional support, email Microsoft Press Book Support at mspinput@
microsoft.com.
Please note that product support for Microsoft software and hardware is not offered
through the previous addresses. For help with Microsoft software or hardware, go to
http://support.microsoft.com.
http://aka.ms/mspressfree
http://aka.ms/tellpress
We know you’re busy, so we’ve kept it short with just a few questions. Your
answers go directly to the editors at Microsoft Press. (No personal information will
be requested.) Thanks in advance for your input!
Stay in touch
Let’s keep the conversation going! We’re on Twitter:
http://twitter.com/MicrosoftPress
Introduction xxiii
In this chapter I focus on the OpenID Connect middleware and supporting classes. These are the
cornerstones of ASP.NET’s support for web sign-on.
As you saw in Chapter 5, “Getting started with web sign-on and Active Directory,” in the most
common case, the OpenID Connect middleware requires very few parameters to enable web sign-on.
Beneath that simple surface, however, are knobs for practically anything you want to control: protocol
parameters, initialization strategies, token validation, message processing, and so on. This chapter will
reveal the various layers of the object model for you, showing how you can fine-tune the authentica-
tion process to meet your needs.
OWIN is a stable standard at this point, but its implementations are still relatively new
technologies. You can find plenty of information online, but the details are rapidly changing (as I
write, ASP.NET vNext is in the process of renaming lots of classes and properties), and you need to
have a solid understanding of the pipeline and model underlying the identity functionality.
In this section I provide a quick tour of OWIN (as implemented in Katana 3.0.1) and the features
that are especially relevant for the scenarios I’ve described throughout the book. For more details,
you can refer to the online documentation from the ASP.NET team.
What is OWIN?
OWIN stands for Open Web Interface for .NET. It is a community-driven specification: Microsoft
is just a contributor, albeit a very important one. Here’s the official definition, straight from the
specifications’ home page at http://owin.org/.
137
In essence, OWIN suggests a way of building software modules (called middlewares) that can
process HTTP requests and responses. It also describes a way in which those modules can be
concatenated in a processing pipeline and defines how that pipeline can be hosted without relying
on any specific web server or host or the features of a particular development stack.
The core idea is that, at every instant, the state of an HTTP transaction and the server-side
processing of it is represented by a dictionary of the following form:
IDictionary<string, object>
This is commonly known as the environment dictionary. You can expect to find in it the usual
request and response data (host, headers, query string, URI scheme, and so on) alongside any data
that might be required for whatever processing an app needs to perform. Where does the data come
from? Some of it, like the request details, must eventually come from the web server. The rest is the
result of the work of the middleware in the pipeline.
I am sure you have already guessed how things might work. The middleware receives the
environment dictionary as input, acts on it to perform the middleware’s function, and then hands
it over to the next middleware in the pipeline. For example, logging middleware might read the
dictionary and pass it along unmodified, but an authentication middleware might find a 401 code in
the dictionary and decide to transform it into a 302, modifying the response to include an authentica-
tion request. By using the dictionary as the way of communicating and sharing context, as opposed
to calling each other directly, middlewares achieve a level of decoupling that was not possible in older
approaches.
How do you bootstrap all this? At startup, the middleware pipeline needs to be constructed and
initialized: you need to decide what middlewares should be included and in which order and ensure
that requests and responses will be routed through the pipeline accordingly. The OWIN specification
has a section that defines a generic mechanism for this, but given that you will be working mostly
with the ASP.NET-specific implementation, I won’t go into much detail on that.
I skipped an awful lot of what the formulaic descriptions of OWIN normally include (like the formal
definitions of application, middleware, server, and host), but I believe that this brief description should
provide you enough scaffolding for understanding Katana, ASP.NET’s implementation of OWIN.
138 CHAPTER 7
Katana != OWIN
OWIN is an abstract specification. Katana is a set of concrete classes that implement that spec,
but it also introduces its own implementation choices for tasks that aren’t fully specified or in
scope for the OWIN spec. In giving technical guidance, it’s easy to say something to the effect
“in OWIN, you do X,” but it is often more proper to say “in Katana, you do X.” I am sure I will be
guilty of this multiple times: please accept my blanket apologies in advance.
In Chapter 5 you encountered most of the Katana NuGet packages and assemblies that appear
in common scenarios. You also successfully used them by entering the code I suggested. Here I’ll
reexamine all that, explaining what really happens.
Using the attribute is only one of several ways of telling Katana which class should act as Startup.
You can also do the following:
■ Have one class named Startup in the assembly or the global namespace.
■ Use the OwinStartup attribute. The attribute wins against the naming convention (using
Startup): if both techniques are used, the attribute will be the one driving the behavior.
■ Add an entry under <appSettings> in the app config file, of the form
This entry wins over both the naming convention and the attribute.
Let’s now turn our attention to Startup.Configure. Observe the method’s signature:
IAppBuilder is an interface designed to support the initialization of the application. It looks like
this:
The Properties dictionary is used in turn by the server and host to record their capabilities and
initialize data, making it available to the application’s initialization code—that is to say, whatever
you put in Configure. In the case of our sample app, the server is IIS Express and the host is the
SystemWeb host we referenced when adding the NuGet package with the same name.
Note That host is actually an HttpModule designed to host the OWIN pipeline. That’s the
trick Katana uses to integrate with the traditional System.Web pipeline.
The Build method is rarely called in your own code, so I’ll ignore it here. The Use method enables
you to add middleware to the pipeline, and I’ll get to that in a moment.
To prove to you that the host does indeed populate app at startup, let’s take a peek at the app
parameter when Configure is first invoked. Open Visual Studio, open the solution from Chapter 5,
place a breakpoint on the first line of Configure, and hit F5. Once the breakpoint is reached, navi-
gate to the Locals tab and look at the content of app. You should see something similar to Figure 7-1.
140 CHAPTER 7
Wow, we didn’t even start, and look at how much stuff is there already!
Katana provides a concrete type for IAppBuilder, named AppBuilder. As expected, the
Properties dictionary arrives already populated with server, host, and environment properties.
Feel free to ignore the actual values at this point. Just as an example, in Figure 7-1 I highlighted the
host.AppName property holding the IIS metabase path for the app.
The nonpublic members hold a very interesting entry: _middleware. If you keep an eye on that
entry as you go through the pipeline-initialization code in the next section, you will see the value of
Count grow at every invocation of Use*.
FIGURE 7-2 The OWIN pipeline as implemented by Katana in the sample application scenario: an HttpModule,
hosting a cascade of middlewares.
Note that one middleware can always decide that no further processing should happen. In that
case the middleware will not call the Invoke method of the next middleware in the sequence,
effectively short-circuiting the pipeline.
142 CHAPTER 7
There’s a neat trick you can use for observing firsthand how the middleware pipeline unfolds. You
can interleave the Use* sequence with your own debug middlewares, and then place strategic break-
points or debug messages. Here’s the pattern for a minimal middleware-like debug implementation:
That’s pretty easy. Here’s the sequence from the sample app, modified accordingly:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.Use(async (Context, next) =>
{
Debug.WriteLine("1 ==>request, before cookie auth");
await next.Invoke();
Debug.WriteLine("6 <==response, after cookie auth");
});
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
Authority = "https://login.microsoftonline.com/DeveloperTenant.onmicrosoft.com",
PostLogoutRedirectUri = https://localhost:44300/
}
);
Run the sample app and see whether everything works as expected. But before you do, you
need to disable one Visual Studio feature that interferes with our experiment: it’s the Browser Link.
The Browser Link helps Visual Studio communicate with the browser running the app that’s being
debugged and allows it to respond to events. The unfortunate side effect for our scenario is that
Browser Link produces extra traffic. In Chapter 6, “OpenID Connect and Azure AD web sign-on,” we
solved the issue by hiding the extra requests via Fiddler filters, but that’s not an option here. Luckily,
it’s easy to opt out of the feature. Just add the following line to the <appSettings> section in the
web.config file for the app:
That done, hit F5. As the home page loads, the output window will show something like the
following:
You can see that all the middlewares executed, and all in the order that was predicted when you
assigned sequence numbers. Never mind that this doesn’t appear to do anything! You’ll find out more
about that in the next section.
Click Contact or Sign In on the home page. Assuming that you are not already signed in, you
should see pretty much the same sequence you’ve seen earlier (so I won’t repeat the output window
content here), but at the end of it your browser will redirect to Azure AD for authentication. Authen-
ticate, and then take a look at the output window to see what happens as the browser returns to the
app. You should see something like this:
144 CHAPTER 7
What happened? Recall what you studied in the section “Response” in Chapter 6: when the OpenID
Connect middleware first receives the token, it does not grant access to the app right away. Rather, it
sends back a 302 for honoring any internal redirect and performs a set-cookie operation for placing
the session cookie in the browser. That’s exactly what happens in the steps 1, 2, 5, and 6: the OpenID
Connect middleware decides that no further processing should take place and initiates the response
sequence. The full 1–6 sequence that follows is what happens when the browser executes the 302 and
comes back with a session cookie.
That’s it. At this point, you should have a good sense of how middlewares come together to form
a single, coherent pipeline. The last generic building block you need to examine is the context that all
middlewares use to communicate.
Sign out of the app and stop the debugger so that the next exploration will start from a clean
slate.
This is not the whole story: for example, it’s possible to use stage markers for requesting
sequences that are incompatible with the natural sequencing of events in the IIS pipeline. There
are a number of rules that determine Katana’s behavior in those cases. Please refer to the
ASP.NET documentation for details.
Context Before getting to the specifics of authentication, let’s invest a few moments to get to know
the OWIN context better.
FIGURE 7-4 The Context.Authentication property content upon receiving the first unauthenticated
request.
146 CHAPTER 7
FIGURE 7-5 The content of the OWIN environment dictionary on first request.
■ Request and Response If you are familiar with HTTP request and response manipulation in
traditional ASP.NET, you should be quite comfortable with their similarly named Context
properties in Katana. Figure 7-6 shows an example.
■ TraceOutput This property is mainly a clever way of exposing a standard trace at the OWIN
level, regardless of the specific host used to run the pipeline.
Add more breakpoints for the other debug middlewares and see how Context changes as the
execution sweeps through the pipeline. After you have experimented with this, head to the next
section, where I review the authentication flow through the OWIN pipeline in detail.
Authentication middleware
The authentication functionality emerges from the collaboration of a protocol middleware (like those
for OpenID Connect or WS-Federation) and the cookie middleware. The protocol middleware reacts
to requests and responses by generating and processing protocol messages, with all that entails
(token validation and so on). The cookie middleware persists sessions in the form of cookies at sign-in
and enforces the presence and validity of such cookies from the instant of authentication onward. All
communication between the two middlewares takes place via the AuthenticationManager instance
in the Context. Let’s break down the sign-in flow we captured earlier into three phases: generation
of the sign-in challenge, response processing and session generation, and access in the context of a
session.
Sign-in redirect message Assume that you triggered the sign-in flow by clicking Contact. As you
observed, this action results in all the middlewares firing in the expected order, and it concludes with
the redirection of the browser toward Azure AD with an authorization request message.
If you go through the flow while keeping an eye on Context.Response, you will notice that after
the request leaves the OWIN pipeline (after the debug message marked 3), something changes the
Response’s StatusCode to 401. In this case, that was the good old [Authorize], which does its job
to enforce authenticated access regardless of the presence of the OWIN pipeline.
148 CHAPTER 7
Walking through the rest of the breakpoint, you will see the response message go unmodified
through the remainder of the pipeline and back to the browser.
In Katana parlance, the OpenID Connect middleware is Active by default. That means that
its options class’s AuthenticationMode property is set to Active, which makes it react to 401
responses by generating its sign-in challenge message. That is not always what you want: for example,
if you have multiple protocol middlewares configured to talk to different IdPs, you will want explicit
control (via calls to Authentication.Challenge) over what middleware should be in charge to
generate the sign-in message at a given moment.
Figure 7-7 displays the steps in the sequence of the sign-in message generation phase.
FIGURE 7-7 The sequence through which an unauthenticated request elicits the generation of a sign-in message.
Token validation and establishment of a session The sequence that processes the Azure AD
response carrying the token (characterized by the debug sequence 1, 2, 5, 6 earlier) is the one
requiring the most sophisticated logic.
The request goes through the cookie middleware (breakpoints on messages 1 and 2) unmodified.
However, as soon as you step over the Invoke call in the diagnostic middleware that calls the OpenID
Connect middleware, you’ll observe that the execution goes straight to the breakpoint on debug
message 5, skipping the rest of the pipeline and the app itself and initiating the response.
Saving the session is the job of the cookie middleware, which at this point has not yet had a chance
to process the response. In fact, saving a session might be a far more complicated matter than simply
sending back a Set-Cookie header. For example, you might want to save the bulk of the session on
a server-side store: the cookie middleware provides that ability as a service so that any protocol
middleware can leverage it without having to reinvent the process every time.
FIGURE 7-8 The AuthenticationResponseGrant content right after the OpenID Connect middleware
successfully validates a sign-in response from Azure AD.
Properties refers to generic session properties, such as the validity window (derived from the
validity window of the token itself, as declared by Azure AD). Identity, as you guessed, is the
150 CHAPTER 7
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
That told the protocol middlewares in the pipeline that in the absence of local overrides, the
identifier to use for electing an identity to be persisted in a session is CookieAuthentication-
Defaults.AuthenticationType, which happens to be the string “Cookies”. When the OpenID
Connect middleware validates the incoming token and generates the corresponding ClaimsPrinci-
pal and nested ClaimsIdentity, it uses that value for the AuthenticationType property. When
the cookie middleware starts processing the response and finds that ClaimsIdentity, it verifies that
the AuthenticationType it finds there corresponds to the AuthenticationType value it has in its
options. Given that here we used the defaults everywhere, it’s a match; hence, the cookie middleware
proceeds to save the corresponding ClaimsPrincipal in the session.
If you examine the Response.Headers collection after the cookie middleware has a chance to
execute, you will see that the Set-Cookie value now includes a new entry for an .Asp.Net.Cookies,
which contains the ClaimsPrincipal information. Figure 7-9 summarizes the sequence.
FIGURE 7-9 The token-validation and session-creation sequence. The OpenID Connect middleware processes
the incoming token, passing the user identity information it carries to the cookie middleware. In turn, the cookie
middleware saves the user identity information in a session cookie.
Figure 7-10 shows how this all plays out through the middleware pipeline.
FIGURE 7-10 During a session, every request carries the session token, which is validated, decrypted, and parsed
by the cookie middleware. The user claims are made available to the application.
Explicit use of Challenge and SignOut The explicit sign-in and sign-out operations you imple-
mented in the AccountController of the sample app also use the Authentication property of
Context to communicate with the middleware in the pipeline.
If you want to see how Challenge works, repeat the sign-in flow as described earlier, but this
time trigger it by clicking Sign In. Stop at the breakpoint on debug message 4. You will see that the
response code is a 401, just like the case we examined earlier. However, here you will also see popu-
lated entries in Authentication, in particular AuthenticationResponseChallenge. If you peek
into it, you’ll see that AuthenticationResponseChallenge holds the AuthenticationType of
the middleware you want to use for signing in (“OpenIdConnect”) and the local redirect you want to
perform after sign-in (in this case, the root of the app). If the OpenID Connect middleware is set to
Passive for AuthenticationMode, the presence of the 401 response code alone is not enough to
provoke the sign-in message generation, but the presence of AuthenticationResponseChallenge
guarantees that it will kick in. Other than that, the rest of the flow goes precisely as described.
The sign-out flow is very similar. Hit the Sign Out link. Stopping at the usual breakpoint 4,
you’ll observe that Authentication now holds a populated AuthenticationResponseRevoke
152 CHAPTER 7
FIGURE 7-11 The contributions to the sign-out sequence from each middleware in the pipeline.
Diagnostic middleware
When something goes wrong in the OWIN pipeline, finding the culprit is often tricky. Adding
breakpoints to an “in line” middleware, as I have done in this chapter to highlight how the pipeline
works, is definitely an option. Alternatively, Katana offers a specialized diagnostic middleware that
can render useful debugging information directly in the browser when an unhandled exception
occurs in the pipeline. Setting it up is super easy.
app.UseErrorPage(new ErrorPageOptions()
{
ShowCookies = true,
ShowEnvironment = true,
ShowQuery = true,
ShowExceptionDetails = true,
ShowHeaders = true,
ShowSourceCode = true,
SourceCodeLineCount = 10
});
The extension method UseErrorPage injects into the pipeline some logic for dumping diagnostic
information on the current page in case an exception is raised in the pipeline. For that reason, it’s
important to place this method at the beginning of the pipeline (otherwise, any exceptions occurring
before it has a chance to fire would not be captured). All the options you see in the call control what
diagnostic information you want to display; the property names are self-explanatory.
If you want to test the contraption, you can artificially raise an exception in any of our debugging
middlewares, and then hit F5 to see what happens. Figure 7-12 shows a typical diagnostic page.
FIGURE 7-12 The page displayed by the diagnostic middleware from Microsoft.Owin.Diagnostics.
154 CHAPTER 7
OpenIdConnectAuthenticationOptions
The options you pass in at initialization are the main way that you control the OpenID Connect
middleware. The Azure AD and ASP.NET teams have taken a lot of care to ensure that only the
absolute minimum amount of information is required for the scenario you want to support. The
sample app you have studied so far shows the essential set of options: the ClientId (which identifies
your app in your requests to the authority) and the Authority (which identifies the trusted source of
identities and, indirectly, all the information necessary to validate the tokens it issues). If you want to
exercise more fine-grained control, you can use the middleware initialization options class to provide
the following:
■ More protocol parameters that define your app and the provider you want to trust.
■ What kind of token requests you want the app to put forth.
■ What logic you want to execute during authentication, choosing from settings offered out of
the box and from custom logic you want to inject.
In this section I describe the most notable categories. Two special properties, Notifications and
TokenValidationParameters, are so important that I’ve dedicated sections to them.
For your reference, Figure 7-13 shows the default values in OpenIdConnectAuthentication-
Options for our app, right after initialization.
Note Parameters in the options class corresponding to OpenID Connect protocol param-
eters have the same name, with the notation adjusted to match .NET naming conventions.
In early iterations, the Active Directory team tried to use the protocol names verbatim—
lowercase, underscore, and all—but the community staged an uprising, and the team
quickly settled on the format you see today.
■ RedirectUri This controls the value of redirect_uri included in the request, correspond-
ing to the route in your app through which you want Azure AD to return the requested token.
As I noted in Chapter 6, if you don’t specify any value, the parameter will be omitted and
Azure AD will pick the one registered at registration time. That’s handy, but you should watch
out for two possible issues. First, you might register multiple redirect_uri values for your
app, in which case Azure AD will choose which one to use in a semirandom fashion (it always
looks like it chooses the first one you registered, but you cannot count on that). Second, if you
are connecting to providers other than Azure AD, they might require the request to comply
with their spec and include a redirect_uri.
This setting is ingested at the time the app is initialized and won’t change later on. In the
section about notifications, you will learn ways of overriding this and other parameters on
the fly in the context of specific requests and responses.
156 CHAPTER 7
Here are a few other parameters that control what’s going to be sent in the request.
■ ResponseType Maps to the OpenID Connect parameter of the same name. Although you
can assign to it any of the values discussed in Chapter 6, only “id_token” and “code id_token”
(the default) lead to the automatic handling of user sign-in. If you want to support other
response types, such as “code”, you need to inject custom code in the notifications described
later in this chapter.
■ Resource In case you are using “code id_token”, you can use this parameter to specify what
resource you want an authorization code for. If you don’t specify anything, the code you get
back from Azure AD will be redeemable for an access token for the Graph API. As mentioned
in Chapter 6, resource is a parameter specific to Azure AD.
Barring any custom code that modifies outgoing messages on the fly, the settings described here
are the ones used in every request and response.
In the new middlewares, the default behavior is to obtain (most of) the validation coordinates by
reference. You provide the authority from which you want to receive tokens, and the middleware
takes care of retrieving the token validation coordinates it needs from the authority’s metadata.
In Chapter 6 you saw how that retrieval operation takes place when you pass an Azure AD
authority. If you want to customize that behavior, there is a hierarchy of options you can use. From
accommodating providers that expose metadata differently from how Azure AD does, to supplying
each and every setting for providers that don’t expose metadata at all, these options cover the full
spectrum.
The ConfigurationManager class is tasked to retrieve, cache, and refresh the validation settings
published by the discovery documents. That class is fed whatever options you provide at initialization.
There is a cascade of options it looks for:
■ If you are working with a provider other than Azure AD, with a different URL structure, or if
you prefer to specify a reference to the actual discovery document endpoint, you can do so by
using the Metadata property.
■ If your provider requires special handling of the channel validation, like picking a well-known
certificate instead of the usual certification authority and subject matching checks, you
can override the default logic via the properties BackchannelCertificateValidator,
BackchannelHttpHandler, and BackchannelTimeout.
■ If you acquire the token-issuance information—such as the authorization endpoint, the issuer
value, the signing keys, and the like—out of band, you can use it to populate a new instance
of OpenIdConfiguration and assign it to the Configuration property.
■ Finally, if you need to run dynamic logic for populating the Configuration values, you can
completely take over by implementing your own IConfigurationManager and assigning it
to the ConfigurationManager property in the options.
The issuer coordinates are only part of the validation story. Following is a miscellany of options
that affect the validation behavior, and there will be more to say about validation in the section about
TokenValidationParameters.
■ CallbackPath If for some reason (typically performance) you decide that you want to
receive tokens only at one specific application URL, you can assign that URL to this property.
That will cause the middleware to expect tokens only in requests to that specific URL and
ignore all others. Use this with care because embedding paths in your code often results in
surprise 401s when you forget about them and deploy to the cloud without changing the
value accordingly.
158 CHAPTER 7
■ AuthenticationType This property identifies this middleware in the pipeline and is used to
refer to it for authentication operations—think of the Challenge and SignOut calls you have
seen in action earlier in this chapter.
■ AuthenticationMode As discussed earlier, when this parameter is set to Active, it tells the
middleware to listen to outgoing 401s and transform them into sign-in requests. That’s the
default behavior: if you want to change it, you can turn it off by setting Authentication-
Mode to Passive.
■ Caption This property has purely cosmetic value. Say that your app generates sign-in
buttons for all your authentication middlewares. This property provides the label you can use
to identify for the user the button triggering the sign-in implemented by this middleware.
Notifications
Just like WIF before them, the Katana middlewares implementing claims protocols offer you hooks
designed for injecting your own custom code to be executed during key phases of the authentica-
tion pipeline. Through the years, I have seen this extensibility point used for achieving all sorts of
customizations, from optimized sign-in flows, where extra information in the request is used to save
the end user a few clicks, to full-blown extensions that support entirely new protocol flavors.
Whereas in old-school WIF those hooks were offered in the form of events, in Katana they are
implemented as a collection of delegates gathered in the class OpenIdConnectNotifications. The
OpenIdConnectAuthenticationOptions class includes a property of that type, Notifications.
OpenIdConnectNotifications can be split into two main categories: notifications firing at sign-
in/sign-out message generation, and notifications firing at token/sign-in message validation. The for-
Here is some code that lists all the notifications. You can add it to the initialization of the OpenID
Connect middleware in the sample application.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
Authority = "https://login.microsoftonline.com/DeveloperTenant.onmicrosoft.com"
PostLogoutRedirectUri = "https://localhost:44300/",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
Debug.WriteLine("*** RedirectToIdentityProvider");
return Task.FromResult(0);
},
MessageReceived = (context) =>
{
Debug.WriteLine("*** MessageReceived");
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
Debug.WriteLine("*** SecurityTokenReceived");
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
Debug.WriteLine("*** SecurityTokenValidated");
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
Debug.WriteLine("*** AuthorizationCodeReceived");
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
Debug.WriteLine("*** AuthenticationFailed");
return Task.FromResult(0);
},
},
}
);
160 CHAPTER 7
This shows that RedirectToIdentityProvider runs in the context of the OpenID Connect
middleware, as expected.
Once you sign in with Azure AD and are redirected to the app, you can expect to see the following
sequence:
This is the same token-processing and cookie-setting sequence you encountered earlier in this
chapter. This time, you can see the other notifications fire and the order in which they execute. Figure
7-14 summarizes the sequence in which the notifications fire.
If you trigger a sign-out, you will see the usual sequence, but look between messages 4 and 5, and
you will find that RedirectToIdentityProvider fires on sign-out as well.
Keep in mind also that notifications derive from a BaseNotification class from which they
inherit a couple of methods exposing two fundamental capabilities. The first, HandleResponse,
signals to the middleware pipeline that whatever logic has been executed in the notification con-
cludes the processing of the current request, hence no other middleware should be executed. A
notification calling this method has the responsibility of having everything in the context tidied up,
including writing the full response. The second, SkipToNextMiddleware, signals to the middleware
pipeline that whatever logic has been executed in the notification concludes the work that the current
middleware should do on the request. Hence, any other request-processing code in the current
middleware should not be executed, and the baton should be passed to the next middleware in the
pipeline as soon as the notification concludes its work.
RedirectToIdentityProvider
This is likely the notification you’ll work with most often. It is executed right after the OpenID Connect
middleware creates a protocol message, and it gives you the opportunity to override the option
values the middleware uses to build the message, augment them with extra parameters, and so on. If
you place a breakpoint in the notification and take a look at the context parameter, you’ll see some-
thing like what’s shown in Figure 7-15.
162 CHAPTER 7
I expanded the ProtocolMessage in Figure 7-15 so that you can see that it already contains all
the default parameters you have seen in the request on the traces in Chapter 6. There are a number
of fun and useful things you can do here, so let’s examine a couple of examples.
Say that my app is registered to run both on my local dev box (hence, on a localhost address) and
on an Azure website (hence, on something like myapp.azurewebsites.net). That means that depending
on where my app is running at the moment, I have to remember to set the correct RedirectUri and
PostLogoutRedirectUri properties in the options right before deploying. Or do I? Consider the
following code:
Here I simply read from the Request the URL being requested, indicating at which address
my app is running at the moment and using it to inject the correct values of RedirectUri and
PostLogoutRedirectUri in the message. Neat!
Or consider a case in which I want to guarantee that when an authentication request is sent, the
user is always forced to enter credentials no matter what session cookies might already be in place.
In Chapter 6 you learned that OpenID Connect will behave that way upon receiving a prompt=login
parameter in the request, but how do you do it? Check out this code:
That’s it. From this moment on, every sign-in request will prompt the user for credentials. Easy.
Now is the time to reap the benefits of having gone through all those nitty-gritty protocol details
in Chapter 6; you can use this notification to control every aspect of the message to your heart’s
content. Of course, this applies to sign-out flows, too.
But before moving on to the next notification, I want to highlight that you don’t have to put the
code for your notifications in line. If you have notification-handling logic you want to reuse across
multiple applications, you can put it in a function, package it in a class, and reuse it as you see fit.
Explicitly creating a function is also indicated when the amount of code is substantial, or when you
want to enhance readability. As a quick demonstration of this approach, let’s rewrite the latest sample
in an explicit function at the level of the Startup class:
//...
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = Startup.RedirectToIdentityProvider,
// ...
I also like the aspect of this approach that makes more visible which parameters are being passed
to the notification, which in turns makes it easier to understand what the notification is suitable for.
164 CHAPTER 7
MessageReceived
This notification is triggered when the middleware detects that the incoming message happens to
be a known OpenID Connect message. You can use it for a variety of purposes; for example, for
resources you want to allocate just in time (such as database connections), stuff you want to cache in
memory before the message is processed further, and so on. Alternatively, you might use this notifi-
cation for logging purposes. However, the main use I have seen for MessageReceived occurs when
you want to completely take over the handling of the entire request (that’s where HandleResponse
comes into play, by the way). For example, you might use MessageReceived for handling response_
types that the middleware currently does not automatically process, like a sign-in flow based on
authorization code. That’s not an easy endeavor, and as such not very common, but some advanced
scenarios will sometimes require it, and this extensibility model makes doing so possible.
SecurityTokenReceived
SecurityTokenReceived triggers when the middleware finds an id_token in the request. Simi-
lar considerations as for MessageReceived apply, with finer granularity. Here, the entity being
processed is the token, as opposed to the entire message.
SecurityTokenValidated
At the stage in which SecurityTokenValidated fires, the incoming id_token has been parsed,
validated, and used to populate context.AuthenticationTicket with a ClaimsIdentity whose
claims come from the incoming token.
This is the right place for adding any user-driven logic you want to execute before reaching the
application itself. Common scenarios include user-driven access control and claims augmentation.
Here are examples for each case.
Say that I run a courseware website where users can buy individual subscriptions for gaining access
to training videos. I integrate with Azure AD, given that business users are very important to me, but
my business model imposes on me the need to verify access at the user level. That means that the
token validations you have studied so far aren’t in themselves sufficient to decide whether a caller can
gain access. Consider the following implementation of SecurityTokenValidated:
Consider this other scenario. Say that your application maintains a database of attributes for its
users—attributes that are not supplied in the incoming token by the identity provider. You can use
SecurityTokenValidated to augment the set of incoming user claims with any arbitrary value you
keep in your local database. The application code will be able to access those values just like any other
IdP-issued claims, the only difference being the issuer value. Here’s an example.
Here I assume that you have a method that, given the identifier of the current user, queries your
database to retrieve an attribute (in this case, hair length). Once you get the value back, you can use
it to create a new claim (I invented a new claim type on the spot to show you that you can choose
pretty much anything that works for you) and add that claim to the AuthenticationTicket’s
ClaimsIdentity. I passed “’LocalAuthority” as the issuer identifier to ensure that the locally gener-
ated claims are distinguishable from the ones received from the IdP: the two usually carry a different
trust level.
Now that the new claim is part of the ticket, it’s going to follow the same journey we have studied
so far for normal, nonaugmented identity information. Making use of it from the app requires the
same code you already saw in action for out-of-the-box claim types.
This is a very powerful mechanism, but it does have its costs. Besides the performance hit of
doing I/O while processing a request, you have to keep in mind that whatever you add to the
AuthenticationTicket will end up in the session cookie. In turn, that will add a tax for every
subsequent request, and at times it might even blow past browser limits. For example, Safari is
famous for allowing only 4 KB of cookies/headers in requests for a given domain. Exceed that limit
and cookies will be clipped, signature checks will fail, nonces will be dropped, and all sorts of other
hard-to-diagnose issues will arise.
166 CHAPTER 7
AuthenticationFailed
This notification gives you a way to catch issues occurring in the notifications pipeline and react to
them with your own logic. Here’s a simple example:
In this code I simply redirect the flow to an error route. Chances are you will want to do some-
thing more sophisticated, like retrieving the culprit exception (available in the context) and then log
it or pass it to the page. The interesting thing to notice here is the use of HandleResponse. There’s
nothing else that can make meaningful work in the pipeline after this, hence we short-circuit the
request processing and send the response back right away.
TokenValidationParameters
You think we’ve gone deep enough to this point? Not quite, my dear reader. The rabbit hole has one
extra level, which grants you even more control over your token-validation strategy.
The TokenValidationParameters type predates the RTM of Katana. It was introduced when
the Azure AD team released the very first version of the JWT handler (a .NET class for processing the
JWT format) as a general-purpose mechanism for storing information required for validating a token,
regardless of the protocol used for requesting and delivering it and the development stack used for
supporting such protocol. That was a clean break with the past: up to that moment, the same function
was performed by special XML elements in the web.config file, which assumed the use of WIF and IIS.
It was soon generalized to support the SAML token format, too.
The OpenID Connect middleware itself still uses the JWT handler when it comes to validating
incoming tokens, and to do so it has to feed it a TokenValidationParameters instance with the
desired validation settings. All the metadata inspection mechanisms you have been studying so far
ultimately feed specific values—the issuer values to accept and the signing keys to use for validating
incoming tokens’ signatures—in a TokenValidationParameters instance. If you did not provide
any values in the TokenValidationParameters property (I know, it’s confusing) in the options, the
The preceding mechanisms hold for the validation of the parameters defining the token issuer, but
as you know by now, there are lots of other things to validate in a token, and even more things that
are best performed during validation. If you don’t specify anything, as is the case the vast majority
of the time, the middleware fills in the blanks with reasonable defaults. But if you choose to, you can
control an insane number of details. Figure 7-16 shows the content of TokenValidationParameters
in OpenID Connect middleware at the initialization time for our sample application. I am not going to
unearth all the things that TokenValidationParameters allows you to control (that would take far
too long), but I do want to make sure you are aware of the most commonly used knobs you can turn.
Valid values
As you’ve learned, the main values used to validate incoming tokens are the issuer, the audience,
the key used for signing, and the validity interval. With the exception of the last of these (which
does not require reference values because it is compared against the current clock values), Token-
ValidationParameters exposes a property for holding the corresponding value: ValidIssuer,
ValidAudience, and IssuerSigningKey.
168 CHAPTER 7
Validation flags
One large category of TokenValidationParameters properties allows you to turn on and off
specific validation checks. These Boolean flags are self-explanatory: ValidateAudience turns on and
off the comparison of the audience in the incoming claim with the declared audience (in the OpenID
Connect case, the clientId value); ValidateIssuer controls whether your app cares about the
identity of the issuer; ValidateIssuerSigningKey determines whether you need the key used to
sign the incoming token to be part of a list of trusted keys; ValidateLifetime determines whether
you will enforce the validity interval declared in the token or ignore it.
At first glance, each of these checks sounds like something you’d never want to turn off, but
there are various occasions in which you’d want to. Think of the subscription sample I described
for SecurityTokenValidated: in that case, the actual check is the one against the user and the
subscription database, so the issuer check does not matter and can be turned off. There are more
exotic cases: in the Netherlands last year, a gentleman asked me how his intranet app could accept
expired tokens in case his client briefly lost connectivity with the Internet and was temporarily unable
to contact Azure AD for getting new tokens.
There is another category of flags controlling constraints rather than validation flags. The first
is RequireExpirationTime, which determines whether your app will accept tokens that do not
declare an expiration time (the specification allows for this). The other, RequireSignedTokens,
specifies whether your app will accept tokens without a signature. To me, a token without a signature
is an oxymoron, but I did encounter situations (especially during development) where this flag came
in handy for running some tests.
Validators
Validation flags allow you to turn on and off validation checks. Validator delegates allow you to
substitute the default validation logic with your own custom code.
Say that you wrote a SaaS application that you plan to sell to organizations instead of to individu-
als. As opposed to the user-based validation you studied earlier, now you want to allow access to any
user who comes from one of the organizations (one of the issuers) who bought a subscription to your
app. You could use the ValidIssuers property to hold that list, but if you plan to have a substantial
number of customers, doing that would be inconvenient for various reasons: a flat lookup on a list
might not work too well if you are handling millions of entries, dynamically extending that list with-
out recycling the app would be difficult, and so on. The solution is to take full control of the issuer
validation operation. For example, consider the following code:
The delegate accepts as input the issuer value as extracted from the token, the token itself, and the
validation parameters. In this case I do a flat lookup on a database to see whether the incoming issuer
is valid, but of course you can imagine many other clever validation schemes. The validator returns
the issuer value for a less-than-intuitive reason: that string will be used for populating the Issuer
value of the claims that will ultimately end up in the user’s ClaimsPrincipal.
All the other main validators (AudienceValidator, LifetimeValidator) return Booleans, with
the exception of IssuerSigningKeyValidator and CertificateValidator.
Miscellany
Of the plethora of remaining properties, I want to point your attention to two common ones.
SaveSignInToken is used to indicate whether you want to save in the ClaimsPrincipal (hence,
the session cookie) the actual bits of the original token. There are topologies in which the actual token
bits are required, signature and everything else intact: typically, the app trades that token (along with
its credentials) for a new token, meant to allow the app to gain access to a web API acting on behalf
of the user. This property defaults to false, as this is a sizable tax.
The TokenReplayCache property allows you to define a token replay cache, a store that can be
used for saving tokens for the purpose of verifying that no token can be used more than once. This is
a measure against a common attack, the aptly called token replay attack: an attacker intercepting the
token sent at sign-in might try to send it to the app again (“replay” it) for establishing a new session.
The presence of the nonce in OpenID Connect can limit but not fully eliminate the circumstances in
which the attack can be successfully enacted. To protect your app, you can provide an implemen-
tation of ITokenReplayCache and assign an instance to TokenReplayCache. It’s a very simple
interface:
170 CHAPTER 7
More on sessions
Before I close this long chapter, I need to spend a minute on session management. You already
know that by default, session validity will be tied to the validity specified by the token itself, unless
you decouple it by setting the option UseTokenLifetime to false. When you do so, the Cookie-
AuthenticationOptions are now in charge of session duration: ExpireTimeSpan and Sliding-
Expiration are the properties you want to keep an eye on.
You also know that the cookie middleware will craft sessions that contain the full
ClaimsPrincipal produced from the incoming token, but as mentioned in discussing the use of
SaveSignInToken, the resulting cookie size can become a problem. This issue can be addressed
by saving the bulk of the session server-side and using the cookie just to keep track of a reference
to the session data on the server. The cookie middleware allows you to plug in an implementa-
tion of the IAuthenticationSessionStore interface, which can be used for customizing how an
AuthenticationTicket is preserved across calls. If you want to provide an alternative store for
your authentication tickets, all you need to do is implement that interface and pass an instance to
the cookie middleware at initialization. Here’s the interface:
That’s pretty much a CRUD interface for an AuthenticationTicket store, which you can use for
any persistence technology you like. Add some logic for cleaning up old entries and keeping the store
size under control, and you have your custom session store.
Considerations about I/O and latency are critical here, given that this guy will trigger every single
time you receive an authenticated request. A two-level cache, where most accesses are in-memory
and the persistence layer is looked up only when necessary, is one of the solutions you might want to
consider.
The complexity you have confronted here is something that the vast majority of web developers
will never have to face—or even be aware of. Even in advanced cases, chances are that you will always
use a subset of what you have read here. Don’t worry if you don’t remember everything; you don’t
have to. After the first read, this chapter is meant to be a reference you can return to whenever you
are trying to achieve a specific customization or are troubleshooting a specific issue. Now that you’ve
had an opportunity to deconstruct the pipeline, you’ll know where to look.
The next chapter will be significantly lighter. You’ll learn more about how Azure AD represents
applications.
172 CHAPTER 7
It’s time to take a closer look at how Azure AD represents applications and their relationships to other
apps, users, and organizations.
You got a brief taste of the Azure AD application model in Chapter 3, “Introducing Azure Active
Directory and Active Directory Federation Services.” Later on you experienced firsthand a couple of
ways to provision apps and use their protocol coordinates in authentication flows. Here I will go much
deeper into the constructs used by Azure AD to represent apps, the mechanisms used to provision
apps beyond one’s own organization, and the consent framework, which is the backbone of pretty
much all of this. I’ll also touch on roles, groups, and other features that Azure AD offers to grant
fine-grained access control to your application.
■ It holds all the data for deciding what other resources an application might need to access and
whether a given request should be fulfilled and under what circumstances.
■ It provides the infrastructure for implementing application provisioning, both within the app
developer’s tenant and to any other Azure AD tenant.
■ It enables end users and administrators to dynamically grant or deny consent for the app to
access resources on their behalf.
■ It enables administrators to be the ultimate arbiters of what apps are allowed to do and which
users can use specific apps, and in general to be stewards of how the directory resources are
accessed.
That is A LOT more than setting up a trust relationship, the basic provisioning step you perform
with traditional on-premises authorities like ADFS. Remember how I often bragged about how much
easier it is to provision apps in Azure AD? What makes that feat possible is the highly sophisticated
application model in Azure AD, which goes to great lengths to make life easy for administrators and
end users. Unfortunately, the total complexity of the system remains roughly constant, so somebody
must work harder to compensate for that simplification, and this time that somebody is the devel-
oper. I could work around that complexity and simply give you a list of recipes to follow to the letter
for the most common tasks, but by now you know that this book doesn’t work that way. Instead, we’ll
173
In traditional Active Directory, every entity that can be authenticated is represented by a principal.
That’s true for users, and that’s true for applications—in the latter case, we speak of service principals.
In traditional Kerberos, service principals are used to ensure that a client is speaking to the intended
service and that a ticket is actually intended for a given service. In other words, they are used for any
activity that requires establishing the identity of the service application itself.
Although Azure AD has been designed from the ground up to address modern workloads, it re-
mains a directory. As such, it retains many of the concepts and constructs that power its on-premises
ancestor, and service principals are among those. If you use the Internet time machine and fish out
content from summer 2012, describing the very first preview of Azure AD development features,
you’ll see that at that time, provisioning an application in Azure AD was done by using special
Windows PowerShell cmdlets, which created a new service principal for the app in the directory.
Even the format of the service principal name was a reminder of its Kerberos legacy, following a fixed
schema based on the app’s execution environment. Disregarding the protocols it enabled, that service
principal already had all the things we know are needed for supporting authentication transactions:
application identifiers, a redirect URI, and so on.
Service principals are a great way of representing an application’s instance, but they aren’t very
good at supporting the development of the application itself. Their limitations stem from two key
considerations:
■ Applications are usually an abstract entity, made of code and resources: the service principal
represents a concrete instance of that abstract entity in a specific directory. You will want
that abstract entity to have many concrete instances, especially if you build and sell software
for a living: one or more instances for each of your customers’ organizations. Even if you are
building applications for your own organization, to be used by your colleagues, chances are
that you’ll want to work with multiple instances—for example, development, staging, and
production. If the only building block at your disposal were app instances, development and
deployments would be unnatural, denormalized, and repetitive. For one thing, every time you
changed something, you’d have to go chase all your app instances and make the same change
everywhere.
174 CHAPTER 8
Given this, and for various other reasons, Azure AD defines a new entity, the Application,
which is meant to describe an application as an abstract entity: a template, if you will. As a devel-
oper, you work with Applications. At deployment time a given Application object can be used
as a blueprint to create a ServicePrincipal representing a concrete instance of an application in
a directory. It’s that ServicePrincipal that is used to define what the app can actually do in that
specific target directory, who can use it, what resources it has access to, and so on.
Bear with me just a little longer, the abstract part is almost over. The main way through which
Azure AD creates a ServicePrincipal from an Application is consent. Here’s a simplified
description of the flow: Say that you create an Application object in directory A, supplying all
the protocol coordinates we’ve discussed so far in earlier chapters. Say that a user from tenant B
navigates to the app’s pages and triggers an authentication flow. Azure AD authenticates the user
from B against its home directory, B. In so doing, it sees that there is no ServicePrincipal for
the app in B; hence, it prompts the user about whether he or she wants to consent for that app to
have access to the directory B (you’ll see later in what capacity). If the user grants consent, Azure AD
uses the Application object in A as a blueprint for creating a ServicePrincipal in B. Along with
that, B records that the current user consented to the use of this application (expect lots of details
on this later on). Once that’s done, the user receives a token for accessing the app . . . and provision-
ing magically happens. No lengthy negotiations between administrators required. Isn’t Azure AD
awesome? Figure 8-1 summarizes the process.
You can iterate the process shown in Figure 8-1 as many times as you want, for directory C, D, E,
and so on. Directory A retains the blueprint of the app, in the form of its Application object. The
users and admins of all the directories where the app is given consent retain control over what the
application is allowed to do (and a lot more) through the corresponding ServicePrincipal object
in each tenant.
A special case: App creation via the Azure portal and Visual Studio
As I write, both of the application provisioning techniques you’ve experienced so far (using the
Azure portal and using Visual Studio) assume that you want to run your application in the same
tenant in which you are creating it. Hence, these techniques create both the Application and
the ServicePrincipal objects. The presence of a ServicePrincipal right after creation
time in the home tenant will cause differences in behavior in respect to what happens when the
application is consumed through different tenants. That is especially true for native applications,
which are out of scope for this book, but in general this is something you need to be aware of.
Note that the current behavior is not set in stone and not part of any explicit contract. I cannot
guarantee that it will not change after this book goes to the printer.
In the next two subsections, you’ll take a look at the content of the Application and
ServicePrincipal objects. This will give me an opportunity to introduce lots of new directory
artifacts, which in turn will refine your understanding of what an application is for Azure AD and what
it can do for you.
176 CHAPTER 8
The Application
The Application object in Azure AD is meant to describe three distinct aspects of an application:
■ The identifiers, protocol coordinates, and authentication options that come into play when a
token is requested for accessing the application.
■ The resources that the application itself might need to access, and the actions it might need to
take, in order to perform its functions. For example, an application might need to write back
to the directory, or it might need to send email via Exchange as the authenticated user. You’ll
have to wait until the next chapter to learn how to actually perform these actions in code,
but it’s important to understand in this context the provisioning and consent mechanisms
underpinning this aspect.
■ The actions that the application itself offers. For example, an application representing a
facade for a data store might allow for read and write operations—and make it possible for
the directory to decide whether to grant a client permission to do only read operations, or
both read and write, depending on the identity of the client. This feature is used when the
application is a web API, but it rarely comes into play when doing web sign-on, so I won’t
spend much time on it in this chapter.
So far you’ve acted directly only on the first aspect. You indirectly took advantage of the defaults
in the second point—every web app is configured to ask for permissions to sign in and access the
user’s profile. You have not interacted with the third aspect yet, but you will in Chapter 9, “Consuming
and exposing a web API protected by Azure Active Directory.”
Mercifully, neither the Azure portal or the Visual Studio ASP.NET project templates wizards ask you
to provide values for all the properties that constitute an Application object. The vast majority of
those properties are assigned default values that work great for most of the populace, who can get
their web sign-on functionality by providing just a handful of strings (as you have seen, mainly name
and redirect_uri) without ever being aware that there are customizations available.
That said, if you do want to know what’s available in the Application object, how would you go
about it? You have three strategies to choose from:
The information shown there is what you would probably customize to meet the requirements
of the most common scenarios. However, not all the application features are exposed there.
■ Still in the Azure portal, with your app selected, you can use a link at the bottom of the page,
Manage Manifest, to download a JSON file that contains the verbatim dump of the corre-
sponding Application entity in the directory. You can edit this file to change whatever you
want to control, then upload it again (through the same portal commands) to reflect your new
options in the directory.
■ Finally, you can use the Directory Graph API (mentioned in Chapter 3) to query the directory
and GET the Application object, once again in JSON format.
The first method goes against the policy I am adopting in the book—the portal UX can change far
too easily after the book is in print, so including screenshots of it would be a bad idea. Also, it does
not go nearly deep enough for my purposes here.
The second method, the manifest, would work out well—and is the method I advise you to use
when you work with your applications. However, there is something that makes it less suitable for
explaining the anatomy of the Application object for the first time: the manifest is a true object
dump from the directory, and for pure inheritance reasons it includes lots of properties that aren’t
useful or relevant for the Application itself.
To keep the signal-to-noise ratio as crisp as possible, the JSON snippets I’ll show you here will all
be obtained through the third method, direct queries through the Graph. I am using a very handy
sample web app (which you can find at https://graphexplorer.cloudapp.net), which provides an easy
UI for querying the graph. I cannot guarantee that the app will still be available when you read this
book, but performing those queries through code, or with curl or via Fiddler, is extremely easy. In the
next chapter you’ll learn how.
Following is a dump of the Application object that corresponds to the sample app we’ve been
working with so far. The query I used for obtaining it is as follows:
https://graph.windows.net/developertenant.onmicrosoft.com/
applications?$filter=appId+eq+'e8040965-f52a-4494-96ab-0ef07b591e3f'&api-version=1.5
You’ll likely recognize the typical OData ‘$’ syntax. The GUID you see there is the client_id of the
application. Here’s the complete JSON from the result:
{
"odata.metadata": "https://graph.windows.net/developertenant.onmicrosoft.
com/$metadata#directoryObjects/Microsoft.DirectoryServices.Application",
"value": [
{
"odata.type": "Microsoft.DirectoryServices.Application",
"objectType": "Application",
178 CHAPTER 8
Feel free to ignore anything that starts with “odata” here. Also, some properties listed are for
internal use only or are about to be deprecated, so I won’t talk about those.
■ objectId is the unique identifier for this Application entry in the directory. Note, this is
not the identifier used to identify the app in any protocol transaction—you can think of it as
the ID of the row where the Application object is saved in the directory store. It is used for
referencing the object in most directory queries and in cross-entity references.
■ deletionTimestamp is always null, unless you delete the Application, which in that case it
records the instant in which you do so. Azure AD implements most eliminations as soft deletes
so that you can repent and restore the object without too much pain should you realize the
deletion was a mistake.
■ replyUrls This multivalue property holds the list of registered redirect_uri values that
Azure AD will accept as destinations when returning tokens. No other URI will be accepted.
This property is the source of some of the most common errors: even the smallest mismatch
(trailing slash missing, different casing) will cause the token-issuance operation to fail.
Although at creation time the only URL in the collection is the one you specified, as is the case
with the localhost-based URL in the sample here, you’ll often find yourself adding more URLs
180 CHAPTER 8
These values are used to represent the application as a resource in protocols such as SAML
and WS-Federation, where they map to the concept of realm. The URIs are also used as
audience in access tokens issued for the app via OAuth2, when the app is consumed as a
web API (as opposed to a web app with an HTML UX). This often generates confusion,
given that this scenario can also be implemented by using the app’s client_id instead of
one identifier URI. More about this in Chapter 9.
■ publicClient In the current Azure AD model, applications can be either confidential clients
(apps that can have their own credentials, usually run on servers, etc.) or public clients (mobile
and native apps running on devices, with no credentials, hence no strong identity of their
own). The security characteristics of the two app types are very different, and so is the set of
protocols that the two types support. For example, a native client cannot obtain a token purely
with its app identity because it has no identity of its own; and a confidential client cannot
request tokens with user-only flows, where the identity of the app would not play a role.
This book focuses on web apps; hence, confidential clients. That means that the apps dis-
cussed here will always have the publicClient property set to null.
■ displayName This property determines how the application is called in interactions with
end users, such as consent prompts. It’s also the mnemonic moniker used to indicate the
application for the developer in the Azure management portal. Given that the display name
has no uniqueness requirements, it’s not always a way to conclusively identify one app in a
long list.
■ Homepage The URL saved here is used to point to the application from its entry in applica-
tion portals such as the Office 365 application store. It does not play any role in the protocol
behavior of the app; it’s just whatever landing page you want visitors, prospective buyers, and
corporate users (who might get there through the list of applications their company uses) to
use as an entry point. At creation time, the Homepage value is copied from the replyUrls
property. A common bit of advice to software developers from Office is to ensure that the URL
in Homepage corresponds to a protected page/route in your application so that if visitors are
already authenticated when they click the link, they’ll find themselves authenticated with the
same identity in your app as well.
■ appRoles This property is used for declaring roles associated with the application. I provide
a complete explanation of this property in later sections of this chapter.
However, that clearly does not work if your intent is to make the application available across
organizations: that is the case for SaaS scenarios, naturally. If you are in that situation, you
can flip availableToOtherTenants to true. That will make Azure AD allow requests from
other tenants to trigger the consent flow I described briefly earlier instead of carrying out the
default behavior, in which the request would be rejected right away.
Applications available across tenants (what we commonly call “multitenant apps”) have
extra constraints. For example, whereas identifierURIs can normally be any URI with
182 CHAPTER 8
Note Flipping this switch only tells Azure AD that you want your app to behave
as a multitenant app. Actually promoting one application from line of business to
multitenant requires some coding changes, which I’ll discuss later on.
■ knownClientApplications The last property listed here is also about provisioning. You
have seen how consenting for one application to have access to your own directory results
in the creation of a ServicePrincipal for the app in the target directory. To anticipate
a bit the upcoming discussion on permissions, the idea is that the ServicePrincipal
will also need to record the list of resources and actions on those resources that the user
consented to. This is possible only if the requested resources are already present with their
own ServicePrincipal entries in the target directory. That is usually the case for first-party
resources: if your app needs access to the Directory Graph or Exchange online, you can expect
those to already have an entry in the directory. It will occasionally happen that your solution
includes both a client application and one custom web API application. You’ll want your
prospective customers to have to consent only once, when they first try to get a token for the
client application. If consent can happen only when all the requested resources are present as
a ServicePrincipal in the target directory, and one of the resources you need is your own
API, you have a problem. It looks like you have to ask your user to first consent to the web API
so that it can create its ServicePrincipal in the target directory, and only after that ask the
user to go back and consent to the client application.
Well, this property exists to save you from having to do all that work. Say that the application
you are working on is the web API project. If you save in knownClientApplications the
client_id (the appId, that is) of the client application you want to use for accessing your API,
Azure AD will know that consenting to the client means implicitly consenting to the web API,
too, and will automatically provision ServicePrincipals for both the client and web API at
the same time, with a single consent. Handy!
The main catch in all this is that both the client and the web API application must be defined
within the same tenant. You cannot list in knownClientApplications the client_id of a
client defined in a different tenant.
{
"adminConsentDescription": "Allow the application to access WebAppChapter5 on behalf of the
signed-in user.",
"adminConsentDisplayName": "Access WebAppChapter5",
"id": "00431d04-5334-4da6-8396-0e6f54631f10",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allow the application to access WebAppChapter5 on your behalf.",
"userConsentDisplayName": "Access WebAppChapter5",
"value": "user_impersonation"
}
Each property ending in “description” or “name” indicates how to identify and describe this
permission in the context of interactive operations, such as consent prompts or Application
configuration at development time.
The type property indicates whether this permission can be granted by any user in the directory
(in which case it is populated with the value User) or is a high-value capability that can be granted
only by an administrator (in which case, the value is Admin).
184 CHAPTER 8
"requiredResourceAccess": [
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
}
]
}
■ Which specific permissions it is after, via the resourceAccess collection, which contains
• The permission ID—the same ID the resource declared (in its own Application object) for
the permission in its own corresponding oauth2Permission entry
• The type of access it intends to perform: possible values are Scope and Role.
In our sample, the resource we need access to is the directory itself, in the form of the Graph API—
the identifier 00000002-0000-0000-c000-000000000000 is reserved for the Graph in all tenants. The
permission we are requesting, of ID 311a71cc-e848-46a1-bdf8-97ff7156d8e6, corresponds to “sign
in and access the user’s profile.” I know it doesn’t sound that easy to remember . . . but it is not sup-
posed to be. The Azure portal or the Visual Studio project wizards normally take care of putting those
values there for you when you select the human-readable counterparts in their UIs.
The type of access Scope determines that the app request the permission in delegated fashion;
that is to say, as the identity of the user who’s doing the request. Whether an admin user is required
for successfully obtaining this permission at run time, or a normal user can suffice, is determined by
the Type declared in the corresponding oauth2Permission entry—found in the Application
object of the resource exposing the permission. As you have seen in the preceding section, the
A requiredResourceAccess entry with a Type of value Role indicates that the application
requires that permission with its own application identity, regardless of which user identity is used to
obtain the token (if any—there are ways for an app to get tokens with no users involved, and I’ll talk
about that in the “Application permissions” section toward the end of this chapter). This option does
require consent from an administrator.
Now here is a super important concept; put everything else down and read very carefully. In the
current Azure AD model, one application must declare in advance all resources it needs access to, and
all the associated permissions it requires. At the first request for a token for that app, that list will be
presented to the user in its entirety, regardless of what resources are actually needed for that specific
request. Once the user successfully grants consent, a ServicePrincipal will be provisioned, and
that consent will be recorded in the target directory (you’ll see later how that happens in practice) for
all the requested resources. This makes it possible to prompt the user for consent only once.
The side effect of this approach is that the list of consented permissions is static. If you decide to
add a new permission request to your application after a customer of yours already consented to it
in its own directory, your customer will not be able to obtain the new permission for your app in the
customer’s own tenant until he or she revokes consent in its entirety and then grants it again. This can
sometimes be painful. In version 2 of Azure AD, we are working hard to eliminate this constraint, but
in version 1, that is the way it is today.
Figure 8-2 summarizes the main functional groups the Application object’s properties fall into.
Sure, there are a lot of details to keep in mind, but at the end of the day, more often than not, this
simple subdivision will help you to ignore the noise and zero in on the properties you need for your
scenario.
FIGURE 8-2 A functional grouping of the properties of the Application object in Azure AD.
186 CHAPTER 8
Following is the ServicePrincipal for our sample app. It is deployed on the same tenant as the
Application, but for our analysis that doesn’t matter. At first glance, it does look a lot like the
Application itself, but it is in fact quite different.
{
"odata.type": "Microsoft.DirectoryServices.ServicePrincipal",
"objectType": "ServicePrincipal",
"objectId": "29f565fd-0889-43ff-aa7f-3e7c37fd95b4",
"deletionTimestamp": null,
"accountEnabled": true,
"appDisplayName": "WebAppChapter5",
"appId": "e8040965-f52a-4494-96ab-0ef07b591e3f",
"appOwnerTenantId": "6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e",
"appRoleAssignmentRequired": false,
"appRoles": [],
"displayName": "WebAppChapter5",
"errorUrl": null,
"homepage": "https://localhost:44300/",
"keyCredentials": [],
"logoutUrl": null,
"oauth2Permissions": [
{
"adminConsentDescription": "Allow the application to access WebAppChapter5 on behalf
of the signed-in user.",
"adminConsentDisplayName": "Access WebAppChapter5",
"id": "00431d04-5334-4da6-8396-0e6f54631f10",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allow the application to access WebAppChapter5 on your
behalf.",
"userConsentDisplayName": "Access WebAppChapter5",
"value": "user_impersonation"
}
],
"passwordCredentials": [],
"preferredTokenSigningKeyThumbprint": null,
"publisherName": "Developer Tenant",
"replyUrls": [],
"samlMetadataUrl": null,
"servicePrincipalNames": [
"https://localhost:44300/WebProjectChapter5",
"e8040965-f52a-4494-96ab-0ef07b591e3f"
],
"tags": [
"WindowsAzureActiveDirectoryIntegratedApp"
]
}
Notably missing are all the flags determining protocol behaviors at run time: availableTo-
OtherTenants, groupMembershipClaims, oauth2AllowImplicitFlow, oauth2AllowUrlPath-
Matching, oauth2-RequirePostResponse, and publicClient. Other properties that don’t di-
rectly make it in the form of properties in ServicePrincipal are knownClientApplications and
requiredResourceAccess, both of which are properties that influence the consent process and the
very creation of this ServicePrincipal. As you will see later on, requiredResourceAccess gets
recorded in a different form—one that makes it easier for the directory to track down who in the ten-
ant has actually been granted the necessary permissions to use the app.
■ appOwnerTenantId This property carries the tenantId of the tenant where you’ll find the
Application object that was used as a blueprint for creating this ServicePrincipal—in
this case, developertenant.onmicrosoft.com. If you search Chapter 6 for the GUID value shown
in our example’s ServicePrincipal, you’ll find it everywhere.
■ publisherName Another property meant to be used for describing the app in user
interactions, publisherName stores the display name of the tenant where the original
Application was defined. This represents the organization that published the app.
■ servicePrincipalNames This property holds all the identifiers that can be used for
referring to this application in protocol flows: as you might have noticed in the sample, it
contains the union of the values in the identifierUris collection and the appId value from
the Application object. The former is used for OAuth2 and OpenID Connect flows, the latter
for WS-Federation, SAML, or OAuth2 bearer token resource access requests.
■ tags This property is used mostly by the Azure portal to determine the type of application
and how to present it in the administrative interface. Without going into fine detail, an empty
tag collection results in the corresponding ServicePrincipal not being shown as one of the
resources that can be requested by other applications.
188 CHAPTER 8
You have learned that all it takes for provisioning an app in a tenant (creating a ServicePrincipal
for that app in the tenant) is one user requesting a token by using the app coordinates defined in the
Application object, successfully authenticating, and granting to the app consent to the permissions
it requires. To get to the next level of detail, you must take into account what kind of user created
the application in the first place, what permissions the applications requires, and what kind of user
actually grants consent to the app and in what terms. There is an underlying rule governing the entire
process, but that’s pretty complicated. Instead of enunciating it here and letting you wrestle with it,
I am going to walk you through various common scenarios and explain what happens. Little by little,
we’ll figure out how things work.
Initially, I’ll scope things down to the case in which you are creating line-of-business apps—
applications meant to be consumed by users from the same directory in which they were created. If
your company has an IT department that creates apps for your company’s employees, you know what
kind of apps I am referring to. Once you have a solid understanding of how consent works within a
single directory, I’ll venture to the multitenant case, where you’ll see more of the provisioning aspect.
I’ll stick to delegated permissions, but there are other kinds of permissions, like the things that an app
can do independently of which user is signed in at the moment, but I’ll defer coverage of those and
describe the basics here.
As you have seen in the preceding section, creating one app via the Azure portal has the effect
of creating both the Application object and the corresponding ServicePrincipal. What
you haven’t seen yet is how the directory remembers what permissions have been granted to the
ServicePrincipal and to which user. The Application object enumerates the permissions
it needs in the requiredResourceAccess collection, but those aren’t present in the Service-
Principal. Where are they?
{
"odata.metadata": "https://graph.windows.net/developertenant.onmicrosoft.com/$metadata#oauth2
PermissionGrants",
"value": [
{
"clientId": "29f565fd-0889-43ff-aa7f-3e7c37fd95b4",
"consentType": "Principal",
"expiryTime": "2015-11-21T23:31:32.6645924",
"objectId": "_WX1KYkI_0Oqfz58N_2VtEnIMYJNhOpOkFrsIuF86Y8",
"principalId": "13d3104a-6891-45d2-a4be-82581a8e465b",
"resourceId": "8231c849-844d-4eea-905a-ec22e17ce98f",
"scope": "UserProfile.Read",
"startTime": "0001-01-01T00:00:00"
}
]
}
Note The query I used for retrieving this result was https://graph.windows.net/
developertenant.onmicrosoft.com/oauth2PermissionGrants?$filter=clientId+eq
+'29f565fd-0889-43ff-aa7f-3e7c37fd95b4'.
Let’s translate that snippet into English. It says that the User with identifier 13d3104a-6891-
45d2-a4be-82581a8e465b (the PrincipalId) consented for the client 29f565fd-0889-43ff-aa7f-
3e7c37fd95b4 (the clientId) to access the resource 8231c849-844d-4eea-905a-ec22e17ce98f (the
resourceId) with permission UserProfile.Read (the scope). Resolving references further, the
client is our sample app, and the resource is the directory itself—more precisely, the Directory Graph
API. Figure 8-3 shows how the consent for the first application user is recorded in the directory;
Figure 8-4 shows how the oauth2PermissionGrants table grows as more users give their consent.
Important All the identifiers here refer to the objectId property of the respective
entity they refer to. Given that clientId and resourceId ultimately refer to
ServicePrincipals, it’s easy to get confused and expect those values to represent the
appId. But nope, it’s the objectId. The principalId is the objectId property of the
User object representing the user account used for consenting.
190 CHAPTER 8
When Azure AD receives a request for a token to be issued to the application defined here, it looks
in the oauth2PermissionGrants collection for entries whose clientId matches the app. If the
authenticated user has a corresponding entry, she or he will get back a token right away. If there’s
no entry, the user will see the consent prompt listing all the requiredResourceAccess permissions
from the Application object. Upon successful consent, a new oauth2PermissionGrant entry for
the current user will be created to record the consent. And so on and so forth.
If you want to try, go ahead and launch the sample app again, but sign in as another user. This
time, you will be presented with the consent page. Consent and then sign out. Sign in again with
the new user: you will not be prompted for consent again. If you queried the directory (in the next
chapter you’ll learn how) to find all the oauth2PermissionGrants whose clientId matches
the sample app, you’d see that there are now two entries, looking very much alike apart from the
principalId, which would point to different users. Note that it doesn’t matter whether your second
user is an administrator or a low-privilege user; the resulting oauth2PermissionGrant will look just
like the one described earlier when following this flow.
192 CHAPTER 8
As of today, the directory itself is represented by a ServicePrincipal in your tenant. You already
know both the AppId and the ObjectId of that principal, given that our sample app had to request
at least the permission UserProfile.Read in order to sign users in. The AppId, 00000002-0000-
0000-c000-000000000000, comes from the requiredResourceAccess in the Application object
representing our sample. The ObjectID of the ServicePrincipal, 8231c849-844d-4eea-905a-
ec22e17ce98f, comes from the oauth2PermissionGrant tracking the consent to our sample. The
objectId is enough for crafting the resource URL referring to the Graph API ServicePrincipal: it’s
https://graph.windows.net/developertenant.onmicrosoft.com/servicePrincipals/8231c849-844d-4eea-
905a-ec22e17ce98f.
I won’t show the entire JSON for the ServicePrincipal here, as it contains a lot of stuff I want to
cover later. But take a look at the oauth2Permissions, the collection of delegated permissions one
client can request for interacting with the directory:
"oauth2Permissions": [
{
"adminConsentDescription": "Allows the app to create groups on behalf of the signed-in
user and read all group properties and memberships. Additionally, this allows the app to update
group properties and memberships for the groups the signed-in user owns.",
"adminConsentDisplayName": "Read and write all groups",
"id": "970d6fa6-214a-4a9b-8513-08fad511e2fd",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allows the app to create groups on your behalf and read all
group properties and memberships. Additionally, this allows the app to update group properties
and memberships for groups you own.",
"userConsentDisplayName": "Read and write all groups",
"value": "Group.ReadWrite.All" },
{
"adminConsentDescription": "Allows the app to read basic group properties and memberships
on behalf of the signed-in user.",
"adminConsentDisplayName": "Read all groups",
"id": "6234d376-f627-4f0f-90e0-dff25c5211a3"
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allows the app to read all group properties and memberships on
your behalf.",
194 CHAPTER 8
Here’s a quick description of each delegated permission listed, per their Value property. Please
note that this list does change over time. Funny story: it changed a couple of weeks after I finished
writing this chapter—I had to come back and revise much of what follows. In fact, the change is not
fully complete, as the ServicePrincipal object shown above still shows some old values. The first
four permissions described in what follows are the ones that Azure AD has offered since it started
supporting consent as described in this book; the last four are brand-new and likely to be less stable.
Wherever appropriate, I will hint at the old values so that if you encounter code based on older
strings, you can map it back to the new permissions. Chances are the list will change again: please
keep an eye on the permissions documentation, currently available at https://msdn.microsoft.com/
Library/Azure/Ad/Graph/howto/azure-ad-graph-api-permission-scopes.
Besides the ability to request a token containing claims about the incoming user, this permission
grants to the app the ability to query the Graph API for information about the currently signed-in
user.
As you’ve experienced, this permission can be granted by nonadmin users. That is confirmed by
the type property of value User in the permissions declaration.
Here’s the first exception. In the general case, Directory.Read is an admin-only permission:
only an admin user can consent to it. However, if the application is a web app (as opposed to a native
client) defined in tenant A, and the user being prompted for consent is also from A, Directory.
Read behaves like a User-type permission, which is to say that even a nonadmin user can consent
to it. For the scenario we have been considering until now—app developer and app users are from
the same tenant—this is effectively a User-type permission. When we consider the case in which the
app is available to other tenants, you’ll see that an app created in A that is requesting Directory.
Read and being accessed by a user from B will be provisioned in B only if that user happens to be an
administrator.
As a side note, for native applications, this permission behaves like a User permission instead. A
native app does not have an identity per se, and it is already doing the direct user’s bidding anyway.
It stands to reason that the app should be able to do what the user is able to do, just as happens
on-premises when a classic native client (say Word or Excel) can or cannot open a document from a
network share depending on whether the user has the correct permissions on that folder.
User.ReadBasic.All
You can think of this permission as the minimum requirement allowing an app to enumerate all users
from a tenant. Namely, User.ReadBasic.All will give access to the user attributes displayName,
givenName, surname, mail and thumbnailPhoto. Anything beyond that requires higher permissions.
User.Read.All
This is an extension of User.ReadBasic.All. This permission allows an app to access all the
attributes of User, the navigation properties manager, and directReports. User.Read.All can be
exercised only by admin users.
196 CHAPTER 8
As usual, it’s important to remember that scopes don’t really add to what a user can do: an
application obtaining Group.ReadWrite.All will only be able to manipulate the groups owned by
the user granting the delegation to the app.
Table 8-1 summarizes how the out-of-the-box Azure AD permissions work. I’ve added a column for
the permission identifier, which I find handy so that when I look at the Application object, which
uses only opaque IDs, I know what permission the app is actually requesting. Let me stress that there’s
no guarantee these won’t change in the future, so please use them advisedly.
TABLE 8-1 A summary of the Azure AD permissions for accessing the directory.
Now that you have some permissions to play with, let’s get back to the exploration of how consent
operates.
Go back to the Azure portal, sign in as a nonadmin user, and go through the usual application
creation flow. Once the app is created, head to the Configure tab and scroll all the way to the bottom
of the page. As of today, you’ll find a section labeled Permissions To Other Applications, already
containing one entry for Azure Active Directory—specifically, the default delegated permission Sign
In And Read User Profile. Figure 8-5 shows you the UI at the time of writing, but as usual you can be
sure there will be something different (but I hope functionally equivalent) by the time you pick up
the book.
FIGURE 8-5 The application permission selection UI in the Azure portal (fall 2015).
You’ll also see an ominous warning: “You are authorized to select only delegated permissions
which have personal scope.” Today that isn’t actually the case. Select Read And Write Directory Data,
and then click Save.
You’ll receive a warning that the portal was unable to update the configuration for the app, but
that’s only partially true. If you go take a look at the Application, you’ll see that it was correctly
updated. Here is its requiredResourceAccess section:
"requiredResourceAccess": [
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175",
"type": "Scope"
},
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
}
]
}
198 CHAPTER 8
The part that the portal was not able to add was the oauth2PermissionGrant that would allow
the current (nonadmin) user to have write access to the directory. If you list the oauth2Permission-
Grants of the ServicePrincipal, you’ll find only the original entry for User.Read.
That entry is the reason why, if you try to sign in to the app as the user who created it, you will suc-
ceed: the directory sees that entry, and that’s enough to not show the consent prompt and issue the
requested token. However, if after you sign in, your app attempts to get a token for calling the Graph,
the operation would fail.
If you launch the application again and try to sign in as any other nonadmin user, instead of the
consent prompt you’ll receive an error along the lines of “AADSTS90093: Calling principal cannot
consent due to lack of permissions,” which is exactly what you should expect.
Finally, launch the app again and try to sign in as an administrator. You will be presented with the
consent page as in Figure 8-6, just as expected.
Grant the consent—you’ll find yourself signed in to the application. That done, take a look at what
changed in oauth2PermissionGrants:
There’s a new entry now, representing the fact that the admin user consented for the app to have
UserProfile.Read and Directory.Write permissions. As discussed earlier, by the time you read
this, those scopes will likely have their new values— User.Read and Directory.ReadWrite.All—
but it is really exactly the same semantic.
Note that this did not change the access level for anybody but this particular admin user. If you try
to sign in as a nonadmin user (other than the app's creator), you’ll still get error AADSTS90093.
Admin consent
If the consent styles you’ve encountered so far were the only ones available, you’d have a couple of
serious issues:
■ Each and every user, apart from the application developer, would need to consent upon their
first use of the app.
■ Only admin-level users would be able to consent for applications requiring more advanced
access to the directory, even when a user did not plan to exercise those higher privileged
capabilities.
Both issues would limit the usefulness of Azure AD. Luckily, there’s a way of consenting to applica-
tions that results in a blanket grant to all users of a tenant, all at once, and regardless of the access
level requested. That mechanism is known as admin consent, as opposed to user consent, which
you’ve been studying so far. Achieving admin consent is just a matter of appending to the request to
the authorization endpoint the parameter prompt=admin_consent.
200 CHAPTER 8
Let’s give it a try and see what happens. From Chapter 7, you now know how to modify authentica-
tion requests by adding the change you want to the RedirectToIdentityProvider notification. In
a real app, you would add some conditional logic to weave this parameter in only at the time of first
access, but for this test you can go with the brute-force solution in which you add it every time.
Important Here I am adding Prompt=admin_consent in the sign-in request for the sake
of simplicity, but you would never do that in a production application without at least
some conditional logic. In fact, more often than not, you would not include it in the sign-in
action but wire it up to a dedicated sign-up action instead. Including Prompt=admin_con-
sent in a request will result in the consent being shown to the user, regardless of the past
consent history. You want to show this only when needed, and that’s only the first time.
Wire it up to some specific action in your app, like sign-up, onboarding, or any other label
that makes sense for your application.
After you’ve added that code, hit F5 and try signing in. You will be prompted by a dialog similar to
the one shown in Figure 8-7.
Superficially, the dialog in Figure 8-7 looks a lot like the one shown in Figure 8-6, but there is a
very important difference! The dialog shown when admin consent is triggered has new text, which
articulates the implications of granting consent in the admin consent case: “If you agree, this app
will have access to the specified resources for all users in your organization. No one else will be
prompted.”
Click OK—you’ll end up signing in as usual. The app will look the same, but its entries in the
directory underwent a significant change. Once again, take a look at the ServicePrincipal’s
oauth2PermissionGrants:
{
"odata.metadata": "https://graph.windows.net/developertenant.onmicrosoft.com/$metadata#oauth2
PermissionGrants",
"value": [
{
"clientId": "725a2d9a-6707-4127-8131-4f9106d771de",
"consentType": "AllPrincipals",
"expiryTime": "2016-02-27T00:38:03.4045842",
"objectId": "mi1acgdnJ0GBMU-RBtdx3knIMYJNhOpOkFrsIuF86Y8",
"principalId": null,
"resourceId": "8231c849-844d-4eea-905a-ec22e17ce98f",
"scope": "Directory.Write UserProfile.Read",
"startTime": "0001-01-01T00:00:00"
},
202 CHAPTER 8
I highlighted the new entry for you: it has a consentType of AllPrincipals, as opposed to the
usual Principal. Furthermore, its principalId property does not point to any user in particular;
it just says null. This tells Azure AD that the application has been granted a blanket consent for any
user coming from the current tenant. To prove that this is really the case, sign out from the app, stop
it in Visual Studio, comment out the code you added for triggering admin consent, and start the app
again. Sign in as a third user from the same tenant, one that you have never used before with this
app. Figure 8-8 shows a visual summary of this oauth2PermissionGrant configuration.
After the credential gathering, you’ll find yourself signed in right away, with no consent prompt of
any form.
Note The creation of the ServicePrincipal and the associated grant is at the origin
of the peculiar behavior of native apps created via the Azure portal by an admin. That is
the only case in which a native app does not trigger consent for all users in a tenant. In
all other cases, Azure AD today does not record consent for native apps in the directory,
storing it in the refresh token instead—which means that each new native app instance
running on a different device will prompt its user for consent regardless of its past consent
history. This is really out of scope for this book, but given that you have the concept fresh
in your mind, I thought I’d share this tidbit.
204 CHAPTER 8
The first part of this section will discuss how Azure AD enables authentication flows across multiple
tenants, and how you can generalize what you have learned about configuring the Katana middle-
ware to the case in which users are sourced from multiple organizations.
The second part will go back to the application model proper, showing you what happens to the
directory data model when your app triggers consent flows across tenants.
In traditional claims-based protocols such as SAML and WS-Federation, the problem of enabling
access to one application from multiple IdPs has a canonical solution. It entails introducing one
intermediary STS (often referred to as resource STS, R-STS or RP-STS) as the authority that the
application trusts. In turn, the intermediate STS trusts all the IdPs that the application needs to
work with—assuming the full burden of establishing and maintaining trust, implementing whatever
protocol quirks each IdP demands. This is a very sensible approach, which isolates the application
itself from the complexities of maintaining relationships with multiple authorities. It is also likely the
best approach when you don’t know anything about the IdPs you want to connect to, apart from the
protocol they implement and the STS metadata they publish. ADFS, Azure Access Control Services
(ACS), and pretty much any STS implementation supports this approach.
If you restrict the pool of possible IdPs to only the ones represented by a tenant in Azure AD,
however, you have far more information than that, and as you’ll see in the following, this removes the
need to have an intermediary in the picture. Although each administrator retains full control over her
or his own tenant, all tenants share the same infrastructure—same protocols, same data model, same
provisioning pipes. Focusing on endpoints in particular (recall their description from Chapter 3), rather
than a collection of STSs for each of its tenants, Azure AD can be thought of like a giant parametric
STS, where each tenant is expressed by instantiating its ID in the right segment of the issuance
endpoint. Figure 8-9 compares the R-STS approach with the multitenant pattern used by Azure AD.
In the hands-on chapters, you've experienced directly how the endpoint pattern https://<instance>
/<tenant>/<protocol-specific-path> can be modulated to indicate tenant-specific token-issuance
endpoints, sign-out endpoints, metadata document endpoints, and so on. You have also seen how
the Katana middleware leverages those endpoints for tying one application to one specific tenant.
For example, in Chapter 6 you saw how the metadata document published at https://login
.microsoftonline.com/DeveloperTenant.onmicrosoft.com/.well-known/openid-configuration (which,
206 CHAPTER 8
You can repeat the same reasoning for all tenants: all you need to do is instantiate the right
domain (or tenantID) in the endpoints paths.
Azure AD makes it possible to deal with multitenant scenarios by exposing a particular endpoint,
where the tenant parameter is not instantiated up front. There is a particular value, common, that can
be instantiated in endpoints in lieu of a domain or tenantID. By convention, that value tells Azure AD
that the requestor is not mandating any particular tenant—any Azure AD tenant will do.
Very important: Common is not a tenant. It is just an artifact used for constructing Azure
AD endpoints when the tenant to be used is not known yet. This is a crucial point to keep
in mind at all times when working with multitenant solutions, or you’ll end up baking as-
sumptions into your app that will inevitably turn out to be false and create all sorts of is-
sues that are hard to debug.
When the endpoint being constructed is one that would serve authentication UI, as is the case
for the OAuth2 authorization endpoints, the user is presented with a generic Azure AD credentials-
gathering experience. As the user enters his or her credentials, the account he or she chooses
will indirectly determine a specific tenant—the one the account belongs to. That will resolve the
ambiguity about which tenant should be used for the present transaction, concluding the role
of common in the flow. The resulting code or token will look exactly as it would have had it been
obtained by specifying the actual tenant instead of common to begin with. In other words, whether
you start the authentication flow using https://login.microsoftonline.com/common/oauth2/authorize
or https://login.microsoftonline.com/6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e/oauth2/authorize
for an OpenID Connect sign-in flow, if at run time you sign in with a user from the tenant with ID
6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e, the resulting token will look the same, with no memory
of what endpoint path led to its issuance. That should make it even clearer that common is not a real
tenant: it’s just an endpoint sleight of hand for late binding a tenant, if you will.
Now comes the fun part. Upon learning about the common endpoint, the typical (and healthy)
developer reaction is “Awesome! Let me just change the OpenID Connect middleware options as
shown here, and I’ll be all set!”
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
Authority = "https://login.microsoftonline.com/common",
Recall the id_token validation logic from Chapter 7, and the comment about how the discovery
document of each tenant establishes what iss value an app should expect. If your app is initialized
with a tenant-specific endpoint, it will read from the metadata the tenant-specific issuer value to
expect; but if it is initialized with common, what issuer value is it going to get? I’ll save you the hassle
of visiting https://login.microsoftonline.com/common/.well-known/openid-configuration yourself:
the discovery doc says “issuer”: “https://sts.windows.net/{tenantid}/”. No real tenant will ever issue a
token with that value, given that it’s just a placeholder, but the middleware does not know that. That’s
the value that the metadata asserts is going to be in the iss claim, and the default logic will refuse
anything carrying a different value.
Note What about all the other values in the discovery doc? Issuer is the only problematic
one, everything else (including keys, as you have seen in Chapter 6) is shared by all tenants.
This simply means that the default validation logic cannot work in case of multitenancy. What
should you do instead? You already saw the main strategies for dealing with this in Chapter 7,
although at the time I could not fully discuss the multitenant case. I recommend that you leaf back
a few pages to get all the details, but just to summarize the key points here:
■ If you have your own list of tenants that your application should accept, you have two main
approaches. If the list is short and fairly static, you can pass it in at initialization time via
TokenValidationParameters.ValidIssuers. If the list is long and dynamic, you can
provide an implementation for TokenValidationParameters.IssuerValidator where
you accommodate for whatever logic is appropriate for your case.
■ If the decision about whether the caller should be allowed to get through is not strictly tied
to the tenant the caller comes from, you can turn off issuer validation altogether by setting
TokenValidationParameters.ValidateIssuer to false. You should be sure that you do
add your own validation logic; for example, in the SecurityTokenValidated notifications
or even in the app (custom authorization filters, etc.). Otherwise, your app will be completely
open to access by anybody with a user in Azure AD. There are scenarios where this might be
what you want, but in general, if you are protecting your app with authentication, that means
that you have something valuable to gate access to. In turn, that might call for you to verify
whether the requestor did pay his monthly subscription or whatever other monetization
strategy you are using—and usually that verification boils down to checking the issuer or the
user against your own subscription list.
Now that you know how Azure AD multitenancy affects the application’s code, I’ll go back to how
consent, provisioning, and the data model are influenced.
208 CHAPTER 8
Let’s say that you signed in to the Azure portal and modified your app entry to be multitenant.
Let’s also say that you modified your app code to correctly handle the validation for tokens coming
from multiple organizations. Let’s give the app a spin by hitting F5.
Note If you promote the app you have been using in this chapter until now, be sure to
comment out the logic that triggers the admin consent (for now). Consequently, make sure
also that the app does not request any admin-only permissions.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
Authority = "https://login.microsoftonline.com/common",
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
},
I cannot stress this enough: you should not go into production with the issuer validation
logic disabled unless you have also added your own validation.
Once the app is running, click the Sign In link, but this time sign in with a user from a different
Azure AD tenant. As explained in Chapter 3, in the section “Getting Azure Active Directory,” any
Azure subscriber can create a number of Azure AD tenants, create users and apps in them, and so
on. If you belong to a big-ish organization, you likely already did this in creating your development
Upon successful sign-in, you’ll be presented with the consent page. As you can see in Figure 8-10,
the consent page presents some important differences from the single-tenant case. For one, the ten-
ant where the Application object was originally created is prominently displayed as the publisher.
Moreover, there’s now text telling you to consider whether you trust the publisher of the applica-
tion. This is serious stuff—if you give consent to the wrong application for the wrong permissions,
the damage to your own organization could be severe. That’s why only admins can publish apps for
multiple organizations, and that’s why even the simple Directory.Read permission requires admin
consent when it’s requested by a multitenant app.
At the beginning of this chapter, you encountered a description of what happens in the tenants
for this exact consent scenario: the Application object in the original development tenant is
used as a blueprint for creating a ServicePrincipal in the target tenant. In fact, if you query
the Applications collection in the target tenant (you’ll learn how to do this in the next chapter),
you’ll find no entries with the ClientId of your application—but you will find a ServicePrincipal
with that ClientId. From what you have learned a few pages ago, you know that if you look into the
collection of oauth2PermissionGrants for that ServicePrincipal, you will find an entry record-
ing the consent of that particular user for this app and the permissions it requires. The principles of
admin consent apply here as well: if you want the admin of your prospective customer tenants to be
able to grant a blanket consent for all of his or her users, provide a way for your app to trigger an
admin consent request.
210 CHAPTER 8
This is less than ideal, especially if you consider that Azure AD offers you no way of warning your
users that something changed—you have to handle that in your own app or subscription logic. The
good news is that the next version of the Azure AD application model will allow for dynamic consent,
solving this issue once and for all.
The last section discussed at length the consent framework used for driving delegated permissions
assignment to applications. That is a super important aspect of managing application capabilities,
but it is far from the only one. The next section will continue to explore how Azure AD helps you to
control how users and applications have access to the directory itself, and to each other.
■ App user assignment The ability to explicitly define which users should be allowed to get a
token for a certain application, at the exclusion of everybody else.
■ App-level permissions The ability to expose (and request) permissions that can be assigned
to applications themselves, as opposed to the users of the apps.
■ App roles The ability for developers to define application-specific roles, which can be used
by administrators to establish in which capacity users can access an application.
All these features give you even more control over who can access your application and how.
Given that this feature is related to specific instances of the app in specific tenants, the knobs
used to control it are not in the Application object but in the ServicePrincipal and associated
entities in the target tenant. You already encountered the ServicePrincipal property appRole-
AssignmentRequired: flipping the switch in the portal has the effect of setting this property to true.
The Users tab in the application entry in the Azure portal lists all the users that are assigned to the
application. From now on, no user not on that list can successfully request a token for the application.
If you flip the switch for one of the apps you’ve been playing with in the preceding section, you’ll see
that all the users that already gave consent for the app are present in the list. Every time a user gives
consent to the app, Azure AD adds an entry to a list of AppRoleAssignment, an entity I haven’t yet
discussed. Here’s how one typical entry looks:
{
"odata.type": "Microsoft.DirectoryServices.AppRoleAssignment",
"objectType": "AppRoleAssignment",
"objectId": "Bkp-sDgT4kq5a-YB4HMf2q2NyOTf4hpKhVKXXQHxMhA",
"deletionTimestamp": null,
"creationTimestamp": "2015-09-06T08:53:30.1974755Z",
"id": "00000000-0000-0000-0000-000000000000",
"principalDisplayName": "Vittorio Bertocci",
"principalId": "b07e4a06-1338-4ae2-b96b-e601e0731fda",
"principalType": "User",
"resourceDisplayName": "MarioApp1",
"resourceId": "725a2d9a-6707-4127-8131-4f9106d771de"
}
That entry declares that the user Vittorio Bertocci (identified by its objectId b07e4a06-
1338-4ae2-b96b-e601e0731fda) can have access to the app MarioApp1 (object ID of the app’s
ServicePrincipal being 725a2d9a-6707-4127-8131-4f9106d771de) in the capacity of role
00000000-0000-0000-0000-000000000000.
This is where the role of Role (pun intended) comes into play. As you will see later, Azure AD allows
developers to define application-specific roles. The AppRoleAssignment entity is meant to track that
a certain app role has been assigned to one user for a certain app. What you are discovering here is
that Azure AD uses AppRoleAssignment also for tracking app user assignments—but in this case,
Azure AD automatically sets in the AppRoleAssignment a default role, 00000000-0000-0000-0000-
000000000000. It’s as simple as that.
One notable property of AppRoleAssignment is principalType. The sample entry here has the
value User, indicating that the entity being assigned the role is a user account. Other possible values
are Group (in which case, all the members of the group are assigned the role) or ServicePrincipal
(in which case, the role is being assigned to another client application).
212 CHAPTER 8
https://graph.windows.net/developertenant.onmicrosoft.com/servicePrincipals/725a2d9a-6707-4127-
8131-4f9106d771de/appRoleAssignedTo.
Just for kicks, try to access your application with a user that has not been assigned. Instead of the
usual consent dialog, you’ll get back a lovely error along the lines of:
The behavior described in this section is what you would observe if your application didn’t define
any app roles. In the next section, I’ll explore app roles in more depth.
App roles
Azure AD allows developers to define roles associated with an application. Traditionally, roles are
handy ways of assigning collections of permissions to a bunch of users all at once: for example,
people in a hypothetical Approver role might have read/write access to a certain set of resources,
while people in the Reviewer role might have only read access to the same resources. Roles are handy
because assigning a user to a role saves you the hassle of adding all the permissions a role entails
one by one. Moreover, when a new resource is added to the milieu, access to that resource can be
added to the role to enable access to it for all the users already assigned to the role, replacing the
need to assign access individually, account by account. That said, roles in Azure AD do not necessar-
ily need to represent permissions grouping: Azure AD does not offer you anything for representing
such permissions anyway; it is your app’s job to interpret each role. You can use application roles to
represent any user category that makes sense for your application, like what is the primary spoken
language of a user. True, there are many other ways of tracking the same info, but one big advantage
of app roles over any other method is that Azure AD will send them in the form of claims in the token,
making it extra easy for the app to consume the info they carry.
After you declare application roles, such roles are available to be assigned to users by the
administrators of the tenants using your app. Let’s take a look at how that cycle plays out.
The Application entity has one collection, appRoles, which is used for declaring the roles you
want to associate with your application. As of today, the way in which you populate that property is
by downloading the app manifest as described in “The Application” section at the beginning of this
chapter, adding the appropriate entries in appRoles, and uploading it back via the portal. Here is
what one appRoles collection looks like:
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
The properties of each entry are mostly self-explanatory, but there are a couple of nontrivial
points.
The displayName and description strings are used in any experience in which the role is
presented, such as the one in which an administrator can assign roles to users.
The value property represents the value that the role claim will carry in tokens issued for users
belonging to this role. This is the value that your application should be prepared to receive and
interpret at access time.
The id is the actual identifier of the role entry. It must be unique within the context of this
Application.
The allowedMemberTypes property is the interesting one. Roles can be assigned to users, groups,
and applications. An allowedMemberTypes collection including the entry “User” indicates a role
that can be assigned to both users and groups. (In the next section, I’ll cover roles assignable to
applications.)
Once you have added the roles in the manifest file, don’t forget to upload it back via the portal.
Note Sometimes the upload will fail, unfortunately without a lot of information to help
you troubleshoot: watch out for silly errors—for example, nonmatching parentheses. I
recommend using a syntax-aware JSON editor, which should take care of most issues up
front.
If you head back to the Users tab and try to assign a new user to the app like you did in the
preceding section, you’ll see that you are no longer able to simply declare that you want to assign a
user to the app: now you are presented with a choice between the various roles you declared in the
manifest. Assign one of the roles to a random user, and then launch the app and try to sign in with
that user.
214 CHAPTER 8
If you go back to the appRoleAssignedTo property of the ServicePrincipal and inspect the
role assignments there, you’ll find the same user assignments from the preceding section, plus a new
entry for the user you just assigned to a role. It should look something like this:
{
"odata.type": "Microsoft.DirectoryServices.AppRoleAssignment",
"objectType": "AppRoleAssignment",
"objectId": "9pcRosZaC0a10yoa5r0IwZrIr_JYzUxFtCmlWBYn6w0",
"deletionTimestamp": null,
"creationTimestamp": null,
"id": "8f29f99b-5c77-4fba-a310-4a5c0574e8ff",
"principalDisplayName": "Fabio Bianchi",
"principalId": "a21197f6-5ac6-460b-b5d3-2a1ae6bd08c1",
"principalType": "User",
"resourceDisplayName": "MarioApp1",
"resourceId": "725a2d9a-6707-4127-8131-4f9106d771de"
},
As expected, the id property points to one of the roles just defined, as opposed to the default
00000000-0000-0000-0000-000000000000 used during user assignment.
Launch the app and sign in as the user you just assigned to the role. If you capture the traffic via
Fiddler (as you learned about in Chapter 6) and peek at the JWT token sent in the id_token, you’ll
notice a new roles claim:
{
"amr" : [ "pwd" ],
"aud" : "1538b378-5829-46de-9294-6cfb4ad4bbaa",
"c_hash" : "EOuY-5M5XFxRyNCvRHe8Kg",
"exp" : 1442740348,
"family_name" : "Bianchi",
"given_name" : "Fabio",
"iat" : 1442736448,
"iss" : "https://sts.windows.net/6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e/",
"name" : "Fabio Bianchi",
"nbf" : 1442736448,
"nonce" : "635783335451569467.YzJiYmZjMGUtOWFkMS00NzI3LWJkYjMtMzhiMjE0YjVmNWE0ZDcwZTk3YmY
tNzQ4NC00YjkyLWFiY2YtYWViOWFhNjE0YjFj",
"oid" : "a21197f6-5ac6-460b-b5d3-2a1ae6bd08c1",
"roles" : [ "approver" ],
"sub" : "OpcgG-Rxo_DSCJnuAf_7tdfXp7XaOzpW6pF3x7Ga8Y0",
"tid" : "6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e",
"unique_name" : "fabio@developertenant.onmicrosoft.com",
"upn" : "fabio@developertenant.onmicrosoft.com",
"ver" : "1.0"
}
If you are using roles for authorization, classic ASP.NET development practices would suggest
using [Authorize], <Authorization>, or the evergreen IsInRole(). The good news is that
they are all an option. The only thing you need to do is tell the identity pipeline that you want to use
the claim type roles as the source for the role information used by those artifacts. That’s done via
one property of TokenValidationParameters, RoleClaimType. For example, you can add the
following to your OpenID Connect middleware initialization options:
Azure AD roles are a very powerful tool, which is great for modeling relationships between users
and the functionality that the app provides. Although the concept is not new, Azure AD roles operate
in novel ways. For example, developers are fully responsible for their creation and maintenance,
while the administrators of the various tenants where the app is provisioned are responsible for
actually assigning people to them. Also, Azure AD roles are always declared as part of one app—it
is not possible to create a role and reuse it across multiple applications. There is no counterpart for
this on-premises. The closest match is groups, but those have global scope, and a developer has no
control over them. Before the end of the chapter, I will also touch on groups in Azure AD.
Application permissions
All the features you encountered in this chapter are meant to give you control over how users have
access to your app and how users can delegate your app to access other resources for them.
In some situations you want to be able to confer access rights to the application itself, regardless
of what user account is using the app, or even when the app is running without any currently signed-
in user. For example, imagine a long-running process that performs continuous integration—an app
updating a dashboard with the health status of running tests against a solution and so on. Or more
simply, think about all the situations in which an app must be able to perform operations that a
low-privilege user would not normally be entitled to do—like provisioning users, assigning users to
groups, reading full user profiles, and so on. Note that, once again, those kinds of permissions come
into play when accessing the resource as a web API, so you won’t see this feature really play out until
the next chapter. Here I’ll just discuss provisioning.
216 CHAPTER 8
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"description": "Allows the app to read and write all device properties without
a signed-in user. Does not allow device creation, device deletion, or update of device
alternative security identifiers.",
"displayName": "Read and write devices",
"id": "1138cb37-bd11-4084-a2b7-9f71582aeddb",
"isEnabled": true,
"value": "Device.ReadWrite.All"
},
{
"allowedMemberTypes": [
"Application"
],
"description": "Allows the app to read and write data in your organization's
directory, such as users and groups. Does not allow create, update, or delete of applications,
service principals, or devices. Does not allow user or group deletion.",
"displayName": "Read and write directory data",
"id": "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175",
"isEnabled": true,
"value": "Directory.Write"
},
{
"allowedMemberTypes": [
"Application"
],
"description": "Allows the app to read data in your organization's directory, such as
users, groups, and apps.",
"displayName": "Read directory data",
"id": "5778995a-e1bf-45b8-affa-663a9f3f4d04",
"isEnabled": true,
"value": "Directory.Read"
}
],
Note Directory.Write and Directory.Read will follow the same update path as their
delegated homonyms and become Directory.ReadWrite.All and Directory.Read.All,
respectively.
You can think of each of those roles as permissions that can be requested by applications invok-
ing the Graph API. Although in the case of user and group roles, administrators can perform role
assignments directly in the Azure management portal, granting application roles works very much
like delegated permissions—via consent at the first token request.
What happens when you select an application permission, say Read Directory Data, for the
Directory Graph API? Something pretty similar to what you have seen in the case of delegated
permissions. Take a look at what changes in the Application’s requiredResourceAccess
collection:
"requiredResourceAccess": [
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "5778995a-e1bf-45b8-affa-663a9f3f4d04",
"type": "Role"
},
{
"id": "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175",
"type": "Scope"
},
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
}
]
}
The resource you want to access remains the same, the Directory Graph API—represented by the
ID 00000002-0000-0000-c000-000000000000. In addition to the old delegated permissions, of
type Scope, you’ll notice a new one, of type Role. The ID of this one corresponds exactly to the ID
declared in the Directory Graph API’s ServicePrincipal appRoles for the Read Directory Data
permission.
As I mentioned, granting application permissions takes place upon successful request of a token
from the app and positive consent granted by the user at authentication time. The presence of
an entry of type Role in a RequiredResourceAccessCollection introduces a key constraint,
however: only admin consent requests will be considered. This means that every time you develop an
app requesting application permissions, you have to be sure that the first time you request a token
from it, you append the prompt=admin_consent flag to your request.
If you actually launch the app and go through the consent dance, you’ll find that after provision-
ing, the directory has added one new AppRoleAssignment entry to the appRoleAssignedTo
property of the app’s ServicePrincipal entry in the target tenant. Or better, you would find it if
your app had requested permissions for any resource other than the Directory Graph API. As I am
writing this chapter, the Directory Graph API is the only resource that received special treatment from
218 CHAPTER 8
With their permission/role dual nature, application permissions can be confusing. However, they
are an extremely powerful construct, and the possibilities their use opens up are well worth the effort
of mastering them.
Groups
In closing this chapter about how Azure AD models applications, I am going to show you how to
work with groups. Groups in Azure AD can be cloud-only sets of users, created and populated via the
Azure portal or the Office 365 portal, or they can be synched from on-premises distribution lists and
security groups. Groups have been a staple of access control for the last few decades. As a developer,
you can count on groups to work across applications and to be assigned and managed by administra-
tors: all you need to know is that a group exists and what its semantic is and then use that information
to drive your app’s decisions regarding the current user (access control, UI customization, and so on).
By default, tokens issued by Azure AD do not carry any group information: if your app is interested
in which groups the current user belongs to, it has to use the Directory Graph API (cue the next
chapter).
Just as with application roles, you can ask Azure AD to start sending group information in issued
tokens in the form of claims—simply by flipping a switch property in the Application object. If you
download your app manifest, modify the groupMembershipClaims property as follows, and then
upload the manifest again, you will get group information in the incoming tokens:
"groupMembershipClaims": "All",
If you are interested in receiving just the security groups, enter “SecurityGroup” instead of “All”.
After changing the manifest as described, I used the portal to create in my test tenant a new group
called “Hippies,” and assigned to it the test user Fabio. That done, I launched the app and signed in as
Fabio. Here’s the token I got:
{
"amr" : [ "pwd" ],
"aud" : "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
"c_hash" : "zit-F66pwRsDeJPtjpuzgA",
"exp" : 1442822854,
"family_name" : "Bianchi",
"given_name" : "Fabio",
"groups" : [ "d6f48969-725d-4869-a7a0-97956001d24e" ],
You can see that there is indeed a groups claim, but what happened to the group name? Well, the
short version of the story is that because Azure AD is a multitenant system, arbitrary group names
like “People in building 44” or “Hippies” have no guarantee of being unique. Hence, if you wrote code
relying on only a group name, your code would often be broken and subject to misuse (a malicious
admin might create a group matching the name you expect in a fraudulent tenant and abuse your
access control logic). As a result, today Azure AD sends only the objectId of the group. You can use
that ID for constructing the URI of the group itself in the directory, in this case that’s:
https://graph.windows.net/developertenant.onmicrosoft.com/groups/d6f48969-725d-4869-a7a0-
97956001d24e.
In the next chapter, you’ll learn how to use the Graph API to use that URI to retrieve the actual
group description, which in my case looks like the following:
{
"odata.metadata": "https://graph.windows.net/developertenant.onmicrosoft.
com/$metadata#directoryObjects/Microsoft.DirectoryServices.Group/@Element",
"odata.type": "Microsoft.DirectoryServices.Group",
"objectType": "Group",
"objectId": "d6f48969-725d-4869-a7a0-97956001d24e",
"deletionTimestamp": null,
"description": "Long haired employees",
"dirSyncEnabled": null,
"displayName": "Hippies",
"lastDirSyncTime": null,
"mail": null,
"mailNickname": "363bdd6b-f73c-43a4-a3b4-a0bf8b528ee1",
"mailEnabled": false,
"onPremisesSecurityIdentifier": null,
"provisioningErrors": [],
"proxyAddresses": [],
"securityEnabled": true
}
Your app could query the Graph periodically to find out what group identifiers to expect, or you
could perform queries on the fly as you receive the group information, though that would somewhat
defeat the purpose of getting groups in the form of claims.
220 CHAPTER 8
One last thing about groups. There are tenants in which administrators choose to use groups
very heavily, resulting in each user belonging to very large numbers of groups. Adding many groups
in a token would make the token itself too large to fulfil its usual functions (such as authentication
and so on), so Azure AD caps at 200 the number of groups that can be sent via JWT format. If the
user belongs to more than 200 groups, Azure AD does not pass any group claims; rather, it sends an
overage claim that provides the app with the URI to use for retrieving the user’s groups information
via the Graph API. Azure AD does so by following the OpenID Connect core specification for aggre-
gated and distributed claims: in a nutshell, a mechanism for providing claims by reference instead of
passing the values. Say that Fabio belonged to 201 groups in our sample above. Instead of the groups
claims, the incoming JWT would have contained the following claims:
"_claim_names": {
"groups": "src1",
},
"_claim_sources": {
"src1": {"endpoint": "https://graph.windows.net/developertenant.onmicrosoft.com/users/
a21197f6-5ac6-460b-b5d3-2a1ae6bd08c1/getMemberObjects"}
}
In the next chapter, you’ll learn how to use that endpoint for extracting group information for the
incoming user.
Summary
The Azure AD application model is designed to support a large number of important functions: to
hold protocol information used at authentication time, provide a mechanism for provisioning applica-
tions within one tenant and across multiple tenants, allow end users and administrators to grant or
deny consent for apps to access resources on their behalf, and supply access control knobs to admin-
istrators and developers to fine-tune user and application access control.
That’s a tall order, but as you have seen throughout this chapter, the Azure AD application model
supports all of those functions—though in so doing, it often needs to create complex castles of inter-
locking entities. Note that little of that complexity ever emerges all the way to the end user, and even
for most development tasks, you don’t need to dive as deep as we did in this chapter. However, as a
reward for the extra effort, you now have a holistic understanding of how applications in Azure AD
are represented, provisioned, and granted or denied access to resources. You will find that this skill
will bring your proficiency with Azure AD to a new level.
295
296
297
298
299
D ADFS, 276–277
common, 207
decoupling web servers from apps, 138. See also multitenancy and, 206–207
middlewares; Open Web Interface for .NET network, 52
(OWIN) OAuth2, 64
default authentication process, 4–7 protocol, 60, 63–64
delegated access, 34–36 protocol/credential type, 60
delegated permissions, 185, 192–197 turning on and off, 52
scopes, 201 entities, 22–23, 70
deletionTimestamp property, 180, 188 environment dictionary, 138, 147
Devasahayam, Samuel, 15 errorUrl property, 188
developer-assigned application identifiers, 181 exp claims, 132
development certificates, 91 ExpiresOn property, 230
development libraries
in Active Directory, 75. See also libraries
for native clients, 294 F
for other platforms, 293
family_name claims, 133
development on dedicated machines, 91
federated tenants, 65–66, 122
diagnostic middleware, 153–154
federation. See also Active Directory Federation Services
digital signatures, 19
(ADFS)
directories, defined, 62
for integrating with Azure AD, 66
Directory.AccessAsUser.All permission, 196
for synchronized deployments of Azure AD, 65
directory access permissions, 193–196
Fiddler, 110
directory entities, programmatic access to, 236–237
capturing trace, 112
directory entries for web APIs, 257–258
HttpClient traffic tracing, 261
Directory Graph API, 10, 59–60, 64–65, 236–237
setup, 111
Application object JSON file, 178–180
300
301
J signing, 280
symmetric, 19
JavaScript token-signing, 120–122, 158
HTTP requests to back end, 46 token-validation, 167
logic-layout management, 45–46 ValidateIssuerSigningKey property, 168
native apps, 81 keys document, 120–121
token bits, retrieving, 46–47 Klout web application, 248–249
Jones, Mike, 41 knownClientApplications property, 183, 258
JSON Tokens, 41
JSON Web Algorithms (JWA), 110, 131
JSON Web encryption (JWE), 129 L
JSON Web Keys set (JWKS), 280
libraries
JSON Web Signature (JWS), 129–131
in Active Directory, 75
JSON Web Token (JWT), 19, 40
authentication tasks, 73–74
access token representation as, 255
for hybrid token-requestor and resource-protector
for access tokens, 271
role, 74–75, 85–86
ADFS support, 55
for native clients, 294
claim set, 131–132
open source, 76
components, 129–130
for other platforms, 293
handlers, 84, 92
reasons for using, 71
header types, 131
for resource-protector role, 73–74, 82–85
specification, 110, 129
for token-requestor role, 70–71, 76–81
tokens, 84
line-of-business (LOB) applications, 4–5
just-in-time provisioning, 58
local networks, authentication on, 14–15
localStorage, 47
302
Microsoft Online Directory Service (MSODS), 60 admin creation in Azure portal, 204
Microsoft.Owin.Diagnostics NuGet package, 154 authentication flows, 94
Microsoft.Owin NuGet package, 92 broker apps and, 48
Microsoft.Owin.Security.ActiveDirectory NuGet package, development libraries, 75–76
83, 254 Kerberos and, 47
Microsoft.Owin.Security.Jwt NuGet package, 254 modern authentication for, 294
Microsoft.Owin.Security.OAuth NuGet package, 254 popularity, 47–48
Microsoft.Owin.Security.OpenIdConnect NuGet package, tokens, obtaining, 21–22
84 nbf claims, 132
Microsoft.Owin.Security NuGet package, 92 .NET-based applications, 78
Microsoft.Owin.Security.WsFederation NuGet package, 83 .NET core, OWIN middleware for, 84
Microsoft Visual Studio. See Visual Studio .NET Framework
_middleware entry, 141 caller identity class, 7–10
middleware initialization options class, 155–159 SAML and, 25
middlewares version 4.5, 82
activation sequence, 142–145 Windows Identity Foundation classes, 82–83
behavior settings, 158–159 .NET JWT handler, 84
building, 138. See also Open Web Interface for .NET .NET web development, 138
(OWIN) network endpoints, 52
caption setting, 159 network tracing features, 110
context, 142, 145–148 nickname claims, 133
environment dictionary, 138 Node.JS, 81
initialization pipeline, 265–266 nonadmin users, application creation, 189–192. See also
Invoke method, 142 users
message received notification, 164 nonce value
observing pipeline, 143–145 of authentication requests, 117
pipeline of web APIs, 254–255 cookie tracking, 124–125
pointers to next entries, 142 OpenID Connect, 149
requesting execution, 145 notifications, 159–166
resource protectors, 74, 81 AuthenticationFailed, 166
response handling, 161 AuthorizationCodeReceived, 166
security token received notification, 164 in bearer token middleware, 264
security token validated notification, 164–165 MessageReceived, 164
sign-in and sign-out flow, 99–103 RedirectToIdentityProvider, 162–164
skipping to next, 161 SecurityTokenReceived, 164
stopping processing, 142, 145 SecurityTokenValidated, 164–165
UseStageMarker method, 145 sequence, 159–161
midtier clients ADAL libraries, 81 of TokenCache class, 244–245
MMC (Microsoft Management Console), 60 Notifications property, 155
mobile operating systems, native apps on, 80 NuGet packages
modern authentication techniques, 31–48 adding references, 92
multiple authentication factors (MFA), 122 Microsoft.AspNet.WebApi.Owin, 266
Multiple Response Type specifications, 109 Microsoft.IdentityModel.Protocol.Extensions, 84, 92
multiresource refresh tokens (MRRT), 242–243, 260 Microsoft.Owin, 92
multitenancy, 205–211 Microsoft.Owin.Diagnostics, 154
MVC 5 Controller, 100 Microsoft.Owin.Security, 92
/myorganization alias, 237 Microsoft.Owin.Security.ActiveDirectory, 254
Microsoft.Owin.Security.Jwt, 254
Microsoft.Owin.Security.OAuth, 254
N Microsoft.Owin.Security.OpenIdConnect, 83
.NET, 227–228
native applications, 47–48
System.IdentityModel.Tokens.Jwt, 84, 92
ADAL libraries, 48, 80–81
SystemWeb, 92
ADFS support, 55
for web APIs, 254
ADFS template, 275
web apps referencing, 92
303
304
305
306
Security Assertion Markup Language (SAML), 8, sign-in and sign-out flow, 110–112
25–27, 55, 182 SignInAsAuthenticationType property, 159
security code, custom, 71 sign-in flow
security groups, 219 access in context of session, 148, 152
SecurityTokenHandlers property, 158 challenge generation, 148–149, 152–153
SecurityTokenReceived notification, 165 OpenID Connect for, 107–134
Security Token Service (STS), 29 response processing, 149–151
Access Control Service, 78–79 session generation, 149–151
resource, 205–206 specifications and dependencies, 107–108
SecurityTokenValidated notification, 165–166 WS-Federation, 29–31
server applications, ADFS template, 275 signing keys for web apps, 280
servers sign-in messages
in OWIN pipeline, 140 generation of, 149
server-to-server calls, 42 redirects, 148–149
ServicePrincipal, 174–177, 187–188 request generation, 73–74
AppId, 193 sign-out, 99–103
oauth2Permissions, 193–196 distributed, 101, 109
ObjectId, 193 flow sequence, 136
properties, 187–188 ID hint, 135
provisioning, 186 notifications, 161
for web APIs, 257 OpenID Connect, 134–136
ServicePrincipal.appRoleAssignedTo object, 216–219 postlogout redirects, 156
servicePrincipalNames property, 188 PostLogoutRedirectUri property, 102
service providers (SPs), 26 redirect URI, 135
session artifacts, 73 request syntax, 135
session cookies, 24–25, 45, 92, 122 state preservation, 135
discarding, 135 target endpoint, 135
in OpenID Connect hybrid flow, 42 UI elements, 102–103
persisting, 150 user credentials prompts, 163
validation, 73, 125 sign-out flow, 152–153
session data, 24 SignOut method, 99
session management, 70–71 Simple Web Token (SWT), 40–41
by ADAL, 238–251 Single Logout messages, 27
in OpenID Connect middleware, 109, 171 single-page applications (SPAs), 45–47, 294
sessions ADAL JS library for, 85
ClaimsPrincipal, saving, 151 single sign-on, hack for, 38
cleaning, 135 single sign-out, 27
ending, 134–136 SkipToNextMiddleware method, 161
establishing, 22, 73, 149–151 software as a service (SaaS) apps, 17
properties, 151 _sso_data claim, 289
request token validation, 152 SSO sessions, 27
saving, 150 stage markers, 145
validation, 73 Startup.Auth.cs file
sessionStorage, 47 ADFS identity provider code, 103–104
Set-Cookie value, 135, 149–151 identity pipeline initialization code, 96–97
shared secrets, 279–280, 287 Startup class, 139–141
signatures, 19 Startup.Configure, 140
signature verification, SAML and, 26 Startup.cs file, 95
signed tokens, 20 call to activate authentication, 97
sign-in, 37–39, 99–103, 126. See also web sign-on state, preserving at sign-out, 135
notifications, 159–160 state parameter
response phase, 224–225 of authentication requests, 116–117
sequence, 126–127, 224 local URL of resource, 125
UI elements, 102–103 storing tokens, 70–71
user credentials prompts, 163 string identifiers, 18
307
308
309
310
VIT TORIO BE RTOCC I is principal program manager on the Azure Active Directory
team, where he works on the developer experience: Active Directory Authentication
Library (ADAL), OpenID Connect and OAuth2 OWIN components in ASP.NET, Azure
AD integration in various Visual Studio workstreams, and other things he can’t tell
you about (yet).
Vittorio joined the product team after years as a virtual member in his role as principal architect
evangelist, during which time he contributed to the inception and launch of Microsoft’s claims-
based platform components (Windows Identity Foundation, ADFS 2.0) and owned SaaS and
identity evangelism for the .NET developers community.
Vittorio holds a masters degree in computer science and began his career doing research on
computational geometry and scientific visualization. In 2001 he joined Microsoft Italy, where
he focused on the .NET platform and the nascent field of web services security, becoming a
recognized expert at the national and European level.
In 2005 Vittorio moved to Redmond, where he helped launch the .NET Framework 3.5 by working
with Fortune 100 and Global 100 companies on cutting-edge distributed systems. He increasingly
focused on identity themes until he took on the mission of evangelizing claims-based identity for
mainstream use. After years of working with customers, partners, and the community, he decided
to contribute the experience he had accumulated back to the product and joined the identity
product team.
Vittorio is easy to spot at conferences. He has spoken about identity in 23 countries on four
continents, from keynote addresses to one-on-one meetings with customers. Vittorio is a regular
speaker at Ignite, Build, Microsoft PDC, TechEd (US, Europe, Australia, New Zealand, Japan),
TechDays, Gartner Summit, European Identity Conference, IDWorld, OreDev, NDC, IASA, Basta,
and many others. At the moment his Channel 9 speaker page at https://channel9.msdn.com/
events/speakers/vittorio-bertocci lists 44 recordings.
Vittorio is a published author, both in the academic and industry worlds, and has written many
articles and papers. He is the author of Programming Windows Identity Foundation (Microsoft
Press, 2010) and coauthor of A Guide to Claims-Based Identity and Access Control (Microsoft
patterns & practices, 2010) and Understanding Windows Cardspace (Addison-Wesley, 2008). He
is a prominent authority and blogger on identity, Azure, .NET development, and related topics:
he shares his thoughts at www.cloudidentity.com and via his twitter feed, http://www.twitter.com/
vibronet.
Vittorio lives in the lush green of Redmond with his wife, Iwona. He doesn’t mind the gray skies
too much, but every time he has half a chance, he flies to some place on the beach, be it the
South Pacific or Camogli, his home town in Italy.
311