Modern Authentication With Azure Active Directory For Web Applications
Modern Authentication With Azure Active Directory For Web Applications
Vittorio Bertocci
PUBLISHED BY
Microsoft Press
A Division of Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399
Copyright © 2016 by Vittorio Bertocci. All rights reserved.
No part of the contents of this book may be reproduced or transmitted in
any form or by any means without the written permission of the publisher.
Library of Congress Control Number: 2014954517
ISBN: 978-0-7356-9694-5
Printed and bound in the United States of America.
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.
Acquisitions and Developmental Editor: Devon Musgrave
Project Editor: John Pierce
Editorial Production: Rob Nance, John Pierce, and Carrie Wicks
Copyeditor: John Pierce
Indexer: Christina Yeager, Emerald Editorial Services
Cover: Twist Creative • Seattle and Joel Panchot
Ai miei carissimi fratelli e sorelle: Mauro, Franco, Marino,
Cristina, Ulderico, Maria, Laura, Guido e Mira—per avermi
fatto vedere il mondo attraverso altre nove paia d’occhi.
Contents
Foreword
Introduction
Chapter 1 Your first Active Directory app
The sample application
Prerequisites
Microsoft Azure subscription
Visual Studio 2015
Creating the application
Running the application
ClaimsPrincipal: How .NET represents the caller
Summary
Chapter 2 Identity protocols and application types
Pre-claims authentication techniques
Passwords, profile stores, and individual applications
Domains, integrated authentication, and applications on an
intranet
Claims-based identity
Identity providers: DCs for the Internet
Tokens
Trust and claims
Claims-oriented protocols
Round-trip web apps, first-generation protocols
The problem of cross-domain single sign-on
SAML
WS-Federation
Modern apps, modern protocols
The rise of the programmable web and the problem of access
delegation
OAuth2 and web applications
Layering web sign-in on OAuth
OpenID Connect
More API consumption scenarios
Single-page applications
Leveraging web investments in native clients
Summary
Chapter 3 Introducing Azure Active Directory and Active Directory
Federation Services
Active Directory Federation Services
ADFS and development
Getting ADFS
Protocols support
Azure Active Directory: Identity as a service
Azure AD and development
Getting Azure Active Directory
Azure AD for developers: Components
Notable nondeveloper features
Summary
Chapter 4 Introducing the identity developer libraries
Token requestors and resource protectors
Token requestors
Resource protectors
Hybrids
The Azure AD libraries landscape
Token requestors
Resource protectors
Hybrids
Visual Studio integration
AD integration features in Visual Studio 2013
AD integration features in Visual Studio 2015
Summary
Chapter 5 Getting started with web sign-on and Active Directory
The web app you build in this chapter
Prerequisites
Steps
The starting project
NuGet packages references
Registering the app in Azure AD
OpenID Connect initialization code
Host the OWIN pipeline
Initialize the cookie and OpenID Connect middlewares
[Authorize], claims, and first run
Adding a trigger for authentication
Showing some claims
Running the app
Quick recap
Sign-in and sign-out
Sign-in logic
Sign-out logic
The sign-in and sign-out UI
Running the app
Using ADFS as an identity provider
Summary
Chapter 6 OpenID Connect and Azure AD web sign-on
The protocol and its specifications
OpenID Connect Core 1.0
OpenID Connect Discovery
OAuth 2.0 Multiple Response Type, OAuth2 Form Post
Response Mode
OpenID Connection Session Management
Other OpenID Connect specifications
Supporting specifications
OpenID Connect exchanges signing in with Azure AD
Capturing a trace
Authentication request
Discovery
Authentication
Response
Sign-in sequence diagram
The ID token and the JWT format
OpenID Connect exchanges for signing out from the app and
Azure AD
Summary
Chapter 7 The OWIN OpenID Connect middleware
OWIN and Katana
What is OWIN?
Katana
OpenID Connect middleware
OpenIdConnectAuthenticationOptions
Notifications
TokenValidationParameters
Valid values
Validation flags
Validators
Miscellany
More on sessions
Summary
Chapter 8 Azure Active Directory application model
The building blocks: Application and ServicePrincipal
The Application
The ServicePrincipal object
Consent and delegated permissions
Application created by a nonadmin user
Interlude: Delegated permissions to access the directory
Application requesting admin-level permissions
Admin consent
Application created by an admin user
Multitenancy
App user assignment, app permissions, and app roles
App user assignment
App roles
Application permissions
Groups
Summary
Chapter 9 Consuming and exposing a web API protected by Azure
Active Directory
Consuming a web API from a web application
Redeeming an authorization code in the OpenID Connect
hybrid flow
Using the access token for invoking a web API
Other ways of getting access tokens
Exposing a protected web API
Setting up a web API project
Handling web API calls
Exposing both a web UX and a web API from the same Visual
Studio project
A web API calling another API: Flowing the identity of the
caller and using “on behalf of”
Protecting a web API with ADFS “3”
Summary
Chapter 10 Active Directory Federation Services in Windows Server
2016 Technical Preview 3
Setup (for developers)
The new management UX
Web sign-on with OpenID Connect and ADFS
OpenID Connect middleware and ADFS
Setting up a web app in ADFS
Testing the web sign-on feature
Protecting a web API with ADFS and invoking it from a web app
Setting up a web API in ADFS
Code for obtaining an access token from ADFS and invoking
a web API
Testing the web API invocation feature
Additional settings
Summary
Appendix: Further reading
Index
It’s never a good idea to use the word “modern” in the title of a book.
Growing up, one of the centerpieces of my family’s bookshelf was a 15-
tomes-strong encyclopedia titled Nuovissima Enciclopedia (Very new
encyclopedia), and I always had a hard time reconciling the title with the
fact that it was 10 years older than me.
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.”
The practices and technologies used to take care of authentication in
business solutions have changed radically nearly overnight, by a perfect
storm of companies moving their assets to the cloud, software vendors
starting to sell their products via subscriptions, the explosive growth of
social networks with the nascent awareness of consumers of their own
digital identity, ubiquitous APIs offering programmatic access to
everything, and the astonishing adoption rate of Internet-connected
smartphones.
“Modern authentication” is a catch-all term meant to capture how today’s
practices address challenges differently from their recent ancestors: JSON
instead of XML, REST instead of SOAP, user consent and individual
freedom alongside traditional admin-only processes, an emphasis on APIs
and delegated access, explicit representation of clients, and so on. And if it
is true that those practices will eventually stop appearing to be new—they
are already mainstream at this point—the break with traditional approaches
is so significant that I feel it’s important to signal it with a strong title, even
if your kids make fun of it a few years from now.
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 obtaining tokens to invoke
Microsoft APIs and to handle authentication for your own web applications
and web APIs.
Modern Authentication with Azure Active Directory for Web Applications
is an in-depth exploration of modern authentication protocols and
techniques used to implement sign-on for web applications and to protect
web API calls. Although the protocols and pattern descriptions are
applicable to any platform, my focus is on how Azure AD, the latest version
of Active Directory Federation Services (ADFS), and the OpenID Connect
and OAuth2 components in ASP.NET implement those approaches to
handle authentication in real applications.
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 considerations, 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.
Who should read this book
I wrote this book to fill a void of expert-level content for modern
authentication, Azure AD, and ADFS. Microsoft offers great online quick
starts, samples, and reference documentation—check out
http://aka.ms/aaddev—that are perfect for helping you fulfil the most
common tasks as easily as possible. That content covers many scenarios and
addresses the needs of the vast majority of developers, who can be
extremely successful with their apps without ever knowing what actually
goes on the wire, or why. I like to think of that level of operation as the
automatic mode for handheld and smartphone cameras—their defaults work
great for nearly everybody, nearly all the time. But what happens if you
want to take a picture of a lunar eclipse or any other challenging subject?
That’s when the point-and-click facade is no longer sufficient and knowing
about aperture and exposure times becomes important. You can think of this
book as a handbook for when you want to switch from automatic to manual
settings. Doing so is useful for developers who work on solutions for which
authentication requirements depart from the norm and for the devops who
run such solutions.
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 anything I’ve found on the Internet at this
time: if you are interested in an in-depth case study of ASP.NET’s Katana
libraries, you’ll find one here.
This book also comes in handy for security experts coming from a classic
background and looking to understand modern protocols and approaches to
authentication—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 architectures, 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.
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).
A Microsoft Azure subscription and access to the Azure portal.
Telerik Fiddler v4 (http://www.telerik.com/fiddler).
Internet connection to reach Azure AD during authentication
operations and provisioning tasks.
In addition, Chapter 10 requires you to have access to an ADFS instance
using Windows Server 2016 Technical Preview 3. Its system requirements
can be found at https://technet.microsoft.com/en-us/library/mt126134.aspx.
For the book, I hosted my own instance in a Hyper-V virtual machine,
running on a laptop with Windows 10.
Downloads: Code samples
This book contains a lot of code, and I present some of it in the form of
guided walk-throughs. The goal is always to unveil the concepts you need
to understand in manageable chunks, as opposed to the classic recipes you
get in traditional labs or exercises. Also, I often discuss alternatives in the
text, but the code can’t always reflect all possible options. Expect the code
to demonstrate the mainline approach; where possible and appropriate,
alternatives are provided in code comments.
You can find the code I use in the book on my GitHub, at the following
address:
http://aka.ms/modauth/files
You will notice a number of repositories with the form
<ModAuthBook_ChapterN>, where N represents the chapter number in
which the repository code is described and demonstrated. (Not every book
chapter contains code; only the chapters that do have a corresponding
repository on GitHub.) If you are not familiar with GitHub, just click the
repository name for the chapter you are interested in; somewhere on the
page (at this time, at the bottom-right corner of the layout), you’ll find the
Download ZIP button, which you can use to save a local copy of the code.
We have to go deeper.
—Cobb in Inception, a film by Christopher Nolan, 2010
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 management. 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 Omer Cansizoglu—
developers on Rich’s team—those libraries would not be nearly as usable
and powerful as they are.
Danny Strockis has been on the PM team for a relatively short time, but
his contributions 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 Azure AD authentication service is for developers and maintained by
some of the finest developers I know—Shiung Yong, Ravi Sharma, Matt
Rimer, and Maxim Yaryn are the ones patiently fielding my questions and
listening to my crazy scenarios. The architects behind the service, Yordan
Rouskov and Murli Satagopan, are an inexhaustible source of insight.
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, Vijay Srirangam, Jeff Staiman, and Shane Oatman
are always there to help. Special mention to Yi Li who reviewed Chapter 8
and deals with my questions nearly every day.
Besides doing a fantastic job with ADFS in Windows Server 2016,
Samuel Devasahayam, 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 contribution from people in the development community is of
paramount importance, especially now that our libraries are open source.
Dominick Baier and Brock Allen are the most prominent sources of
insight I can think of and are a beacon in the world of claims-based identity
and modern authentication.
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.
Stay in touch
Let’s keep the conversation going! We’re on Twitter:
http://twitter.com/MicrosoftPress
Chapter 1. Your first Active Directory app
To give you a good sense of how modern identity works, this book takes you on
a roller-coaster ride, swinging from the heights of highly abstract architectural
diagrams down to nitty-gritty details of implementation and protocols. There
will be time for all that later, though. In this chapter, I provide some immediate
gratification by walking you through the simple task of using Active Directory
to protect a web application—without worrying at all about the underlying
principles at play or the code necessary to implement them. Later chapters will
give you more insights about what’s really going on: you’ll be able to come
back to this example, reinterpret what you see in light of your new knowledge,
and tweak things to your specific needs. For now, we’ll just have some
uncomplicated fun.
Prerequisites
The use of cloud services and of highly integrated tooling such as Microsoft
Visual Studio allows me to keep the list of requirements very short.
Microsoft Azure subscription
The scenario requires a Microsoft Azure subscription to host the Azure AD
tenant and allow development against it. In fact, to try the code samples for
yourself, you’ll use your Azure AD tenant over and over again throughout this
book.
Developing against Azure AD is free as long as you stay below 500,000
objects. You can get a Microsoft Azure free trial and, assuming that you don’t
use any other paid services, you can try everything you’ll find in this book
without spending a dime—excluding electricity and Internet connectivity bills,
of course.
At the time of writing this chapter, you can set up a Microsoft Azure trial by
visiting http://azure.microsoft.com/en-us/pricing/free-trial/. I won’t provide
detailed setup instructions here because the process has likely changed since the
book went to press. However, setup should be pretty simple. Please note that
you must successfully complete the process and have a valid subscription to
follow along with the samples. All the other setup steps depend on you having a
subscription available, so you need to take care of this right away.
Of course, if you already have a Microsoft Azure subscription, you are most
welcome to use it here: there’s no need to set up a new one.
Note
Go back to Visual Studio and press F5. The first thing you’ll see in the
browser is an Azure AD sign-in page prompting you for your Azure AD
credentials, as shown in Figure 1-2.
Figure 1-2 The Azure AD credentials prompt.
Enter any valid credentials from your Azure AD tenant, and then click Sign
In. You are presented with the consent prompt shown in Figure 1-3. Because
you are running this application for the first time, Azure AD informs you about
the privileges the application needs to acquire in your directory to perform its
function. In this case it is requesting the bare minimum—the abilities to sign in
the user and gather basic information.
Figure 1-3 The Azure AD consent page for the test application.
Click OK. That frees Azure AD to conclude the authentication flow and
return the results to the application, which will sign in the user. In Figure 1-4
you can observe the results.
Figure 1-4 The application home page after authentication.
This is all very straightforward. When it works well, identity is quite boring
and uneventful!
This walk-through demonstrates that you don’t need to read an abstruse book
cover to cover to take care of the most fundamental authentication scenario. As
things become more complicated later on, don’t forget that digging deeper is a
choice, not a requirement!
Place a breakpoint on the last line, and run the application. You’ll see that
you end up with the expected values in your welcome string.
Note
The first name/last name sequence won’t work for every culture.
That does not change the value of the code snippet as a
demonstration of the programming model, but I wanted to be sure
to point this out.
Note
Before I close the chapter, there are a couple of other things about user
attributes that I want to tell you.
The first is that you should be prepared for every provider to send a different
set of claims. If you list the entire content of the claims collection you received
in the sample application, you will have an idea of the attributes that Azure
Active Directory includes in the context of an authentication operation. Every
Azure AD tenant will consistently issue the same set of claims, but that is not
true if you are authenticating users directly against their on-premises directory,
via Active Directory Federation Services (ADFS) or any other identity product.
The set of claims issued by ADFS during an authentication operation is
determined by what the directory administrator has decided to share with your
application. Although it is plausible to expect that some claims will be present
most of the time (UPN, names, and so on), the reality is that what claims are
available is a completely arbitrary decision by the administrator, with no
defaults you can rely on.
Note
Getting back to Azure AD. I cover this in depth later in the book, but I want
to give you a heads-up at this point: ClaimsPrincipal is not the only way
of accessing user info in Azure AD. The claims your app receives contextually
to an authentication operation are not everything that Azure AD knows about
the current user. The directory records a far richer set of user information;
however, including all of it at authentication time would be impractical and
would waste a lot of bandwidth. Azure AD provides a specialized API,
commonly referred to as the Directory Graph API, for gaining access to extra
information after authentication takes place. You’ll learn more about this as you
go along.
There is more to be said about ClaimsPrincipal, in particular about its
structure and usage. For the time being, the preceding discussion should be
enough to get you going for the most common scenarios.
Summary
This lightweight chapter started our journey into the world of modern
authentication. You acquired two of the key assets you’ll need through the
book, an Azure AD tenant and Visual Studio 2015, and you had a taste of what
you can achieve with them. The simple web application protected by Azure AD
you created in this chapter will be the backbone of many future explanations.
Finally, you learned the conventional ways in which .NET makes available to
you the user identity information acquired during the authentication phase.
The next couple of chapters will be decidedly more abstract. Hang in there.
Code-level considerations will return—with a vengeance—in just a couple of
chapters.
Chapter 2. Identity protocols and application types
If experience serves me well, this chapter and the next will likely cost me
one star on Amazon. That’s because I’ll write quite a bit without showing
any code, a mortal sin in a book for developers. Still, I believe that this
chapter is the fulcrum around which the entire book turns, especially for
readers without a background in identity. I like great reviews, but I like my
readers more; hence, I’ll go ahead and write this chapter anyway.
Note
Note
The application associates that secret to the set of attributes defining the
user for its purposes. If during a future transaction the user presents that very
same secret, the app knows that the identity that should be selected is the one
previously associated with that secret—and voilà, that’s authentication for
you.
Note
Claims-based identity
No company is an island, as the old adage goes. The need for cross-company
collaboration solutions was the natural follow-up to the local network era,
but the local authentication solutions that worked so well within the
boundaries of one organization were not as effective in addressing the new
scenario. Company A and company B could both have well-managed
intranets, but company A’s domain controller didn’t help one bit when one of
company A’s users wanted to access an app exposed by company B.
Moreover, the increasing appeal of software as a service (SaaS) apps or
hosting one’s own applications off-premises—hosts at first, cloud providers
later on—further exposed the limits of an approach based entirely on
network locality. Consider this: if your employees log in to their domain-
joined workstation, but the apps they access live on other networks or the
public Internet, all the user identity info known by the DC cannot be used as
is. Nonetheless, business apps still need the identity of the user, and without
a way of making it available via infrastructure, it’s back to a cottage industry
in which every app reimplements identity management.
The industry did not tolerate for long the inefficiencies of having to
reinvent the wheel at every new partnership or purchase of an SaaS app.
Although different people place its inception at different historical moments,
I think it is safe to say that claims-based identity was born to address these
issues. Claims-based identity is not an actual protocol. Rather, it is a set of
concepts that is common to many of the identity protocols that emerged in
the last decade. In this section I’ll sketch its main traits, the ones that—if you
squint hard enough—you can find in all modern protocols. The following
sections will show how the principles of claims-based identity find
application in actual protocols.
The key point is to let go of the idea that you can have a single,
omniscient authority that knows every possible actor and can broker all
transactions on all possible networks. Such an authority can exist in the
limited scope of a local network, but it is simply unfeasible on the Internet.
More realistically, we should acknowledge that there are constellations of
multiple authorities run by independent business entities, the competence of
each scoped to a specific user population. All the Active Directory instances
out there are good examples of that: Contoso’s AD instance can tell you
whether Mario is a Contoso employee, his job title, and so on, but it can’t
really say much about whether someone works for Fabrikam. There are other
user populations that naturally aggregate through different criteria, with
different degrees of presence on the Internet: customers of a given company,
students of a school, members of a club, citizens of a country, and so on.
In the literature you’ll often find that such authorities are referred to as
identity providers, or IPs (or IdPs). At the level of detail I’m providing at this
point in the story, IdP is a fine moniker. However, later in the book, things
become more nuanced, and the notion of identity provider will not be
general enough for our goals. At that point I will switch to the broader term
authority. (Be prepared for other terms in this chapter to be renamed or
redefined later on, when you’ll have more context to build on.)
We say that an application trusts a given IdP if the application believes
what the IdP has to say about the users the app wants to work with. (An
application that trusts an IdP is often referred to as a relying party, or RP. I
will keep using “application” here, though, given that I feel it is easier to
understand in this context, but I wanted to be sure you are aware of the
term.)
A trivial case exemplifying the above is the home network of a domain.
Each and every application on the intranet that uses integrated authentication
trusts the domain controller. If you took one of those applications and lifted
it to the public cloud, conceptually it would still trust the domain controller:
the business requirements have not changed, and the domain’s users remain
its intended audience. In its new environment, however, the app can no
longer rely on the network infrastructure to find the DC and request its
services. The trust is still there, but the infrastructure described so far is no
longer enough to express it.
After postulating the existence of the role of the identity provider, the next
problem to solve is how to ensure that one IdP’s authority is acknowledged
by the interested parties in a transaction—without the luxury of operating
within a closed network.
Tokens
The details are different for every protocol, but the essence of the solution to
the problem of extending one authority’s scope beyond infrastructural
boundaries is the same across the board:
Be sure that the IdP can be easily identified by every application. That
boils down to describing the IdP through formal means and making
that description available for any entity that wants to work with that
IdP. Unique string identifiers, specific endpoints, and cryptographic
public-private key pairs are the standard arsenal to do that. The set of
coordinates formally identifying an IdP are commonly known as
metadata.
Represent the outcome of an authentication operation with some
artifact that can be unambiguously tied to the IdP that performed the
authentication, without relying on any special network infrastructure.
The information in the formal IdP description mentioned just above is
used to identify such an artifact as coming from that IdP, through
mechanisms that I will mention in a moment.
To be more concrete, say that you want to use Contoso as an IdP. You
assign to Contoso’s DC an X.509 certificate and a unique identifier,
something like http://contoso.com—both technologies that do not require
any special network settings to function. When Mario, a Contoso employee,
authenticates for accessing an app running in the public cloud, the DC
produces a string along the lines of “I, http://contoso.com, certify that Mario
is an employee and that at 9:05 a.m. he successfully authenticated for
accessing application A.” The DC uses the private key associated with the
X.509 certificate to digitally sign the string. The application receives the
string, verifies the digital signature, and confirms it has been performed with
Contoso’s key. If everything works as expected, the application believes the
assertions it finds in the string and lets Mario in.
How did the application know about Contoso’s key and identifier? It
learned about those values previously in one offline step, from Contoso’s
metadata. I’ll dig into this scenario soon.
Digital signatures
Oversimplifying a bit, a digital signature is an operation that
combines a document with a certain string (called a key) to
generate a third string, called a signature. If somebody who
knows the key receives the document and its signature, she can
repeat the signature operation and verify that the resulting string
is exactly the same as the signature. That guarantees that the
document was not modified after the original signature was
computed.
Signatures are a pretty great technology that makes a lot of
today’s secure communications possible. However, in the form
I’ve described (where the keys are known as symmetric), they
have an important shortcoming: given that both the signer and
the signature verifier know the signing key, we can’t use the
knowledge of the key as a way of distinguishing one from the
other.
X.509 certificates enable what is known as public key
cryptography, which uses two keys. Signatures are applied with
a private key, known only by the signer. Such signatures are
special, as they are meant to be verified by using a different
key: that key is called public because everybody can know it
without compromising the security of the system. I will use
public key cryptography all the time throughout the book, but I
will rarely point that out. It is mostly an implementation detail,
not actionable for you.
Note
Set aside for a moment the mechanics of how a requestor can get a token
from an IdP. A token as I’ve described it is a satisfying representation of the
successful outcome of an authentication operation, which can be verified
simply by knowing the IdP’s coordinates (identifier, signature-verification
key) without requiring any special network sauce. In fact, a token can do
much more than that.
Note
Claims-oriented protocols
Let me summarize the story so far.
An identity solution was needed that would allow applications to run
anywhere, without completely giving up on the investment made in DCs and
local domains. We recognized that DCs are just concrete instances of a more
abstract role, the IdP, which represents an authority that knows about users
(attributes and credentials). We devised a mechanism for identifying IdPs via
identifiers and keys (metadata), breaking free of the network restrictions of
DCs. We invented a new artifact, the token, to make the outcome of an
authentication operation verifiable (via signature validation) by any app
knowing the IdP’s coordinates. Finally, I explained what trust between an
app and an IdP means, and how this allows claims (attributes traveling in a
signed token) to provide to the application just-in-time user-identity
information right at authentication time.
The main concept left to define is how these entities interact with each
other—what messages should be exchanged and in what order—to take
advantage of all the good properties we identified and that make
authentication happen.
As I anticipated a few pages ago, claims-based identity refers to a bunch
of different protocols sharing a common undercurrent—they make
authentication happen through boundaries. Those protocols differ from one
another in various aspects: for example, in the token formats that they
mandate or prefer, the exact message shape and sequence, metadata formats,
names they assign to the roles that identity-transaction participants can play,
and more. Every protocol has its zealots who swear their approach is the best
and insist on renaming common concepts using their own terminology—a
handbook case of Freud’s narcissism of small differences. For now I am
going to ignore all that and paint in very broad strokes the main legs that
nearly all claims-oriented protocols must specify.
Say that you have Mario, an employee at Contoso, and he wants access to
an expense note application from Fabrikam. Fabrikam is a software vendor
offering SaaS solutions running in the cloud. Here’s how claim-based
identity would go. The steps are summarized in Figure 2-3.
Figure 2-3 Entities, roles, and messages come into play in claims-based
identity.
1. The application reads an IdP’s metadata Typically, this step
happens out of band, although there are exceptions. Fabrikam decides
that its expense note application should trust Contoso (as a
consequence of Contoso buying a few licenses, for example). In
concrete terms, this means that the application must access Contoso’s
IdP metadata and be sure that its content will be available later, at
authentication time. That will provide to the application the
information it needs to verify whether the incoming token is truly from
Contoso.
2. The user authenticates and obtains a token This step will be
different depending on the type of application. Good portions of this
book will be dedicated to filling in details about how some key
protocols perform this task.
a. Web applications In classic browser-based apps, Mario would type
the address of the expense note app and navigate there. The app
would detect an access attempt from an unauthenticated requestor.
The canonical reaction to that would be to look up its own
configuration, find Contoso’s metadata, and use it to craft a sign-in
message according to the message syntax defined by the protocol of
choice. The browser’s session would then be transferred to Contoso,
which would take the necessary steps to authenticate Mario:
showing a credentials-gathering page is a common way of
implementing that. Upon successful authentication, Contoso issues a
token for Mario in the format that the protocol of choice dictates.
b. Native clients and web APIs Native clients typically pursue a
different strategy. Redirects don’t really work when calling an API
(usually, there is no browser to execute the redirect); hence, even
before attempting to access the API, the client will use a different
protocol for asking Contoso for a token for Mario. The sequence is
slightly different, and the exchanged messages also differ, but a
successful authentication still results in a token issuance.
3. Client sends the token to the application, and the app validates the
token The token so obtained is sent to the application according to the
protocol of choice. The application (or, more likely, the developer
libraries it uses to implement authentication) retrieves the token and
verifies whether it is a valid Contoso token. If that’s the case, it will
extract the claims describing Mario’s persona and pass them to the app
so that it can do whatever the app needs to do with them: welcome
messages, authorization operations, priming of a shopping cart with a
shipping address, whatever.
4. Optional: Application establishes a session For applications such as
websites, where the user interaction consists of round trips, going
through the token-acquisition dance at every request would be
fantastically expensive. That is why various web protocols very
commonly react to the first successful token validation with the
creation of some kind of session—represented by a cookie or similar
mechanism—that can be included in every subsequent request. Such a
session reminds the application at every round trip that a successful
token validation already took place and provides a natural place for
holding the claim values of the current user, keeping them available to
the app through the lifetime of the session.
Recall that the goal of a claims-oriented protocol is to enable identity
transactions to span boundaries, both organizational (Contoso to Fabrikam)
and infrastructural (intranet to public Internet). As such, all the messages I
enumerated cannot depend on any special network requirement. Rather, they
rely on the minimum common denominator of all networks, the public
Internet: every claims-oriented protocol with meaningful adoption is built on
top of HTTP. In the next sections, you’ll see this in more detail.
Note
SAML
The Security Assertion Markup Language, SAML for short, appeared on the
scene mostly for handling this very problem. Its origin dates back to the
early 2000s as a concerted effort of various industry players that wanted to
establish an interoperable solution to the SSO problem. SAML 2.0 is the
most widely adopted version, with some systems (especially those in
academia) still on 1.1. Although SAML touches on how to secure web
services and lots of other scenarios, its most widely adopted use case is web
browser–based SSO, and that’s what I’m going to focus on.
Note
Roles
I am sure you noticed that the sample scenario I introduced earlier contained
one entity playing the role of the IdP (that was airline.example.com and its
profile store). The good news is that in SAML, IdPs are called . . . IdPs.
In the terminology of claims-based identity, the cars.example.com.uk
application is called an RP. In SAML, it is known as a service provider, or
SP. Another important role is the subject, the entity that is meant to be
authenticated. In the vast majority of cases, that’s simply the user. SAML
also describes other roles, but the ones I’ve enumerated suffice for the
purposes of this book.
Artifacts
SAML is guilty of having introduced not one but two widely successful
technologies: the protocol it defines and the specific token format that the
protocol’s messages exchange. I say “guilty” facetiously: people commonly
refer to both technologies with the same term, “SAML,” which has caused
confusion for the past decade or so. When somebody states, “My app
supports SAML,” you always have to ask for clarification: “The protocol or
the token format?”
In SAML parlance, tokens are called assertions. They follow the exact
token semantic described in the preceding section: they are a vessel for the
IdP’s assertions about the user (excuse me), the subject. And they are signed.
The SAML acronym, together with the epoch in which it was conceived,
probably already gave away that SAML assertions are based on XML. In
fact, the entire specification defines everything in terms of XML. That leads
to a very expressive, powerful format that can represent pretty much
anything. However, all that expressivity comes with various drawbacks. The
main one is that XML is very verbose, which leads to big tokens.
Furthermore, in XML, the same document can be expressed in multiple
equivalent representations, and that flexibility becomes a problem when you
need to perform signatures, where two elements listed in a different order
can break a signature verification. Those are the main reasons that you won’t
encounter SAML assertions in modern protocols later in the book, apart
from cases in which they are used to bridge existing solutions to new ones.
Note
Messages
SAML defines lots of different messages that support various sign-in flows,
from the one triggered by an unauthenticated request to an SP (similar to
what’s described in the claims-identity section), to one in which the IdP itself
initiates a sign-on with a given SP. One interesting fact is that besides
signing its assertions, SAML often mandates that messages themselves need
to be signed as well.
The other interesting category of SAML messages, Single Logout, focuses
on providing a mechanism to propagate a sign-out operation to all the
applications participating in an SSO session. SAML defines many other
messages for various other operations, which I won’t mention here.
Status
SAML has had an impressive ride from its first versions in the early 2000s.
It’s still going strong in many of today’s SSO deployments in enterprises,
government, and education. SAML is widely supported in SSO products,
developer libraries (across platforms and languages), and cloud services. For
many of those products, the SAML functionality is the centerpiece of their
offering. As I mentioned, Active Directory itself (both ADFS from version 2
onward and Azure AD) supports it. On the software vendor side, many
applications in active development today use SAML, including software as a
service (SaaS) apps. The protocol is alive and well.
That said, if you are starting to develop a new solution, SAML might not
be your best choice. Although really well suited for solving the cross-SSO
domain problem and bringing lots of good features to the table, SAML does
not offer the flexibility for addressing the challenges of the modern
topologies I will introduce later in this chapter. Furthermore, its own
richness translates into expensive requirements in term of cryptography and
bandwidth that are not proportionate to the actual needs of modern
applications. I won’t go so far as to say that SAML is dead, as was
fashionable to say in identity circles a couple of years ago, but it is certainly
no longer the recipient of innovation. I believe it will be around for a long
time still, but mostly as a bridge to existing systems.
WS-Federation
Web applications weren’t the only type of application that suffered from
cross-boundary integration problems back in the early 2000s. Nonbrowser
flows between remote components, such as server-to-server requests and
calls from rich-client applications to back-end resources, also had to come to
terms with the facts that any two entities could be separated by
organizational and network boundaries, based on different development
stacks, and hosted on different platforms.
That prompted a number of companies to set aside their competitive
differences and work together to create a set of protocols, languages, and
frameworks that defined how to ensure interoperable, reliable, and secure
communications between software components regardless of their location,
development stack, hosting platform, and similar factors.
This effort led to the creation of a long list of specifications, collectively
known as WS-* (pronounced “WS star,” where WS stands for “web
services” and the asterisk is a wildcard character). You might have heard the
names of some of the most important specifications: WS-
ReliableMessaging, WS-Trust, WS-Security, and many others. The idea was
to provide different specifications for every aspect of communications so
that implementers could pick and choose only the capabilities their system
needed. That was in contrast with some earlier efforts, such as CORBA, that
were delivered as monolithic, monumental uber-specifications.
What Happened to WS-*?
Today, you don’t hear much about WS-* anymore.
Companies poured significant effort into implementing those
specifications in their products. Microsoft led the pack, building
its remote API stack on it from .NET 3.0 on (Windows
Communication Foundation, WCF, is largely based on WS-*)
and exposing its server products through those protocols (ADFS
2.0 supports WS-Trust). Other companies, notably IBM and
Sun, released products and development stacks based on WS-*.
Many of those applications are still around, and, in fact, lots of
customers still use WCF for brand-new applications.
However, WS-* lost traction, and nowadays all of our new
work relies on more modern, REST-based protocols. One can
endlessly speculate on the reasons that led to the demise of WS-
*. My read is that WS-* provided very sophisticated features,
but the price that this complexity carried was not justified for
the kinds of apps most developers wanted to build. For
example, WS-* went to great lengths to enable messages to be
exchanged securely over insecure channels and maintain
integrity also after having exited the channel. Using the feature
required rich development stacks on both ends of the channel
and elaborate setups, which could be justified only in specific
high-value scenarios. Most web apps could live without those
high assurances. As a result, the lightweight REST model
gained ground, eventually making inroads in the business
scenarios and supplanting the old models.
Roles
In WS-Federation, the IdP role is indicated by the same term, identity
provider. However, it is abbreviated as IP.
IP and STS
The WS-Federation specification also refers to a Security Token
Service, or STS, which represents the concrete software artifact
that actually processes authentication requests and issues
tokens. In the literature, you will often hear people use IP and
STS interchangeably, but that’s a slight misnomer: whereas IP
indicates a functional role (the job performed by that entity), the
STS is a concrete component that is used to express that role
(the tool that entity needs to perform its job). Given that every
IP must have an STS, one can often confuse the two in a
conversation without serious consequences. However, you’ll see
that there are times when you need to issue tokens without
being an IP, in which case you need an STS that is not used to
implement an IP.
Messages
WS-Federation defines messages supporting standard sign-in and distributed
sign-out operations. Those messages are simpler than their SAML
counterparts. For starters, they do not require any cryptographic operation at
the message level—the only signed (and possibly encrypted) element is the
token itself (for the flows that contain one).
All messages exchanged between the RP and IP rely on a combination of
302 redirects and autoposting forms. You won’t ever use WS-Federation
directly while developing the apps described in this book. However, WS-
Federation is still very much in use as an integration protocol, so you will
often see it in action in network traces. For that reason, it’s good to have at
least an idea of what it looks like. Here’s the simplest sign-in flow you can
enact in WS-Federation, which is illustrated in Figure 2-5.
Figure 2-5 An example of the basic sign-in flow with WS-Federation.
1. The user navigates to the RP application, performing an HTTP GET of
one of its protected pages.
2. The RP (or more likely the development library that sits in front of it
and enforces the use of WS-Federation) detects that the request is from
an unauthenticated user. Instead of returning the requested resource, it
returns a 302 code that redirects the browser to the IP. The query string
of the redirect contains various parameters supplying the IP with
context about the request: an indication that this is a sign-in request,
the identifier with which the RP is known to the IP, and so on.
The IP does whatever it deems necessary to authenticate the user. In
the case depicted in Figure 2-5, the user is accessing the IP within the
boundaries of the local network; hence, the request is automatically
authenticated via integrated authentication.
3. Upon successful user authentication, the IP sends back a signed token
with claims describing the user. The token travels in an HTML form.
The response also contains JavaScript code that will automatically post
the token back to the RP.
4. The RP receives the IP response; as it renders, it triggers the POST of
the token back to the RP.
5. The RP receives the token and validates it. If the verification succeeds,
it creates a session cookie to establish a session. From this moment
onward, every request coming from the user’s browser will carry the
session cookie and will be considered authenticated by the RP. The
session will terminate once the user explicitly signs out (another WS-
Federation flow) or once the session expiration time elapses.
Not especially complicated, right? This simple sample provides two
important confirmations:
The claims-based identity approach does successfully cross
boundaries. Here, the user of a local network gains authenticated
access to one RP located outside his or her domain.
WS-Federation, and claims-based identity in general, work well in
collaboration with older protocols. In this case, the user does not
experience any explicit authentication prompt in step 2 thanks to the
existing local network infrastructure. There is no visible sign that all
this dancing is taking place. The user types the address of the RP, and
the next thing he or she sees in the browser is what gets rendered after
step 5—the authenticated experience of the RP application.
Status
Microsoft bet on WS-Federation as a web sign-on protocol early on, from
the very first version of ADFS back in 2005.
That initial choice created ripples through all its offerings. ADFS kept
supporting WS-Federation through all subsequent Windows Server versions,
including the latest one (Windows Server 2016). WS-Federation was the
protocol of choice in the first developer libraries for identity (Windows
Identity Foundation 1.0, in 2009), it was the protocol integrated in the .NET
Framework from version 4.5 onward, and it is still supported today in the
latest ASP.NET OWIN middleware components that I’ll cover later in this
book. Tools for adding WS-Federation support were included in Visual
Studio 2010, 2012, and 2013. In turn, that determined the protocol of choice
of a generation of servers and cloud services, from SharePoint 2013 on.
Office 365 and Azure AD itself use WS-Federation as the backbone of their
on-premises / cloud integration flows. Every day there are new installations
appearing, all using WS-Federation under the hood.
With its widespread presence, you can imagine that the protocol will keep
being supported for a long time. On the other hand, the same considerations
I offered for SAML apply here as well. WS-Federation does a great job of
allowing tokens (hence claims) to flow outside the boundaries of a local
network, and does solve the cross-domain SSO federation problem.
However, WS-Federation is incapable of modeling many of the topologies
and relationships that are required for addressing the challenges of modern
application architectures. As a result, you can consider WS-Federation
“done”: it is a mature protocol, safe to be used in production, but is no
longer a recipient of innovation. In the next sections, I will finally breach the
modern era, introducing the protocols that are best suited for new apps.
Note
That is, of course, all kinds of wrong. Where should I start? Forsaking
your credentials gives the recipient too much power. What if instead of
simply accessing the resources the recipient declared it wants, it accesses
everything else? Changes things? Does bad things on your behalf? And, of
course, there is the matter of the increased risk. Nothing prevents the
recipient from storing your credentials with insufficient care, exposing you
to the possibility of leaks and various other disasters. And just to close, even
if most apps are honest and perfectly secure, this approach teaches users bad
habits, training them to disclose their credentials in multiple contexts, with
great risk.
The need for granting access to resources across applications was not
going to go away, but the brute-force solution simply could not cut it.
Note
Status
OAuth2 is now completely mainstream, with widespread support from
vendors large and small, ubiquitous presence in cloud and server products,
and support in practically every relevant web programming stack. At this
point the protocol is stable enough to be confidently used in all kinds of
applications, while at the same time it is still evolving for supporting new
scenarios. The subtleties about interoperability I mentioned earlier are often
a source of confusion and inflated expectations on what it means for an app
to support OAuth2—“I know that A and B both speak OAuth2. That means
that I can get any OAuth2 library and use it for making A and B
communicate right out of the box, right?” Not right out of the box, pal, but
close.
All of the developer libraries, tools, services, and server products you’ll
learn to use in this book make use of OAuth2 in one way or another.
Note
Note
Hybrid flow
As you’ve seen, OAuth2 teaches apps how to be clients—to obtain tokens
meant to be consumed by the resources the app wants to access—but the
information in those tokens cannot be directly accessed from the app itself.
Conversely, sign-in protocols such as SAML or WS-Federation produce
tokens meant to be consumed by the app itself so that it can verify that
successful authentication took place and extract user information.
In the so-called hybrid flow, OpenID Connect combines these two
approaches. It augments the classic OAuth2 flow described in Figure 2-7 by
injecting in legs 2 and 3 an extra token (the ID token) specifically meant to
deliver to the application verifiable information about the authentication
operation that just took place. This allows you to both sign in a user and
obtain delegated access to a resource, all within the same transaction. Pretty
neat.
To make the new flow viable, OpenID Connect had to become
prescriptive about the ID token: what format it should be encoded in, the
exact information it should carry, what checks should be performed to
establish validity, and so on.
SAML tokens, the default currency for the older sign-in protocols, was off
the table here. Their size and complex validation rules did not fit the
requirements for simplicity and compactness imposed by the OAuth2
portion of the flow. Luckily, there was another, better-fitting token format
circulating in the wild: the JSON Web Token, abbreviated JWT (and
pronounced “jot”). As you work through the hands-on chapters of this book,
you will become intimately familiar with this format. For the time being, it
should suffice to say that JWT provides SAML-like expressive power (it’s a
great vessel for transporting claims; standard signature and encryption
algorithms; the usual mechanisms for specifying audience, issuer, and
intended validity period; and so on) at a fraction of the size. Just as
important, JWT does not require very sophisticated cryptographic
capabilities from its producers and consumers.
From SWT to JWT: A brief history of lightweight token
formats
Remember OAuth WRAP, the “missing link” protocol that
bridged the evolution of OAuth1 through OAuth2, described in
the sidebar “OAuth and OAuth2: A bit of history”?
Whereas OAuth1 was largely meant to help companies
expose their own APIs—hence coalescing the authorization
server and resource roles in a single entity—OAuth WRAP was
conceived by companies that acted as custodians of other
company’s resources. To take a practical example from the
modern world, Azure Active Directory can be used to protect
calls to the Office 365 API (in which case, the authorization
server and resource belong to the same business owner), but it
can also be used to protect your own custom web API.
(Microsoft provides the authorization server, but the resource is
your own.) This is simply another facet of the phenomenon
described in the section “OAuth2 and claims.”
In their attempt to address this situation and counter the
vagueness of the original OAuth, the author of OAuth WRAP
introduced an explicit token format as part of the core
specification: the Simple Web Token, or SWT. The SWT was
indeed exceptionally simple, just a set of HTML form-encoded
name/value pairs signed by one simple algorithm.
I use the past tense because today SWT is all but dead. When
the OAuth working group decided to take OAuth2 WRAP as the
foundation of OAuth2, its members decided that imposing a
token format was not in line with the spirit of OAuth (teaching
apps how to be clients—and for clients, access tokens are
opaque), and SWT missed its opportunity. You can still observe
pockets of usage of SWT in the real world (Azure Access
Control Service, ACS, uses it for all its management API and
REST workflows), but those are all remnants of early
implementations, still around for honoring support terms but all
unequivocally fading into the sunset.
Naturally, SWT did not make the real-world requirement for
a lightweight token format go away. Evolution took its course,
and at least two new formats, both based on the lightweight but
expressive JSON, independently emerged around 2010. One
was the JSON Tokens, from Google’s Dirk Balfanz. The other
was JWT, from Microsoft’s Mike Jones and Yaron Goland. The
various parties agreed to unify the efforts, and JWT was picked
up by the working group, where it went through multiple drafts
and authors from multiple companies (Microsoft, Google, Ping
Identity, NRI, Facebook, and many others).
When OpenID Connect needed to specify a token format for
its ID token construct, JWT was the obvious choice.
The hybrid flow is represented in Figure 2-8. The sequence is the same as
described in the earlier section, with some extra operations.
Status
OpenID Connect is the area where most of the innovation is taking place
these days, no matter what vendor or platform you pick. Its ability to support
both sign-in and API-invocation scenarios in the same application makes it
well suited for addressing the requirements of today’s solutions. It is the
protocol underlying most Active Directory flows, and it’s going to be the
protocol that fuels all the scenarios I’ll walk you through in this book.
I can almost hear you protesting. “Didn’t you just say the same about
OAuth2?” The fact that OpenID Connect is basically OAuth2 with just a
bunch of extra details makes things a bit confusing. In the literature you’ll
often find that people refer to OpenID Connect when they talk about web
scenarios and stick with OAuth2 when referring to server-to-server flows or
native applications. In fact, even in the latter scenarios you’ll often find that
it’s almost never classic OAuth2—OpenID Connect fills in the details of so
many important functional areas left unaddressed by the original OAuth2
that you’ll almost always end up using at least one or two of its extra
parameters.
More API consumption scenarios
Let’s take a small detour from our history of user-authentication techniques
and consider for a moment a couple of resource-consumption patterns that
are very common in enterprise scenarios.
Single-page applications
Let’s resume the narrative arc that brought us through the ever-escalating
conflict between evolving authentication requirements and the protocols that
emerged to satisfy them.
Although we saw application topologies change a lot from one generation
to another, one thing remained the same: the round-trip-based request-
response pattern underlying every web application with a user interface. We
take that idea for granted, so you might not think about it too often, but it’s
worth spending a moment teasing it apart.
Web applications run entirely on the server. Their code is expected to
implement both the presentation-generation logic and whatever computation
(old-fashion professionals like me would be tempted to call this “business
logic”) is required for performing the function the apps are meant to offer. In
practice, the web app you use for your tax-filling duties spends CPU cycles
both for sending down the HTML rendering the forms you use to fill in your
hard-earned numbers and for crunching the same number according to the
tax scheme in fashion for the year.
This mechanism is what made cookie-based sessions so handy for web
sign-on. Given that every interaction with the app is actually a full round
trip, having a cookie along for the ride is a handy way of reminding the
server that it’s still you, the authenticated user, at the other hand of the cable.
This pattern does have significant downsides. I am ready to bet that you
are no stranger to Facebook, Gmail, or Outlook.com. All those apps have
high-density interfaces, crowded with lots and lots of semi-independent
pieces of information. Whenever you interact with one element, like
expanding a comment thread or selecting one email message, you trigger
changes at the local level. The comment thread expands, moving the rest of
the content down; the email body appears in the main area, while the email
entry shows a “selected” indicator. A large portion of the screen remains the
same.
Think of how wasteful it would be to implement this functionality via a
classic round trip. As soon as you click, the browser would have to tear
down a complex user interface and send a request for the new information
and enough data to reconstruct the current state. Once the server is done
processing your request, it has to send back all the code the browser needs to
reconstruct the entire scene: the parts that changed, but also the parts that did
not. The performance of such an app would be pitiful, and the waste of
resources criminal.
The last few years have seen the rise of a new approach to web
development that provides an elegant and efficient solution for architecting
these kinds of applications. The idea is simple: instead of expecting the
server to handle both presentation and back-end logic, the architecture
separates the two and distributes the work between the browser itself and the
server.
Modern browsers are far more than glorified markup renderers. From
simple origins, the JavaScript language has evolved into a powerful
programming platform that can implement very sophisticated logic entirely
on the client. A developer can now code the presentation layer of one app
entirely in JavaScript: logic-layout management, data binding, dynamic
updates, state changes, and more can now be bundled with one initial HTML
page (or a few more) and sent down at the very first request to the
application. From that moment on, all the UI behavior can be handled
without having to flush the entire browser state and perform a round trip.
That’s why this app architecture is often referred to as single-page
application, or SPA. In theory, your whole app can live in one single page,
the first one, and all the JavaScript files it references.
That sounds fantastic, but clearly something is missing. Thank you,
JavaScript, for having rendered all those tax forms, the experience was
amazingly fluid. But now that I have filed my numbers, how am I going to
deliver them to the tax-crunching logic on the server side?
Simple. JavaScript also allows you to perform programmatic HTTP
requests to the back end. If the back end exposes a web API, the front end
can invoke it via JavaScript, read back the results, and use them to
selectively update the UI. In the Facebook example, clicking a comment
thread can trigger JavaScript logic to perform a request to the server for the
text for all those comments, parse them back, and display them on the page,
without having to touch the UI elements around it. In the tax-return example,
clicking Submit sends all your numbers to the server, which crunches them
and sends back to the client a “total due” number, which the same JavaScript
logic can display in a new text box, once again without having to do
anything with the rest of the UI.
This is a very neat architecture. How do we secure it? Sticking with
cookies is tempting. The browser will automatically attach cookies to every
request heading to the domain a cookie is associated with, and that holds for
JavaScript-generated requests as well. That might work for testing or quick
and dirty prototyping, but as soon as you get serious about your app, the
limitations of this approach become evident:
Cookies go only to the domain from where they originated; but
technically, your JavaScript code might call any API, including APIs
hosted on other domains.
Cookie-based sessions are really children of round-trip applications.
What happens when a cookie expires when working with a web app
protected by WS-Federation or SAML? The app deems the caller
unauthenticated, so it reacts with an HTTP 302 request and a sign-in
message. In a round-trip app, that 302 will be immediately executed by
the browser, prompting the user to authenticate. In an SPA, however,
that won’t happen: a 302 return code is really not actionable for a
JavaScript web API call. Sure, you could write more logic that makes
the redirection happen, but in the process you’d flush whatever client-
side state the app built up to that point. You could prevent that as well
by saving everything, but doing so can get messy.
The solution is surprisingly simple. You secure web API calls just as I
described for other topologies—with tokens. The missing link here is how
do you enable JavaScript to obtain and use tokens? OAuth2 introduced, and
OpenID Connect refined, a special grant precisely for this scenario: it’s
called the implicit grant.
In the implicit grant, an application can request an access token directly to
the authorization endpoint without any interaction with the token endpoint.
The token itself is returned in a URI fragment, which is fancy HTTP jargon
to indicate a string in a URI after the # symbol. Such a string is meant to be
visible only to the browser itself (and everything that runs within it, like your
JavaScript code) and won’t be sent to the server. The JavaScript can retrieve
the token bits and squirrel them away, typically by saving them in some
HTML construct (sessionStorage and localStorage being common favorites).
Once the token bits have been obtained, more JavaScript logic can attach
them to the requests whenever there’s the need to contact a back-end web
API. That is a little more work than letting the browser automatically attach
cookies, but it grants far more control to the developer. Moreover, nowadays
nobody builds a single-page application from scratch: there are multiple
excellent JavaScript frameworks (the one in fashion today is AngularJS), and
the logic to attach tokens can be easily buried there. No action is required for
the application developer.
Azure AD supports the implicit flow, and Microsoft in general exposes
many APIs to be consumed from JavaScript clients.
Summary
This chapter led you through a whirlwind tour, examining how
authentication requirements and technologies changed and adapted through
two decades of IT history.
I began with a definition of some foundational concepts, such as identity
and authentication. You had the opportunity to see those concepts in action
right away, observing how the simplest authentication schemes implemented
them.
You witnessed the advent of local networks and their influence on
authentication artifacts and techniques: the emergence of the domain
controller, the advantages and limitations of Kerberos, and so on. I also
invested some time introducing claims-based identity as a framework for
understanding the intent and scope of modern authentication protocols,
without getting sidetracked by individual differences in syntax and
terminology.
You applied what you learned by examining SAML and WS-Federation
under the claims-identity lenses, acquiring basic terminology and an
understanding of how these protocols provided solutions to the main
authentication need of that time, cross-domain single sign-on.
I also presented the life-altering changes brought by the programmable
web and explained how its widespread adoption created the ideal conditions
for the emergence of OAuth2, a delegated authorization protocol. You saw
how that seeded spontaneous extensions that repurposed OAuth2 to achieve
single sign-on and how those initiatives were soon appropriated by standard
bodies and turned into a du jour protocol specification, OpenID Connect.
Finally, you had a taste of how authentication is evolving beyond its
traditional browser round-trip origins by leveraging the advanced JavaScript
capabilities of modern browsers—or leaving the browser altogether to
enable authentication for native applications.
I realize that this is a lot to take in. I don’t expect you to retain all the
content you read in this chapter right away. Some of it will be repeated later
on, when you write apps using the protocols described here. Other content
will be there for you as a reference so that if you get lost you can always find
refuge back here and refresh your understanding of why things are the way
they are today.
You are now equipped with a specific mindset you can use for
approaching authentication problems: by knowing what the problem is
you’re trying to solve, you’ll know what to look for in a solution.
Next, you’ll become acquainted with the entity that will play the role of
the IdP, IP, and AS throughout the book: Active Directory.
Chapter 3. Introducing Azure Active Directory and
Active Directory Federation Services
In this chapter you make first contact with Azure Active Directory (Azure
AD) and Active Directory Federation Services (ADFS), the two authority
types that Active Directory offers for protecting your applications.
My goals for this chapter are to inform you about what those services are,
how they are structured, and what they can do for you. I’ll focus mostly on
terminology, components, and functional aspects that you encounter while
using those authorities for development-related tasks. You won’t find fine
details or instructions here—those will be provided in the later chapters of
the book, in the context of the scenarios I’ll describe there.
I purposely ignore tasks and features that are prevalently administrative in
nature, not because they are not important or handy—they are—but because
this is a book for developers, and if I don’t draw the line somewhere, the size
of the book will get out of control.
A warning about content freshness
Azure Active Directory is a cloud service. As such, it evolves at
an exceptionally quick pace. New features are added every few
weeks, and existing ones are refined and improved all the time.
This is a very poor match with the typical time frames of the
printed publishing trade. No matter how fast I write, or how
promptly the production team sifts through the manuscript and
fixes my broken English, you will read those words months
from the moment I typed them. To minimize the aging of the
text, I avoid as much as I can presenting content that has a high
rate of obsolescence, such as screenshots and preview features.
Instead, I focus on topics that are slower to change: intended
usage, supported scenarios, architectural principles. Those
measures notwithstanding, some of my descriptions will
inevitably no longer be the latest and greatest at publication
time. I’ll try to minimize impact by publishing new info online.
If you read something that does not seem to perfectly match
what you see or experience when using the products, please
make sure to check out
http://www.cloudidentity.com/blog/books/book-updates/.
Important
That’s pretty much it. After all of this work is in place, navigating to your
app will bounce the user to authenticate against the local ADFS pages. Upon
successful authentication, a token will be issued and forwarded to your app,
which in turn will validate it and sign in the user. All as expected.
Getting ADFS
ADFS is a Windows Server role, its life cycle tied to releases of Windows
Server. That means that every Windows Server release from 2003 R2
onward has had its own ADFS version, with its own features. It also means
that ADFS versions aren’t backported. If you want a certain shiny feature
available in a certain ADFS version, you’ve got to upgrade the entire server
OS to the version of Windows that carries that ADFS version with it. After
that’s done, you can find and turn on the Active Directory Federation
Services role in the Server Roles screen or through any other management
tool you like. And once that role is turned on, you need to configure it. The
good news is that configuration is pretty straightforward; there’s a wizard for
it, and (if you’re okay with the default settings) the biggest task required is
that you create and assign a self-signed certificate.
I am not going to give you detailed instructions because those change
from version to version. I just want to give you a feeling for what setting up
one ADFS instance entails.
There’s more. ADFS requires you to have an AD deployment, even if you
plan to use functions that in themselves would not seem to require AD. You
don’t need to turn on ADFS on a domain controller (though lots of demo
environments do that for economic reasons), but you do need to use a
domain-joined server.
Once ADFS is up, managing it largely boils down to the following:
Deciding which protocols and credential types endpoints should be
active.
Provisioning applications that should be allowed to receive a token,
and specifying what claims such a token should carry.
Keeping certificates fresh, and performing other administrivia.
I am sure that administrators would add tons of tasks to this meager list,
but for developers, I’d say that’s as far as most of us would want to go.
Protocols support
ADFS did have a v1, introduced with Windows Server 2003 R2, and a v1.1
shortly after—but they aren’t talked about nowadays. From a developer’s
perspective, ADFS started to get interesting from version 2.0 onward.
Every version of ADFS has interesting features, besides which protocols it
supports, but from the perspective of how you can hook up apps to it, the
protocol support truly is the higher-order bit.
ADFS v2
ADFS v2 came out in the first half of 2010, as an out-of-band download for
Windows Server 2008 R2.
As I write this, ADFS v2 is probably the most commonly deployed ADFS
version. It supports the following protocols:
SAML2
WS-Federation
WS-Trust
From the perspective of .NET development, the most interesting of the
supported protocols is WS-Federation. That’s mainly determined by
exclusion: as mentioned earlier, the SAML protocol is not directly supported
by .NET libraries, and WS-Trust is on the sunset path together with all its
other WS-* friends. ADFS v2 uses SAML tokens in all its protocols.
ADFS “v3”
Some ADFS team members get irked when someone refers to the version of
ADFS shipping in Windows Server 2012 R2 as “ADFS v3.” These team
members would prefer that everyone say “the ADFS version that ships in
Windows Server 2012 R2,” but they kind of brought this situation on
themselves by calling the former versions 1.0, 1.1, 2.0 and 2.1. Also, who
has time for that?
ADFS “v3” is a superset of ADFS v2. In particular, it adds the OAuth2
authorization-code grant for public clients. In practice, this means that from
this version onward you can write native applications that obtain tokens
from ADFS and web APIs that validate tokens from ADFS. That flavor of
OAuth2 is not suitable for web applications, however, so no code-behind
token-acquisition scenarios are possible with that release.
Apart from the addition of the OAuth2 endpoint, ADFS had to add a
couple of other features to support the new flow:
The ability for issuing and processing JSON Web Tokens (JWTs).
An extended model for representing applications, augmented by the
definition of a client app, which was absent in the protocols supported
in v2. ADFS "v3" did not add this to its management UI, though, and
only offers PowerShell cmdlets for managing this new type of app.
ADFS “v3” also has some other protocol-related capabilities. In particular,
it has its own ceremony for determining whether a device is “workplace
joined”—an operation morally similar to joining a domain but with far fewer
requirements on the OS and the capabilities of the machine and far less
administrative power. ADFS uses that knowledge to decide whether a token
should be issued or refused in accordance to it. I am not going to detail this
flow any further. There are more modern approaches to this scenario, and I
am bringing it up mostly so that you know that it exists and you have one
possible culprit when you troubleshoot why you are failing to get a token
from an ADFS instance: “Don’t tell me it has the workplace join on. . . .
Let’s check.”
Protocol endpoints
The most tangible manifestation of your Azure AD tenant from an app’s
standpoint is the endpoints it exposes. Every Azure AD tenant comes into
existence with a comprehensive collection of tenant-specific endpoints.
Those are the network-addressable endpoints that apps trusting your tenant
need to engage with to complete the protocol dance of choice.
As discussed earlier, when you get a new tenant, you are assigned a
default domain, commonly of the form of tenantname.onmicrosoft.com. The
tenant also receives a unique, immutable, nonreassignable identifier, called
tenantID, in the form of a GUID. The default domain, any domains you add
afterward, and the tenantID itself are used to generate protocol URLs that
are specific to your tenant. Using <tenant> to indicate any of those
identifiers, a protocol URL template looks like the following:
Click here to view code image
https://<instance>/<tenant>/<protocol-specific-path>
https://login.microsoftonline.com/vittoriobertocci.onmicrosoft.co
m/oauth2/authorize
https://login.microsoftonline.com/cloudidentity.net/oauth2/author
ize
These are all equivalent because even though they use different
identifiers, they all refer to the same tenant. There are tradeoffs. The domain-
based identifiers are easier to remember but aren’t set in stone: I might not
renew my custom domain cloudidentity.net, and eventually somebody else
might reclaim it for their own tenant. The URL based on the tenantID is the
most reliable, but it is not the easiest to type from memory or to recognize
while you scan the config files of an old project.
The “login.microsoftonline.com” portion of the URL goes under the name
of “instance.” That represents the Azure AD service deployment in which
your tenant is provisioned. If you are working in a Western country, in the
large majority of cases you will see “login.microsoftonline.com” (or its
predecessor, “login.windows.net”), which represents the public cloud
instance of Azure AD. There are other deployments: for example, in China,
the Azure AD instance is indicated by "login.partner.microsoftonline.cn".
Azure AD instances are isolated from each other and operate in complete
independence.
You’ll get to know the OAuth2 (hence OpenID Connect) endpoints very
well. The other ones (SAML sign-on and sign-out, WS-Federation, and
metadata for both) won’t be covered in any details because those protocols
aren’t the focus of this book. However, you should have no difficulty
leveraging them: they are just standard implementations.
Azure portal
Today, the main user experience for getting settings into and out of your
Azure AD tenant is the Azure management portal
(https://manage.windowsazure.com/). This might, and likely will, change in
the future, but wherever the component itself is hosted, the function it
performs will remain available.
Today’s Azure AD portal extension offers features for both developers and
administrators. From a developer’s perspective, the main reason you use the
portal is to provision new apps, tweak the settings of apps in development,
and manage apps that are further along in their life cycle.
Admin operations you might catch yourself performing have to do with
creating test users and groups, creating brand-new tenants for development
and staging, assigning users and groups to app roles, and so on.
Application model
Azure Active Directory represents applications following a specific model
designed to fulfill two main functions:
Identify the application in terms of the authentication protocols it
supports In practice, this means enumerating all the identifiers, URLs,
secrets, and similar information that play a role at authentication time.
In this, Azure AD is quite similar to ADFS—at least in terms of intent.
Handle user consent at token-request time, and facilitate the
dynamic provisioning of applications across tenants In practice,
when a user requests a token for a given application and no one has
told Azure AD yet that it is okay to issue a token in that context, Azure
AD will ask the user to consent to the operation. If the consent is
successful, the decision will be recorded so that the next time the token
will be issued right away. There’s more! If the application was
originally defined in a different tenant, perhaps by an ISV, Azure AD
takes care of creating one entry for the app in the user’s tenant,
automating a provisioning operation that on other systems (think of
ADFS) would have required administrative involvement and action.
These two functions shape the way in which Azure AD models
applications and the relationships tying them to one another. The application
model does more than that, but I don’t want to go too much into the details
before you are in the position of actually using those features in practice.
Chapter 8 will describe the Azure AD application model in great detail.
Directory Graph API
The previously mentioned Graph API is the programmatic interface of Azure
AD. This is an OData3-compliant set of entities that you can use to
manipulate nearly all aspects of your Azure AD tenant: users, groups, and
applications are the ones you will most often deal with. Like all the other
endpoints discussed so far, the Graph API is exposed through a tenant-
specific endpoint of the form https://<instance>. You can access the API by
using good old REST, or you can use the client libraries that Microsoft
provides. In both cases, calls are authorized through OAuth2 bearer tokens
issued by the same Azure AD tenant. Chapter 9, "Consuming and exposing a
web API protected by Azure Active Directory," will provide some practical
examples demonstrating how to invoke the Graph API.
Directory sync
Earlier I mentioned that an Azure AD tenant can be either standalone and
cloud-only or be a projection of an on-premises AD deployment. How does
such projection work?
Simple. On a local machine, the administrator installs a tool that takes
care of synchronizing users and groups to the Azure AD tenant in the cloud.
As I write this, the recommended tool for performing this function is Azure
Active Directory Connect. Before Azure Active Directory Connect, a
progression of different tools was used to perform that function: the Azure
Active Directory Synchronization Tool (DirSync), the Azure Active
Directory Synchronization Services (Azure AD Sync), and even the familiar
Forefront Identity Manager 2010 R2 (FIM). I mention them all here in case
you stumble onto them in the literature. Azure Active Directory Connect
supersedes them all.
Now, how that synchronization takes place is a fascinating subject.
Administrators can elect to synchronize from single or multiple forests,
consider or ignore custom attributes, filter depending on specific values, and
so on. Beyond the Free tier feature set, other editions add advanced features
such as the ability to write back into the on-premises directory changes that
occurred on the cloud data set. I personally try to stay away from admins
when they set these things up, coming back into the picture when things are
ready to consume my apps.
The most important aspect of a sync deployment that a developer should
know is whether it includes the users’ credentials or relies on federation. An
administrator can elect to sync users but keep all credentials-verification
operations on-premises. This is achieved by federating the Azure AD
endpoints of a given tenant with an ADFS deployment on the corresponding
on-premises AD. Applications trusting a tenant configured that way will
send sign-in requests to Azure AD endpoints as usual, but Azure AD will
bounce those requests to the local ADFS. A successful authentication will
yield an ADFS-issued token, which will be forwarded to Azure AD and
exchanged for the usual Azure AD token. This arrangement decouples the
app from the tenant settings—it’s always an Azure AD token no matter who
checks credentials—which leaves admins free to pursue whatever policy
they prefer.
Tenants configured as I’ve just described are often referred to as federated
tenants (tenants configured to operate exclusively in the cloud are known as
managed tenants). Federated tenants have the advantage of immediately
reflecting in the system on-premises changes: if a user is deprovisioned on-
premises, he won’t be able to sign in anymore right away. This won’t rely on
a timely sync. Moreover, federated tenants preserve in the authentication
process whatever customization an ADFS deployment might have. This
topology does have some disadvantages: it requires an ADFS deployment,
which not everybody has, and its SLA is tied to the SLA of the same ADFS,
so if ADFS is down, no one can access apps even if Azure AD and the apps
themselves are up.
For that reason, admins can decide to also sync credentials to the cloud. In
that case, users can get an Azure AD token by entering their credentials
directly in the pages served by cloud endpoints, guaranteeing service
continuity even in the case of downtime of the on-premises systems.
Application access enhancements
This feature is often confused with the Azure AD developer capabilities
you’ve learned about so far.
Azure AD maintains a list of popular SaaS and consumer web apps that
are integrated with the directory out of the box. This means that one
administrator can offer to his or her users the chance to access those
applications directly through their Azure AD accounts—without needing to
memorize extra credentials or worry about application provisioning. At the
same time, this allows the administrator to exercise control over what
applications can be accessed, in what terms, and by whom. It even allows
provisioning of users in the app so that the app’s life cycle can be tied to the
user account in the directory—and automatically deprovisioned when the
user leaves the company, along with his main account. Admins can set all
this up by using the Azure portal. Users have a dedicated landing page
(myapps.microsoft.com) where they can discover and reach all the apps they
have been granted access to.
Note that Azure AD–app integration is not necessarily (in fact, it usually
isn’t) based on federation protocols such as the ones you encountered in
Chapter 2. In this scenario, Azure AD works behind the scenes doing
whatever it takes to talk to those applications in their own terms. For
example, in many cases Azure AD uses a browser plug-in to perform just-in-
time injection of credentials as soon as the app’s login form appears. Some
apps will indeed use federation for integrating with Azure AD—Salesforce
being a good example of that—in which case the admin setup experience for
the app will be different. The user is none the wiser, of course: the
experience is “I browse to the app I want, and then I am signed in.” How that
happens doesn’t really matter.
This is a pretty awesome administrative feature, but it is not very
actionable for developers. The code to perform that magic is part of Azure
AD itself rather than an extensibility point one can latch on to.
Beyond the Free tier
Azure AD Basic and Azure AD Premium add lots of advanced admin
features: advanced reporting, more sophisticated synchronization options,
many more self-service features for users, and so on. Some of those features
will have an impact on the apps you write. For example, in those tiers an
admin will be able to customize the pages used for gathering credentials and
consent to reflect a specific corporate look and feel or specific policies.
Those pages are the ones that are used in your apps, too. Another good
example is the suite of multifactor authentication features: those don’t really
change the way in which you write your apps—that’s one of the advantages
of claims-based identity, in fact—but they do change the experience of your
app’s users.
Another notable feature in those tiers is the Application Proxy. In a
nutshell, this feature allows admins to expose intranet apps to be consumed
by clients running outside the network—all protected by Azure AD.
Application Proxy is notable because it offers an infrastructural alternative to
using claims-based identity to cross a boundary, and this comes in handy
when you are working with legacy apps whose source has been lost or is as
brittle as a reliquary. The use of the Application Proxy solves the issue
without having to touch the code.
I should again warn you that Azure AD adds features at a crazy pace, so
now that you have this book in your hands, I am sure this list will already be
incomplete. But I hope this will be enough to give you at least an idea of the
kinds of features you should keep an eye out for so that you don’t risk diving
headfirst into implementing custom features at the app level if they are
already present out of the box.
Summary
In this chapter I introduced the sources of user identities that we’ll be
working with, ADFS and Azure AD. For the most part, both authority types
share the same goals, but the way in which they pursue those goals varies
according to the workloads they were designed to support: on-premises
direct federation for ADFS, multiorganization identity as a service for Azure
AD.
This chapter touched on many features you’ll never see mentioned again
in this book, notably all the admin features that can influence the behavior of
your apps but that are usually managed by an administrator. All the other
features introduced here are instead meant to be directly exercised during the
app development life cycle. Later chapters will revisit them, complementing
the information you learned here with hands-on examples.
Chapter 4. Introducing the identity developer
libraries
I want to give you a heads-up here. The identity libraries for token
requestors offered for Active Directory help you with the first and third tasks
(token acquisition and session management) but not the second (token
inclusion in requests). The main reason is that resources often offer client
libraries of their own, and if AD offered an identity library for consuming
resources, you’d be confronted with a difficult choice: use the AD classes or
the client libraries of your target resource. In the time frame of the Windows
Communication Foundation, Microsoft chose to offer specialized channels
and learned a painful lesson. In this generation of its libraries, the AD team
helps you acquire and maintain tokens, but it stays out of the way of any
operation using tokens.
Note
The only exception at this time is ADAL JS. In that case, the
AD team knows how resources are going to be consumed,
hence that library can efficiently inject tokens in the process.
Note
Resource protectors
Think of any piece of software that can be consumed by a remote client: web
applications serving UX elements to a browser, a web API consumed by
mobile apps or server processes, and so on. If you want to restrict access to
those resources, you need some component that enforces the necessary
checks whenever a request is made.
To learn what tasks are necessary for protecting a resource, all you need to
do is leaf back a few pages to the section “Claims-oriented protocols” in
Chapter 2. There you will find a list of the steps that need to happen for an
app to authenticate an incoming request. You can read the complete
description there: for convenience, I’ll repeat the highlights here:
The resource app reads an IdP’s metadata to configure itself.
In web apps, in the event of an unauthenticated request, the app must
generate a sign-in message and send it to the IdP of choice.
In a web API, the resource does not (usually) actively involve itself in
the token-acquisition process; rather, it relies on the client to act as a
token requestor before attempting access to the resource.
The client sends the token to the resource app. The resource finds the
token in the incoming message, extracts it, and attempts validation.
In web apps, the resource app marks a successful token validation with
the creation of a session—for example, by issuing a cookie.
Every request carrying the session cookie must also be validated. If the
cookie represents a valid session, the request is considered
authenticated.
These tasks are illustrated in Figure 4-2. The library is represented as
middleware in the diagram—a rectangle that intercepts and filters requests to
the relying party (RP), which is represented by the circle. The tasks
performed by the library are, from top to bottom: interception of requests
and enforcement of the protocol implemented (for example, intercepting
unauthenticated requests and responding with a sign-in message to the IdP of
choice); validation of tokens; emission of session artifacts (such as cookies);
and session validation. Figure 4-2 also shows a store for holding the protocol
coordinates defining the desired behavior—for example, the trusted identity
provider, the application IDs, and so on.
Figure 4-2 The responsibilities of a library providing resource-protector
functionality.
That’s quite a few tasks. Moreover, some of these tasks are quite
complicated: token validation requires cryptographic logic, sign-in request
generation requires the use of techniques that prevent numerous attacks—
techniques that can become quite intricate at times. The reasons for using a
library instead of coding all of these tasks into your apps from scratch are
generally the same as the ones listed in the previous section.
Whereas you might be required to use token-requestor logic at almost any
point during your app’s activities, the instant in which you need to apply
resource-protection logic is very well defined: it has to happen at resource
access time, of course. That marks an important difference between the two
libraries’ roles. Although a library implementing token-requestor tasks will
offer you primitives that you can access from your code at any time,
resource protectors will tend to be packaged as interceptors—software that
sits between the requestor and the resource the protectors are meant to
protect (hence the use of the term middleware) and triggers automatically
when a request arrives. That has advantages: your code does not have to
change as much, you usually just need to opt in for the portions of the
resource you want to protect, and the protection will happen automatically. It
also carries its own challenges, however: the need for the middleware to
latch on to an existing request-processing pipeline makes it heavily
dependent on the development stack of choice.
Hybrids
Very commonly, a web application plays both the role of the protected
resource and of the token requestor. The classic example of this I can think
of is Twitter. Twitter has protected resources of its own, like the web API
you use for publishing new tweets. On the other hand, if you associate your
Facebook account with Twitter, any new tweet will also appear as a status
update on your Facebook timeline. That means that the body of the Twitter
web API that publishes tweets must also contain token-requestor code,
which is used to gain access to Facebook’s API.
Most of the time, this dual behavior is achieved simply by composing
different library types within the same application. There are occasions in
which those combinations are frequent enough to warrant a higher degree of
integration between scenarios at the library level. At those times, the line
between token-requestor and resource-protector libraries becomes blurred.
Token requestors
Currently, the token-requestor category is composed in its entirety by
instances of the ADAL (Active Directory Authentication Library) franchise.
In some philosophical sense, there is only one ADAL, manifesting itself in
different ways according to the characteristics of each of the targeted
platforms. However, philosophy is rarely useful in practice, so I am going to
present each different package and platform as a standalone deliverable.
The ADAL vision
As its name implies, ADAL is meant to help your apps act as token
requestors against Active Directory, either in its on-premises or Azure flavor.
The library is not designed to get tokens from any other authority type. You
cannot point ADAL to Salesforce or Ping and get a token from them.
The reason for this is not immediately intuitive, but it becomes obvious if
you think about it for a moment. The main protocol that teaches apps how to
act as clients is OAuth2. OAuth2 leaves large areas of functionality
unspecified. A library implementing only the common denominator between
all existing OAuth2 providers would not be able to take care of a lot of
functionality, leaving to you the burden of supplying extra logic in your
apps. That logic would be necessary to bridge the gap between the
theoretical OAuth2 and the reality of a provider choosing its own token
formats, credential types, addresses, parameters, and refresh token strategies.
I won’t name names, but there are various libraries like that in the market.
The only other alternative is to embed in a library an enumeration of
provider-specific modules implementing the quirks and peculiarities of every
provider. In a sense, that’s what ADAL is doing, but just for Azure AD and
Active Directory Federation Services (ADFS).
The goal with ADAL is to make it as easy as possible for you to obtain
and use tokens from AD. As the library was being designed, the AD team
realized that it would be better able to achieve that goal by letting go of the
idea of a protocol library. A traditional protocol library is a library that
provides artifacts representing protocol constructs in the programming
language of choice, forcing you to be an expert in the use of said protocol.
The AD team decided to go for the polar opposite. ADAL is not a protocol
library. ADAL provides you with primitives that are designed to help you
perform the token-requestor tasks I listed earlier in the chapter, without
exposing to you which protocol is actually used to make things happen.
Sure, as of today, that protocol is largely OAuth2, and the AD team is not
immune from the occasional abstraction leak, but the point is that you don’t
need to know what protocols the library uses to successfully take advantage
of ADAL.
In fact, if you squint hard enough, every token acquisition in ADAL can
be modeled by the first leg of the diagram in Figure 4-4.
Figure 4-4 The main ADAL token-acquisition pattern.
The actors are all well known. There’s the application that needs access,
the target resource, and the authority that takes care of handling token
requests. ADAL provides a primitive that implements the first leg of the
diagram—a function call that accepts as input everything you know about
your scenario (authority, application identifier, resource identifier, and more)
and returns to you a token satisfying those constraints. Once you get it, you
can use that token to access the targeted resource.
This simple pattern can be applied in a staggering number of variants.
Applications might be native clients or web apps. The token might be
requested acting as a user or as an application. The targeted authority might
support or require different protocols. The credential types coming into play
can vary. The token requested might be already cached, or it might be
obtainable without prompting the user again. And, of course, there are lots of
platforms you might want to target, all with their peculiarities and
limitations. The need to support such a wide range of combinations
translates into quite a lot of complexity that’s bottled right beneath the
library’s surface: support for different protocol variants, cryptography,
validations, smart caching, and much more. The libraries can’t always isolate
you completely from complexity. For example, if your subscenario requires
a certain key to function, your app does need to somehow pass those key bits
in, but this approach does simplify things quite a lot.
ADAL .NET
ADAL .NET was the first library in the franchise to be released. It created
the blueprint for all the others, and as of today it remains the one that
supports the widest range of scenarios. That’s mainly for two reasons.
First, applications written in .NET can be both desktop apps (think of
Outlook, Excel, or Visual Studio itself) and web apps (ASP.NET, Web
Forms, etc.). In terms of OAuth2, those are public clients and protected
clients, respectively. Both support their own set of scenarios, and both sets
are supported in ADAL .NET. Neither are sandboxed in app stores of any
kind.
Second, applications written in .NET historically run on Windows, which
means that apps can take advantage of special infrastructure features (such
as integrated authentication) and need special logic to do so (for example,
current machine domain-join detection).
You can find the ADAL .NET source in the GitHub repo
https://github.com/AzureAD/azure-activedirectory-library-for-dotnet. The
library itself is distributed as a NuGet package. You can find its entry at
https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDire
ctory/.
As I write this chapter, the AD team has worked through three major
versions of ADAL .NET.
ADAL .NET version 1.x The first version of ADAL .NET was released in
September 2013, in the form of a NuGet package. That version is based on
.NET 4.0, and it supports obtaining tokens from Azure AD, ADFS in
Windows Server 2012 R2, and the Access Control Service 2 (ACS).
What is (was) the Access Control Service (ACS)?
The Access Control Service, ACS for short, is (was) the first
Azure offering in the identity space. In a nutshell, ACS is a
Security Token Service (STS) designed to sit between your
applications (mostly web) and the IdPs you want to work with:
ADFS, Google, Facebook, and Yahoo! were among the choices.
ACS decouples your app from the different protocol
requirements that each IdP has: your app only accepts tokens
from ACS, hence it only needs to work with the protocol that
ACS supports (mostly WS-Federation). As part of the process of
issuing such tokens, ACS takes care of authenticating the user
against the IdPs you want to work with, without exposing any
of those details to your apps.
ACS was a breakthrough product when it first came out, and
its features are still super useful today. However, it was kind of
a local maxima, with some intrinsic limitations (no
georeplication, no disaster recovery) that did not fit well when
Azure AD came to be. As a result, ACS as a service is being
discontinued, but (most) of its features will resurface as part of
Azure AD. Given the current estimates, I don’t believe that
those new features will emerge fast enough for me to be able to
describe them in this book. But I wanted to at least give you a
heads-up about this, as I guarantee that sooner or later ACS will
come up in your conversations.
Resource protectors
Microsoft has been in the business of offering development libraries for
resource protectors much longer than it has for token requestors. That is the
natural consequence of two main factors. The first lies in how natural it is to
provide a resource protector—which can simply be a piece of middleware
that intercepts requests without really disturbing the logic it is trying to
protect—and the mission-critical nature of enforcing access restrictions to
resources. The second is that the claims-based protocols were initially used
mostly on web applications, where there’s not much to be developed on the
client (or at least that was the case before Web 2.0 and JavaScript).
For the sake of brevity, I won’t dig too deeply into the history of resource-
protector libraries, leaving ancient artifacts like the Web Services
Enhancements (WSE) library and Windows Communication Foundation
(WCF) to rest in peace. Rather, I’ll cover the mainstream libraries in use
today.
Windows Identity Foundation
Windows Identity Foundation (WIF) was the first developer library entirely
devoted to identity tasks. WIF is a collection of classes meant to provide
common language runtime (CLR) representations of protocol artifacts (WS-
Federation messages, SAML tokens, session cookies, and so on) and HTTP
modules meant to easily weave claims-based identity support to ASP.NET
applications. For the first time, a developer could take advantage of those
new protocols without having to code everything from scratch, paving the
way for claims-based identity to reach the mainstream status it enjoys today.
Moreover, WIF offered a rich extensibility model, which the community
used to handle scenarios that were far from the basic ones, including entirely
new products, such as custom STSs.
The first WIF version was an out-of-band release based on .NET 3.5. That
release has special sentimental value to me, because at that time I was the
identity developer evangelist at Microsoft and I finally had a toy to play
with. Between 2008 and 2012, I spent a lot of time producing lots of
samples, videos, hands-on labs, training events, even a book (Programming
Windows Identity Foundation, Microsoft Press) to kick-start the claims-
based identity development movement.
In 2012, clams-based identity (and the cloud scenarios it enabled) reached
such an importance that Microsoft decided to take all the identity classes
from WIF and embed them in the next version of the .NET Framework, 4.5.
That was also the time at which I decided to join the engineering team, to
contribute all the feedback I had gathered in years of evangelizing identity to
developers. The process was already in flight, hence there wasn’t much
latitude for big changes. Those came later, with the OWIN wave that I’ll
introduce in the next section.
The .NET Framework 4.5 was reengineered to root all identity
representations to a base class, ClaimsPrincipal, centered on the idea
of claims. That class went all the way to mscorlib.dll, the core assembly of
.NET. All other classes were scattered through various .NET namespaces.
Visual Studio 2012 was extended with specific tools to facilitate the use of
WIF in .NET applications.
Both versions of WIF share a common feature set and only differ in the
degree of integration they offer with the .NET Framework versions they
target. The most common reason for which WIF is used is to secure an
ASP.NET app with WS-Federation. That is achieved by adding in the
ASP.NET pipeline a series of HttpModules and adding to the app’s config
file a special section capturing the protocol coordinates of both the
application and the STS it wants to trust. The developer does not need to
understand any of the intricacies of the protocols and token formats, and the
config file is generated automatically via tools. However, customizations and
troubleshooting quickly raise the level of proficiency required to operate the
library.
The WIF classes in the .NET Framework are still supported today, in the
same way in which the framework itself is supported, and they are used in
Visual Studio 2013 tools to this day. However, WIF is no longer the recipient
of innovation, and all efforts and new features are concentrated on a new
generation of libraries (described in the next section). Although WIF proved
to be an excellent product—ferrying an entire generation to the claims-based
identity era is no small feat—its extensibility model and configuration
mechanisms were too rooted to its XML legacy. Proper modern protocols
support required some backtracking and a fresh start.
Important
Note
Currently, as I write this chapter, .NET core (and the stacks that rely on it,
such as ASP.NET 5) is in developer preview. I expect some important news
to be added to the new middlewares, so I will cover some aspects of Katana
vNext in the pages ahead.
Hybrids
Applications are often not easily classified as pure resources or pure token
requestors. You have seen that in many occasions they play a bit of both
roles. This is normally addressed by using more than one library in the same
app: an ASP.NET web application can be protected by OpenID Connect
middleware and use ADAL to acquire the access tokens it needs to use
external APIs. However, there are some hybrid situations where the library
itself can be seen as enabling both approaches. As of today, the collection of
ADAL libraries counts only one such artifact, ADAL JS.
ADAL JS
In Chapter 2, I described single-page applications (SPA), a web application
development pattern that distributes app functionality between a JavaScript
front end and a web API back end. From a purely mechanical perspective,
the front end in an SPA acts as a token requestor. But that said, the front end
also works in a way with resources—it is natural for the developer to treat as
resources the routes and views accessed by the end user, although the actual
resource to protect is the web API that such routes need to access.
ADAL JS is a JavaScript library meant to help SPA developers add logic
to their front ends that acquires, stores, and uses tokens for accessing web
APIs, both those in its own back end and any other web API that can be
called from JavaScript (via CORS or an equivalent mechanism). That falls
squarely in the token-requestor camp.
The library also includes primitives for indicating that a certain route
requires the use (hence the presence) of a token, making it also a sort of
resource protector. Note that this is mostly a convenient model for the front-
end developer: ultimately, somebody must validate the token after
acquisition, and today that’s done on the service side. That means that a
complete SPA solution would include ADAL JS on the front end and perhaps
the OWIN middleware for OAuth2 on the web API back end.
ADAL JS is also open source, of course. Source and instructions are
available at https://github.com/AzureAD/azure-activedirectory-library-for-js.
The library proper is split into two files—a core JS file containing all the
low-level primitives, and an AngularJS module that makes it extra easy to
hook up identity features without disturbing the usual Angular application
structure.
This chapter opens the hands-on portion of this book, where all the theory
you’ve been absorbing until now will help you to be a more effective
developer for identity-related tasks.
The first task I’ll cover is the most common you will ever encounter: you
will learn how to use Active Directory to sign in users to a web application.
I will focus on the libraries required, the structure you need to use to
organize your project, and the (largely boilerplate) code you need to add. I
will show you how to set an entry for your application in Azure AD by
using the technologies available today. Finally, I’ll walk you through a test
run of the project to ensure that you’ve achieved the results you aimed for.
Literally all of these steps can be easily automated by using the web
project templates in Visual Studio 2015. I am describing how to do
everything by hand because that gives you great insights about the moving
parts and project structure—insights that you’ll need when you find
yourself troubleshooting more complex scenarios.
After the binge of theory in the first four chapters, I’ll let your brain take
a break . . . and avoid discussing protocol details, object model elements
beyond the ones necessary for the task at hand, and the Active Directory
application model. There will be time for that in the subsequent chapters.
Prerequisites
The prerequisites here are even more relaxed than the ones you encountered
in the “Prerequisites” section in Chapter 1.
You still need a Microsoft Azure subscription: please refer to the
aforementioned section in Chapter 1 for details.
Here you can use any version of Visual Studio you like, from Visual
Studio 2013 on. You don’t need any special tooling or automation. I’m
using Visual Studio Enterprise 2015, but as long as you account for small
differences in the user interface, you can easily apply the same instructions
in whichever version you have.
Steps
Here’s the sequence of steps I’ll walk you through:
1. Create a basic project as a starting point.
2. Add references to the NuGet packages you need.
3. Create the app’s entry in the Azure AD tenant of choice.
4. Write the code that initializes the OWIN pipeline and the OpenID
Connect middleware.
5. Add logic for triggering authentication and access claims and
initiating sign-out
That should all be very straightforward.
Important
We aren’t in Identity Land yet. The SystemWeb package pulls down the
assemblies required to host an OWIN middlewares pipeline in an ASP.NET
application. At this time, the version you get is 3.0.1: the version might be
higher in the future, and this holds for all the OWIN packages covered in
this chapter.
The command also pulls down as a dependency Microsoft.Owin, the
package containing all the OWIN base classes and primitives.
Percolating up through functionality layers, you’ll add the cookie
middleware next. Enter the following command:
Click here to view code image
install-package Microsoft.Owin.Security.Cookies
install-package Microsoft.Owin.Security.OpenIdConnect
Warning
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(WebAppChapter5.Startup))]
namespace WebAppChapter5
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your
application, visit http://go.microsoft.com/fwlink/?LinkID=316888
}
}
}
Those 10 lines of code (barely) are enough to initialize the entire OpenID
Connect pipeline. The best part is that they are almost pure boilerplate.
As I mentioned, Chapter 7 will delve into the details of the OWIN
mechanics. Here I’ll just say that the UseXXX extension methods push
middleware elements onto a stack, passing initialization data when
necessary. UseCookieAuthentication adds an instance of the cookie
middleware in the pipeline. UseOpenIdConnectAuthentication
does the same with the OpenID Connect middleware.
Note
Your app is now configured to use OpenID Connect against your Azure
AD tenant of choice whenever authentication is necessary. What does that
mean, exactly?
return View();
}
Note
As you type this code, Visual Studio shows wiggly red lines
under ClaimsPrincipal and ClaimTypes. That’s
because you are missing the necessary using directives,
hence the types are not available in the current scope. From
now on I will no longer explicitly instruct you to add using
directives unless it serves to clarify a concept. I will assume
that every time you encounter those wiggly lines, you will just
position the cursor on the offending term, press CTRL+. (a
period), and pick the appropriate using directive that Visual
Studio helpfully offers in a context menu.
The effect of this code should already be clear to you. I already covered
how claims are handled in .NET in Chapter 1, in the section
“ClaimsPrincipal: How .NET represents the caller.” If you find that you
need a refresher, I recommend leafing back to that section to bring this to
mind.
Quick recap
Let’s take a moment to summarize what we have done so far:
Starting from a simple project, with no authentication logic whatsoever,
you
Added three NuGet references.
Stepped through a brief wizard on the Azure portal to provision the
app.
Enabled the OWIN pipeline in your app with less than 10 lines of
boilerplate code.
Configured the OpenID Connect middleware by adding another 10
lines (more or less) of boilerplate code, customizing the values of a
couple of strings: Authority and ClientId.
Added an [Authorize] attribute on the action you wanted to
protect.
And that’s all that’s required for setting up a quick web app for users in
one organization, without taking advantage of any of the tools that Visual
Studio offers for automating all this.
Sure, there’s more to do to have a full-featured system—I will add a few
extra details in the following sections—but I wanted to be sure to highlight
the minimal set of actions. If you have been working with enterprise-grade
authentication for web apps in the past, I am sure you appreciate how much
simpler things have become.
Note
Sign-in logic
Let’s start by adding the sign-in functionality. Delete the Index method
from the controller, and add the following:
Click here to view code image
public void SignIn()
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(ne
w AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationTy
pe);
}
}
HttpContext.GetOwinContext().Authentication exposes
the authentication functions of the OWIN pipeline. The Challenge
method accepts input references to the middlewares that should be involved
in generating the sign-in action.
AuthenticationProperties is a general-purpose group of
settings that are independent from the specific protocol implemented by the
middlewares in the pipeline. For example, RedirectUri here represents
the local address in the application to which the browser should ultimately
be redirected once the authentication operations conclude. In this case, it’s
the root of the app: that’s because I plan to place the sign-in link on the
home page, so I expect the user to be brought back there once the
authentication dance is done. You have already seen in the previous section
that a similar behavior occurred automatically when the authentication was
triggered by clicking a specific action: clicking Contacts triggered the
authentication flow, and you ended up back on the Contact view.
Note
Sign-out logic
Implementing sign-out requires a similar method. Add the following code to
HomeController:
Click here to view code image
public void SignOut()
{
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.Authenti
cationType,
CookieAuthenticationDefaults.AuthenticationT
ype);
}
In this case, you are telling OWIN that you want both the OpenID
Connect and the cookie middlewares to act on the sign-out request. As
mentioned earlier, that will cause the OpenID Connect middleware to emit a
sign-out message to Azure AD and the cookie middleware to drop the local
app’s session cookies.
Important
If you leave things as they are right now, you will implement sign-out,
but the experience won’t be great. After performing the sign-out, the
browser will remain stuck on a nondescript page served by Azure AD. A
quick fix to that would be to specify in the OpenID Connect middleware
initialization an app resource you want to land on after a successful sign-
out. (It is usually a good idea to pick a resource that does not trigger an
automatic redirect to the authentication pages, or your user might be up for
a confusing experience.)
It’s easy to do that. Go back to Startup.Auth.cs, and modify the
OpenIdConnectAuthenticationOptions init logic so that it looks
like the following:
Click here to view code image
new OpenIdConnectAuthenticationOptions
{
ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
Authority =
"https://login.microsoftonline.com/DeveloperTenant.onmicrosoft.c
om",
PostLogoutRedirectUri = "https://localhost:44300/"
}
Here I am hardcoding the root of my app, but I am sure that in your apps
you will be far more elegant. It’s important to note that the
PostLogoutRedirectUri property is sent to Azure AD as part of the
sign-out request and must correspond to one of the app URLs you
registered. If for some reason you need to pick a more precise app URL as a
landing point, you can pass it via AuthenticationProperties in the
call to SignOut.
At this point the app has its sign-out logic, too. Now you just need to add
some controls on the app’s surface to activate the two new features.
Note
I’ll do that by adding in line some basic UI elements in the default view.
In Project Explorer, navigate to the folder View/Shared and open
_Layout.cshtml.
Locate the <div> element containing all the links to the
HomeController actions; it’s the one with the style navbar-
collapse collapse. Right below the </ul> closing tag of that list,
paste the following code:
Click here to view code image
@if (Request.IsAuthenticated)
{
<text>
<ul class="nav navbar-nav navbar-right">
<li class="navbar-text">
Hello, @User.Identity.Name!
</li>
<li>
@Html.ActionLink("Sign out", "SignOut",
"Account")
</li>
</ul>
</text>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li>@Html.ActionLink("Sign in", "SignIn", "Account",
routeValues: null, htmlAttributes: new { id = "loginLink" })
</li>
</ul>
}
Summary
In this chapter you discovered what it takes to add web sign-on capabilities
to an ASP.NET app from scratch, without any assistance from tools and
wizards.
You wrote your first identity code, familiarizing yourself with the
libraries required, the absolutely essential set of parameters required to
configure meaningful support for web sign-in via OpenID Connect, and the
rudiments of using OWIN for handling session management.
You also took your first steps with app provisioning in Azure AD via the
Microsoft Azure management portal.
At this point, you know enough to understand the code that Visual Studio
and the ASP.NET project templates generate automatically when you use
the organizational identity features, which puts you already one step above
a general developer who just needs to add authentication to an app and is
happy with the defaults.
The next proficiency level entails being able to manipulate how the
protocol middleware operates by changing its defaults and injecting your
own custom logic. But before you can get there, you must first learn in
more detail how the OpenID Connect protocol operates for making web
sign-on happen and what parameters you can use to influence its course.
That’s the subject of the next chapter.
Chapter 6. OpenID Connect and Azure AD web
sign-on
In this chapter you’ll take a closer look at OpenID Connect. Specifically, I’ll
describe how Azure Active Directory and its libraries use the protocol to
power the sign-in flow you implemented in Chapter 5, “Getting started with
web sign-on and Active Directory.”
I pick up again on some of the ideas that were introduced in Chapter 2,
“Identity protocols and application types,” going into greater detail on
terminology, message exchanges, concepts, and artifacts that come into play
when you use OpenID Connect. Understanding how the basic building
blocks are used in the default case will help you to troubleshoot when
something goes wrong. It also equips you with the knowledge you need for
customizing the default behavior to fit the requirements of your specific
scenarios.
My goal is not to write an annotated version of the entire specification; the
specs themselves are readable enough. Rather, I focus on the aspects that are
directly involved in the default flow that Azure AD and associated libraries
implement for achieving web sign-on. This is not to say that the rest of the
specification is not useful, or that Azure AD supports only the flows
described here. My focus is mostly to keep the size of the book (and the time
it takes to write it) under control. If you want to dig deeper into other
aspects, you will find plenty of material online that expands beyond the
basics.
Supporting specifications
OpenID Connect as a whole is a high-level specification that relies on lower-
level building blocks for essential functionality such as token formats,
cryptographic operations, and the like.
As you know from Chapter 2, OpenID Connect extends OAuth2 to add
support for authentication. The fundamental OAuth2 specifications are the
core OAuth2 Authorization Framework (available at
https://tools.ietf.org/html/rfc6749) and the OAuth2 Bearer Token Usage (you
can find it at https://tools.ietf.org/html/rfc6750).
Besides those two essential specifications, you will encounter JSON Web
Token (JWT, https://tools.ietf.org/html/rfc7519), JSON Web Signature (JWS,
https://tools.ietf.org/html/rfc7515), and JSON Web Algorithms (JWA,
https://tools.ietf.org/html/rfc7518). If you dig deep enough, you can reach all
the way to RSA cryptography and encoding specifications.
I will mention the relevant tidbits from each of these whenever the need
arises. You don’t need to go into any of these in any level of detail, but it is
useful to know where a given concept comes from so that in case an issue or
ambiguity arises, you can zero in on it instead of playing the whack-a-mole
game specs sometimes like to trick you into.
Capturing a trace
Let’s start by capturing a network trace of the flows we want to examine.
You have numerous options for capturing a trace. However, they boil
down to two alternatives:
You can use a proxy, which intercepts and saves traffic. The canonical
example of this approach on Windows is Fiddler, a free web-
debugging proxy utility.
You can use the network tracing features in the development tools of
your web browser of choice (the classic F12 option). Alternatively, you
can use a plug-in such as HttpWatch. The advantage here is that the
plug-in operates at the end of the HTTPS tunnel, hence you don’t need
to make any special provisions to decrypt traffic.
I will use Fiddler in this example, mostly because I am used to it (it was
around well before browsers had decent dev tools) and allows me to give
consistent instructions no matter which browser you’re using. That said,
during development I normally use Chrome. If you use another browser and
something does not look right even when you follow the instructions to the
letter, I recommend that you try it with Chrome before despairing.
Setting up Fiddler
You can download Fiddler from http://www.telerik.com/download/fiddler.
I highly recommend that you choose the option Fiddler for .NET4.
Launch the setup. Once that’s done, run Fiddler. When it first runs, the
app appears as shown in Figure 6-2.
The trace
Now we are ready to capture the trace of a sign-in and sign-out to our
application. Open Visual Studio 2015 and load the project you worked on in
Chapter 5. Start Fiddler if it’s not already running, and ensure that the
leftmost slot in the bottom status bar has the label Capturing On. If it
doesn’t, click that empty space, and you’ll see the label appear.
Go back to Visual Studio and press F5. When the application appears in
the browser, click the Sign In link. Sign in with your test user account, and
observe that the app correctly shows the user principal name (UPN) of the
user in its top-right corner. Hit the Sign Out link and confirm that you
successfully sign out.
Switch back to Fiddler and switch off the Capturing label. If you want to
read this chapter in multiple takes, you can save the current capture by
selecting File, Save, All Sessions. That way, you will always be able to
reload the same session and keep following the discussion without having to
capture the trace every time you pick up the book.
Examining individual frames is easy, as demonstrated in Figure 6-3. Just
select the frame you want in the left pane. In the right pane, choose the
Inspectors tab. You will be presented with a split view representing the
request and the response messages. You can choose different views that
highlight different aspects of the message. My default choice for both
request and response is the Raw view, which shows what you would see on
the wire.
Figure 6-3 The trace of the sign-in and sign-out flows. One frame is
selected on the left; the details of the request and response are shown in
the inspector views on the right.
Now that we have the trace, let’s dive in.
Authentication request
Scan the list of frames from top to bottom. Search for requests that aim for
your app: given that it is running on IIS Express, you can expect the host to
be something like localhost:44300.
The first one you’ll encounter is the request to your home page—that is to
say, to the URL /. Just to warm up, select that frame. You will see on the
right side the bits of the request, a simple GET, and the HTML constituting
the response.
If you followed the proposed sequence, the next frame aimed at your app
should be a GET to /Account/SignIn, triggered by clicking the Sign In
link. You know that the Account/SignIn route activates the code that
generates an authentication request. In fact, if you deselect the frame and
examine its response, you will find a 302 redirect toward Azure AD, carrying
the OpenID Connect authentication request message. It looks like the
following:
Click here to view code image
HTTP/1.1 302 Found
Cache-Control: private
Location: https://login.microsoftonline.com/6c3d51dd-f0e5-4959-
b4ea-a80c4e36fe5e/oauth2/authorize?client_id=c3d5b1ad-ae77-49ac-
8a86-
dd39a2f91081&response_mode=form_post&response_type=code+id_token&
scope=openid+profile&state=OpenIdConnect.AuthenticationProperties
%3dMw7NzZk2Eu7Jtt0TRIcYEs-O81rWJcYNRXUeoA0AO4eu2v7W8KIfE-
zf7fabTD-NVnmGNKb5F4jN1-
F1GYmTj6MfpMexQnrMuc8s1pU5qzU&nonce=635699258270224980.MGFhMDg1OG
QtOGY2Yy00OGJlLThmOGEtODFhMmNhZDJhOWZjYTQxNTlmNDMtYjU3MC00MTQzLTk
zYjMtMDdmNzRlZWY1NWY4
Server: Microsoft-IIS/10.0
X-AspNetMvc-Version: 5.2
X-AspNet-Version: 4.0.30319
Set-Cookie:
OpenIdConnect.nonce.NtAEBISzI9Su4nompbFjAZLP30DDfgV09WcjUtrNdqM%3
D=RkhuWmpsYlREMFJodm1zQkxIcFZ5X
25MWGNfZ1lWOWlBTUF3eTdBdklZMzl1cjU1REtiTGJiZEhmeFNDbDY3MFp4aV9KRX
VyNm9hWUVDMjAtXy14a0wwdncteEV5N
UpJSGsxMk9YdmZBQjE5Z0Q5cDkyN0E4VmgyZzJxTGhjWDBBV3RUUjY4d1JUZTZVTW
dBQ2JXZE5Na3RGdlp4Q3RwUVVBLVR5c
W4xUUZGRkQ0V0w1dUt6Tks3LUZJbm5Bd2FraUJCUnhPcUpwT3dtTE1zLS1rcks3Q3
VqN19aR1JkWGFINEk3Y3hyUDZOaw%3D
%3D; path=/; secure; HttpOnly
X-SourceFiles: =?UTF-
8?B?
QzpcQm9va1xXZWJBcHBDaGFwdGVyNVxXZWJBcHBDaGFwdGVyNVxBY2NvdW50XFNpZ
25Jbg==?=
X-Powered-By: ASP.NET
Date: Mon, 15 Jun 2015 00:43:47 GMT
Content-Length: 0
Note
The Location header is the most important piece, as it contains the sign-in
message that the browser will send to Azure AD as soon as it executes the
302. The default behavior of the OpenID Connect OWIN middleware is to
initiate a hybrid flow, though here I will ignore the authorization code
redemption part and focus only on sign-in functionality and authorization
endpoint traffic. Let’s break the message down to its fundamental
components and examine how the protocol is being used.
Authorization endpoint
Each Azure AD tenant exposes endpoints for the protocols it supports. The
message here is sent to the authorization endpoint of the tenant in which we
configured the app:
Click here to view code image
https://login.microsoftonline.com/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/oauth2/authorize
The authority part of the URL indicates the Azure AD instance, in this
case the public cloud. The GUID following that represents the tenant
identifier of the directory tenant of choice.
That GUID could be replaced by one of the domains associated with the
tenant, such as developertenant.onmicrosoft.com, to the same
effect. But using the tenant identifier is preferable because it can’t be
reassigned, whereas a domain could be decommissioned and acquired by a
different organization later.
The last portion of the path simply tells Azure AD which protocol should
be used. If you were using SAML, for example, the authority and tenant part
of the URL would be the same, but the last portion of the path would be
/saml2.
client_id
The first parameter you encounter in the message is client_id. This is
the same value you encountered while configuring the OpenID Connect
middleware. It’s the ID that allows Azure AD to correlate the request to the
app’s entry in the tenant.
response_mode, response_type
The next two parameters are especially interesting.
The response_type parameter indicates which artifacts your app
wants as the outcome of this authentication operation. The value you observe
here is the default because the sample from Chapter 5 did not specify any
custom value for this parameter. The default response_type for the
OWIN OpenID Connect middleware is code+id_token. In fact, in this
particular scenario we ignore code. It would be useful if we had invoked a
web API from our app, but given that we’re interested just in sign-on, we
end up consuming just the id_token. (That means that if we had sent a
value of id_token, we would have achieved the goals of this scenario just
as well.)
The response_mode parameter indicates the way in which we want the
authorization server (that is, Azure AD) to return the requested artifacts to
the application. In this case the trace reports a value of form_post, which
means that Azure AD is supposed to return the id_token in a POST. Once
again, that’s the middleware’s default behavior. This is just like how WS-
Federation and SAML return their tokens—by sending back to the browser
some simple JavaScript that autoposts the token to the application. The main
advantage of this approach lies in the fact that big tokens can be handled
easily, whereas other methods (such as those embedding tokens in a URL)
suffer from stringent size limitations. Another advantage is that token
validation can take place entirely on the server.
Important
scope
Just like in OAuth2.0, the scope parameter indicates which things
(resources and permissions/actions) an app is requesting access to.
If my mention of access control and authorization at this point confuses
you, remember what you read in Chapter 2: OpenID Connect layers sign-in
on top of OAuth2, which remains fundamentally a means for obtaining
authorization. In OAuth2 the scope applies only to the access token, whereas
in OpenID Connect it also affects the id_token, the main artifact making
the sign-in portion of the flow possible.
In our example, scope has two values, openid and profile.
openid is a conventional scope value, used to indicate to the
authorization server (Azure AD, that is) that the client intends to use
OpenID Connect as opposed to vanilla OAuth2. Let me stress this: a
request that does not include a scope parameter that includes the
value openid is not an OpenID Connect request.
profile is one of four special scope values (profile,
email, address, phone) that OpenID Connect defines as a
mechanism for requesting access to a specific set of predefined claims.
That access is expressed in different ways depending on the artifact
requested via response_type. An access token will carry
permission to access that particular claim set, and id_token might
include such claim values directly. You will see in this case that the
id_token you get back will indeed contain some claims from the
profile set (name, given_name, family_name).
state
The client uses the state parameter for preserving state throughout the
authentication flow. Whatever information the app needs to remember after
the user comes back with the requested token (and/or code) can be squirreled
away here: the authority (Azure AD) has the responsibility of reattaching the
state parameter, unchanged, to its eventual response.
From the trace, you can see that this parameter can be rather beefy. The
canonical function of the state parameter as envisioned by the
specification’s authors is to supply a mechanism for averting forgery attacks
from cross-site requests. The OpenID Connect OWIN middleware goes a bit
further and uses state to remember important information, such as
whether the authentication request was triggered by a specific route (as
happened in Chapter 5 when you clicked the Contact action). If that’s the
case, after the authentication exchange concludes later on, the middleware
can unpack from the state the original path and perform an internal redirect
to dispatch the browser to the requested resource.
nonce
The nonce parameter is a hard-to-guess value that OpenID Connect
introduced for mitigating token replay attacks. Here's how it works.
The client generates a nonce value and includes it in the request.
Furthermore, it saves that value somewhere—in the case of Azure AD, it’s
saved in a cookie with a unique name—so that the original nonce value
will be available later, during the last leg of the authentication flow. If you
examine the request trace, you will find the set-cookie directive for the
OpenIdConnect.nonce.NtAEBISzI9Su4nompbFjAZLP30DDfgV
09WcjUtrNdqM%3D cookie. That cookie is protected by server-driven
cryptography and cannot be forged or tampered with by a client.
When the app eventually receives an id_token, it searches among its
claims for a nonce claim and verifies that it contains the original nonce
value communicated in the request. For the middleware, that’s an easy task,
given that the same value is available in the aforementioned cookie. An
attacker who somehow stole an id_token would not be able to pass this
test, given that he or she would not be able to craft the corresponding cookie.
This is a nice and necessary security measure. However, be warned that it
will occasionally give you pain. Any scenario in which cookies are somehow
deleted or otherwise altered in the time between the sign-in request and the
response will cause the authentication to fail, given that the nonce check
relies on them. There are various solutions to this, up to and including saving
the nonce reference values on the server side. I will get back to that later in
the book when I focus on libraries and the object model.
{
"authorization_endpoint" :
"https://login.microsoftonline.com/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/oauth2/authorize",
"check_session_iframe" :
"https://login.microsoftonline.com/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/oauth2/checksession",
"claims_supported" : [
"sub",
"iss",
"aud",
"exp",
"iat",
"auth_time",
"acr",
"amr",
"nonce",
"email",
"given_name",
"family_name",
"nickname"
],
"end_session_endpoint" :
"https://login.microsoftonline.com/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/oauth2/logout",
"id_token_signing_alg_values_supported" : [ "RS256" ],
"issuer" : "https://sts.windows.net/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/",
"jwks_uri" :
"https://login.microsoftonline.com/common/discovery/keys",
"microsoft_multi_refresh_token" : true,
"response_modes_supported" : [ "query", "fragment",
"form_post" ],
"response_types_supported" : [ "code", "id_token", "code
id_token", "token id_token", "token" ],
"scopes_supported" : [ "openid" ],
"subject_types_supported" : [ "pairwise" ],
"token_endpoint" :
"https://login.microsoftonline.com/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/oauth2/token",
"token_endpoint_auth_methods_supported" : [
"client_secret_post", "private_key_jwt" ],
"userinfo_endpoint" :
"https://login.microsoftonline.com/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/openid/userinfo"
}
Now, aren’t you glad this document is not really meant for human
consumption and is mostly read by software?
But, in fact, if you give it a second glance, it’s not that bad. The document
provides all the information that a client needs to know to consume Azure
AD (and more specifically, this tenant of Azure AD) as an OpenID provider.
It lists all the relevant endpoints (authorization, token, UserInfo, and session-
related endpoints, including end_session_endpoint, which I have not
covered yet), specific claim types it supports, values of response_type
and response_mode that it accepts, and so on.
The reason that the OWIN middleware reaches out to this document,
however, is to discover what criteria to use for validating incoming tokens.
To that end, there are two crucial pieces of information:
The issuer value, which in this case is:
Click here to view code image
https://sts.windows.net/6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e/.
This is the value that applications should expect to find in the iss
claim of all tokens issued by this particular Azure AD tenant. The
OpenID Connect middleware will automatically enforce that the
incoming token complies with this condition and refuse all others,
ensuring that only users from the target tenant are granted access.
The keys to use for validating token signatures, provided by reference
by jwks_uri. The way keys are handled warrants more discussion,
which I provide in the next section.
You might have noticed that the issuer value contains a GUID, which
happens to be the same GUID value that appears in all endpoints. That value
is the identifier that Azure AD assigned to the tenant.
Signing keys
The discovery document does not supply raw key values. Rather, it points to
a different document—
https://login.microsoftonline.com/common/discovery/
keys—indicated by the value of jwks_uri, which the middleware must
retrieve as well. Sure enough, in the neighborhood of the frame retrieving
the discovery document, you will find another request of the following form:
Click here to view code image
GET https://login.microsoftonline.com/common/discovery/keys
HTTP/1.1
Important
Authentication
As you keep scanning down the frames list, you will soon encounter the
GET that honors the 302 with the authentication request. That triggers the
authentication experience for the user.
The details of how that takes place depend on many factors. Here are few
common cases:
Users who are already authenticated will have a session cookie, which
is sent along with the first request. This tells Azure AD that the user is
already authenticated. Hence, if the requested token does not require
any consent, the request will be granted without displaying any user
experience.
If user interaction is required for authenticating the request, you can
observe several subcases:
• Managed tenants will handle the full credential-gathering experience
directly.
• Federated tenants will redirect the browser to the on-premises IdP,
typically ADFS, which will then have full control over the
credential-gathering experience and any customizations that might
have been applied.
• Guest Microsoft account (MSA) users will be redirected to the
Microsoft account pages for authentication.
• In all cases, the authentication process can be affected by extra
elements such as a requirement for multiple authentication factors
(MFAs) or the presence of Windows Integrated Auth, and so on.
To make things even more interesting, different releases of the service
might handle the details of authentication differently. For example, about
one year ago a trace of the authentication phase of the OpenID Connect flow
would have shown some extra redirects to an internal STS endpoint based on
WS-Federation. Today, such extra redirects are no longer in place.
Tomorrow others might be introduced. The bottom line is that how
authentication takes place is up to the authority you are working with, in this
case Azure AD. The OpenID Connect protocol does not specify what should
be done to authenticate the user; it only regulates how to format requests and
responses without worrying too much about what happens between the two.
That doesn’t mean that you can afford to ignore the authentication phase,
though. Very often, issues in the authentication flows take place in this phase
—misconfigured ADFS, network restrictions, and DNS errors are all
examples of such potential issues. It is useful to know that the solution to
those issues is usually independent from how OpenID Connect is set up for
the application.
For the sake of simplicity, I assume here that you are in a situation in
which you are working with a managed tenant, your user does not have an
existing session, and you do not have any MFA setup in place.
Important
I also assume that Azure AD will be using the same logic for
handling credential gathering, which might not be the case by
the time you read this. Don’t take any of this too literally, and
keep your eyes open for functional equivalents.
Search the trace for the first POST after the discovery frames that targets
https://login.microsoftonline.com/6c3d51dd-f0e5-
4959-b4ea-a80c4e36fe5e/login, where the GUID is the ID of your
tenant. The body of that POST will contain, among lots of other things, your
username and password. (This is a good opportunity for me to remind you to
be very careful with your traces, especially if you save them, as they can
contain very sensitive information, such as passwords.)
The response to that POST is the OpenID Connect response we were
waiting for.
Response
Let’s examine the bits of the Azure AD response. Here’s a dump of it, edited
for clarity:
Click here to view code image
HTTP/1.1 200 OK
[…SNIP…]
Set-Cookie: ESTSAUTHPERSISTENT=AAA[…SNIP…]LIcgiAA; expires=Sat,
12-Dec-2015 00:43:56 GMT; path=/; secure; HttpOnly
Set-Cookie: ESTSAUTH=QUFBQ[…SNIP…]kNBQQ==;
domain=.login.microsoftonline.com; path=/; secure; HttpOnly
Set-Cookie: ESTSAUTHLIGHT=+93dba92a-90d2-4f97-801b-a64a3b320f28;
path=/; secure
Set-Cookie: PPAuth=AW4[…SNIP…]HSvk;
domain=login.microsoftonline.com; path=/; secure; HttpOnly
Set-Cookie: ESTSSC=01; path=/; secure; HttpOnly
Set-Cookie: SignInStateCookie=QUFB[…SNIP…]ySUFB; path=/; secure;
HttpOnly
[…SNIP…]
<html>
<head>
<title>Working...</title>
</head>
<body>
<form method="POST" name="hiddenform"
action="https://localhost:44300/">
<input type="hidden" name="code" value="AAA[…SNIP…]jyAA" />
<input type="hidden" name="id_token" value="eyJ0[…
SNIP…]zLUWB1Q" />
<input type="hidden" name="state"
value="OpenIdConnect.AuthenticationProperties=[SNIP]" />
<input type="hidden" name="session_state" value="93dba92a-
90d2-4f97-801b-a64a3b320f28" />
<noscript>
<p>Script is disabled. Click Submit to continue.</p>
<input type="submit" value="Submit" />
</noscript>
</form>
<script
language="javascript">window.setTimeout('document.forms[0].submit
()', 0);
</script>
</body>
</html>
Note
This response might look intimidating at first glance, but in fact the
message follows a very simple structure. The first thing to notice is that
Azure AD saves a number of cookies on the user’s browser at this point.
Some of them are persistent; others are session bound. These cookies keep
track of the fact that the user now has an authenticated session with Azure
AD. Subsequent requests for tokens will carry these cookies, influencing the
experience in a variety of ways. For example, an already-authenticated user
who requests a token that does not require any other interaction (such as
consent) gets back the requested token without seeing any user experience.
Another example is that requests carrying prompt=select_account
will now list the currently signed-in account as one of the options, as
recorded in one of these cookies.
Knowing the details of what each cookie does won’t be of much help to
you. The lineup of cookies is not part of the contract of Azure AD and the
application; hence, Azure AD can change these cookies or the function they
perform at any time, without notice. They truly are an implementation detail,
and as such, creating a dependency on their behavior leads to brittle
solutions that can break at any time, possibly with no recourse. It’s best to
stick with the knowledge that Azure AD sessions are represented via
cookies, without going into the details of which individual cookies are used.
The body of the response is the interesting part. Remember how the
request included a response_mode=form_post? Azure AD is
complying with the request, returning the requested response_type in a
hidden form: it contains an id_token, a code, and even the state
parameter, with the exact value provided in the request.
The HTML that’s returned also contains a line of JavaScript that is meant
to automatically submit the form (POSTing it to the app, as codified by the
form method and action attributes) as soon as the browser loads that
HTML.
You should be able to locate the subsequent POST request to the app a bit
later in the list of frames. Here an edited dump of it:
Click here to view code image
POST https://localhost:44300/ HTTP/1.1
[...SNIP...]
Cookie: OpenIdConnect.nonce.NtAE[...SNIP...]yUDZOaw%3D%3D
code=AA[...SNIP...]AA&id_token=eyJ[...SNIP...]B1Q&state=OpenIdCon
nect[...SNIP...]&session_state=93d[...SNIP...]28
The content of the body should not be surprising. It’s just the execution of
the form_post we just examined. The only thing I want to highlight is that
the request includes the cookie tracking the nonce value, just as intended
when it was generated together with the request. If something between the
request and the response messes with that cookie—say, an antivirus or a
global cookie-cleanse operation—the authentication will fail.
Here’s the response to that POST:
Click here to view code image
You are almost certain not to notice this at first glance, but if you observe
the string carefully, you will see that it is partitioned in three segments, each
separated by a dot ( . ) character. Each segment is a Base64 encoded string.
To reveal the actual content of the string, you need to decode it. There are
plenty of decoders online, but Fiddler includes one out of the box. Search for
TextWizard on the main menu, and click it. Paste the string into the top pane.
Select From Base64 from the Transform drop-down list. Figure 6-5 gives
you an idea of the result.
Figure 6-5 Using Fiddler’s TextWizard to decode the id_token.
Here’s the decoded id_token, formatted for ease of reference.
Click here to view code image
{
"alg" : "RS256",
"kid" : "MnC_VZcATfM5pOYiJHMba9goEKY",
"typ" : "JWT",
"x5t" : "MnC_VZcATfM5pOYiJHMba9goEKY"
}
{
"amr" : [ "pwd" ],
"aud" : "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
"c_hash" : "wq7YmU5APff8Lec-k1-uTg",
"exp" : 1434332636,
"family_name" : "Rossi",
"given_name" : "Mario",
"iat" : 1434328736,
"iss" : "https://sts.windows.net/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/",
"name" : "Mario Rossi",
"nbf" : 1434328736,
"nonce" :
"635699258270224980.MGFhMDg1OGQtOGY2Yy00OGJlLThmOGEtODFhMmNhZDJhO
WZjYTQxNTlmNDMtYjU
3MC00MTQzLTkzYjMtMDdmNzRlZWY1NWY4",
"oid" : "13d3104a-6891-45d2-a4be-82581a8e465b",
"pwd_exp" : "3137029",
"pwd_url" :
"https://portal.microsoftonline.com/ChangePassword.aspx",
"sub" : "oCyqt3KHjPD-VbiSaRpaAHPSi9Wa2eXf-WaWF6XJ3A8",
"tid" : "6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e",
"unique_name" : "mario@developertenant.onmicrosoft.com",
"upn" : "mario@developertenant.onmicrosoft.com",
"ver" : "1.0"
}
Mz6 ê c […SNIP…]
Note
Important
Please remember that developers will rarely, if ever, operate at this level.
Libraries and middleware are the natural consumers of this information,
taking care of all the cryptography and distilling data away in more abstract
form for the application layer. As usual, knowing what’s going on below the
surface is useful mostly for troubleshooting and customization purposes. The
JWS/JWT parts that hold some actionable value for you are the header and
the JWT claim set, the latter more than the former.
JWS Header For convenience, here again are the JWT header bits of the
decoded id_token from the trace:
Click here to view code image
{
"alg" : "RS256",
"kid" : "MnC_VZcATfM5pOYiJHMba9goEKY",
"typ" : "JWT",
"x5t" : "MnC_VZcATfM5pOYiJHMba9goEKY"
}
The JWS spec lists a number of registered header parameter names and
details the possible values they can assume. In so doing it relies on yet
another specification, JSON Web Algorithms (JWA, see
https://tools.ietf.org/html/rfc7518), which provides a registry of well-known
values and a central place for future extensions. If you are wondering how
deep this specifications rabbit hole runs, don’t worry: it does go deeper, but
this level is as deep as I will go in the context of this book.
The easiest header parameter to describe is typ, indicating the media type
of the bits being signed. For us, this will always be JWT, but JWT can be
used for signing pretty much anything you’ll find listed at
http://www.iana.org/assignments/media-types/media-types.xhtml.
The alg header gets funnier. A signature operation can be performed
through multiple algorithms. The aforementioned JWA spec lists a bunch of
well-known ones, with detailed guidance on what identifier to use and how
to apply the operation on the bits.
The value we have here, RS256, indicates a signature performed with an
RSA key using the SHA-256 hashing function. I might point you to the RFC
3447 specification to expand on what that exactly means, but I promised I
wouldn’t go any further. Suffice to say that RS256 uses public key
cryptography and, in the case of Azure AD, the keys are handled via X.509
certificates.
You should not be surprised that the id_token we received from Azure
AD was signed using RS256. If you turn back to the “Discovery” section
and take a look at the .well-known/OpenId-Configuration document, you
will notice that it contains
"id_token_signing_alg_values_supported" : [ "RS256"
]. Now you know what that means.
The kid header carries an identifier for the key used for performing the
signature. If you go back to the “Signing keys” section and take a look at the
keys-definition document, you will notice that the value of the kid header
here matches exactly the one for the second key on file. That means that the
key used actually belongs to Azure AD, as expected.
The x5t header can be used in case the key is stored as a X.509
certificate—it represents the certificate’s thumbprint. It is still an identifier,
but given that it is computed from the certificate bits themselves, it carries
cryptographic strength. You might have noticed that in this case its value is
the same as for kid.
There are various other header types, but you’ll encounter them more
rarely in Azure AD. (Look them up in the specification if you need to.)
JWT Claim Set JWT defines a number of registered claim names that
represent common ground for all implementers. These are mostly claims that
are useful for validating the incoming token. Here’s a list of the intersection
between the claims registered by the JWT specification and the ones actually
present in the id_token from the trace you captured.
iss This claim represents the identity of the issuer of the token. In
this case that’s the Azure AD tenant where you provisioned the
application.
If you examine the value of the iss claim in the captured token (in
my case it’s https://sts.windows.net/6c3d51dd-f0e5-
4959-b4ea-a80c4e36fe5e/) and compare it with the issuer
value in the discovery method, you will see that they are the same.
The OpenID Connect OWIN middleware validates the issuer by
default, comparing what it finds in iss with what it read in issuer
from the discovery document—and refusing any token that does not
comply. You will see later in the book that you will want to relax this
behavior at times, especially when your app needs to accept tokens
from multiple Azure AD tenants, each with its unique issuer value.
sub The sub is an identifier of the subject that went through the
authentication process—in this case, the user. Azure AD guarantees
that the value of sub is unique and not reassignable.
aud This claim indicates the audience of the token; that is to say, its
intended recipient. The captured token bits will show that in this case
the value of aud is exactly the client_id that was assigned by
Azure AD to your application. You can think of the audience claim as
what’s written for “pay to the order of” on a bank check. If somebody
tries to pay you with a check, but that check indicates someone else as
the payee, you are not going to accept the check no matter how
legitimate it appears. The same goes with tokens. If your application
receives a token with an aud that is different from the app’s identifier,
that token does not prove that the caller successfully obtained a token
for your app from Azure AD. The caller might have stolen that token
from a transaction with a different app, for example.
The OpenID Connect OWIN middleware validates the audience by
default, comparing what it finds in aud with the client_id value
you provide in the middleware initialization.
exp This claim indicates the expiration time of the token. If the token
is received an instant later than this value, it must be refused.
Typically, the request-issuance-validation chain introduces small clock
discrepancies, which our libraries try to account for by introducing
some tolerance, so don’t expect checks to be too strict here.
nbf nbf (“not before") is the partner of exp. Tokens received at a
time that predates nbf must be refused.
iat This claim represents the instant at which the JWT was issued.
This can come in handy in case you want to know how old the token
really is.
id_token validation
OpenID Connect establishes that the id_token is in JWT format and then
proceeds to describe in detail how to validate it. That mostly boils down to
mandating the presence of specific claims and describing what criteria they
must meet for each of the flows it defines—hybrid, authorization code, and
implicit.
The hybrid flow, chosen by default by the OpenID Connect OWIN
middleware, is the flow with the most requirements. I won’t repeat them
verbatim here. The protocol’s spec is very clear and easy to consult for this
information. I just want to point out the validations that are most likely to
provoke failures on real solutions so that you have a starting point.
The signature must be validated.
The iss, sub, aud, exp, and iat claims from the canonical claims
list in the JWT spec must all be present and validated as I described in
their definitions, including all the ties to the discovery constraints (for
example, the iss value must match the identifier in issuer from the
discovery document).
The id_token must contain a nonce claim, whose content must
match the content provided in the request (in our case, saved in a
cookie).
This does not really come into play in this chapter, given that in this
scenario we ignore the authorization code returned alongside the
id_token, but per the specification, the id_token must contain a
claim c_hash that is derived from the value of the authorization code.
In practice, if an attacker would substitute the code in the response
message, the c_hash claim would no longer correspond to the code
value and would make the validation fail.
OpenID Connect exchanges for signing out from the app and
Azure AD
You have learned how to establish a session, so it seems only fair to
conclude by studying how to end one as well.
Assuming that you captured the trace according to the instructions, scroll
down the frames list to find a GET of Account/SignOut. This is the
action you implemented in Chapter 5 that triggers the
HttpContext.GetOwinContext().Authentication.SignOut
code against the OpenID Connect and cookie middlewares. Select the frame
and inspect the response. It should look like the following:
Click here to view code image
HTTP/1.1 302 Found
[...SNIP...]
Location: https://login.microsoftonline.com/6c3d51dd-f0e5-4959-
b4ea-a80c4e36fe5e/oauth2/logout?
post_logout_redirect_uri=https%3a%2f%2flocalhost%3a44300%2f
[...SNIP...]
Set-Cookie: .AspNet.Cookies=; path=/; expires=Thu, 01-Jan-1970
00:00:00 GMT
[...SNIP...]
The Set-Cookie operation gets rid of the app’s own session cookie.
That alone would not be much of a sign-out because at this point the user is
still signed in with Azure AD. That is, there is still a cookie tied to the Azure
AD domain, which would allow the user to get a new token for the app
without any prompt—signing right back in.
Cleaning the session with Azure AD is the purpose of that 302 message
returned in the Location header. The sign-out request syntax is described in
the OpenID Connect Session Management specification (available at
http://openid.net/specs/openid-connect-session-1_0.html—at this time it is
still an implementers’ draft). Here are a few observations:
The target endpoint,
https://login.microsoftonline.com/6c3d51dd-
f0e5-4959-b4ea-a80c4e36fe5e/oauth2/logout, was
advertised by the discovery document under the property
end_session_endpoint.
The parameter post_logout_redirect_uri instructs Azure AD
to redirect the browser to the indicated URI once the sign-out
operation concludes.
Although the default logout logic does not use it, you can use the
state parameter to preserve state between the request and response,
just as you’ve seen for the sign-in flow.
It is possible for one user to be signed in to Azure AD with more than
one account simultaneously. What should Azure AD do upon receiving
a sign-out request? The protocol helps to solve the ambiguity by
providing a parameter, id_token_hint, which can be used for
indicating which account should be signed out. In this case, you would
simply provide the id_token you received at authentication time.
The frames that follow show how Azure AD reacts to the request. I won’t
include the detailed traces here. The implementation details (endpoints,
cookies, messages) aren’t part of the contract and are likely to change.
However, the gist of it is that Azure AD updates its tracking cookies (like the
one listing the currently signed-in accounts) and cleans up the ones
containing details of the account session.
That done, Azure AD redirects back to the address specified by
post_logout_redirect_uri, which in our case is the app’s root. The
user is now signed out: access to the protected portions of the app will
require a new sign-in operation.
Figure 6-7 summarizes the flow.
Summary
This chapter offered you an in-depth look at how Azure AD uses OpenID
Connect to implement web sign-on. I did not leave any stone unturned,
examining the meaning of every parameter on the wire and even mentioning
values that the specs define and that could have been used to customize the
sign-in flow. Although the content of the chapter was undoubtedly biased
toward Azure AD and its defaults, most of what you learned about OpenID
Connect and JWT applies to any service using those specs.
Now that you are aware of what goes on at the wire level, you are ready to
go beyond the basics and customize default request generation, response
processing, and validation to fit your scenarios. The next chapter examines
the OpenID Connect OWIN middleware and supporting classes (such as
those handling the JWT format), showing you what settings and extensibility
points you can use to leverage your newfound protocol knowledge.
Chapter 7. The OWIN OpenID Connect
middleware
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 authentication 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.
Katana
Katana is the code name for a set of Microsoft’s .NET 4.5–based
components that utilize the OWIN specification to implement various
functionalities in ASP.NET 4.6. It’s what you used in Chapter 1 and Chapter
5 and includes base middleware classes, a framework for initializing the
pipeline, pipeline hosts for ASP.NET, and a large collection of middlewares
for all sorts of tasks.
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.
This entry wins over both the naming convention and the attribute.
Fun times! I’ve listed these alternatives so that you know where to look if
your app appears to magically pick up code without an obvious reason. I
used to feel like that all the time when I first started with Katana.
Let’s now turn our attention to Startup.Configure. Observe the
method’s signature:
Click here to view code image
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,
navigate to the Locals tab and look at the content of app. You should see
something similar to Figure 7-1.
Figure 7-1 The content of the app parameter at Configure time.
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*.
Middlewares, pipeline, and context
Stop the debugger and head to Startup.Auth.cs, where you will find the
implementation of ConfigureAuth. This is where you actually add
middleware to the pipeline, through the calls to
UseCookieAuthentication and
UseOpenIdConnectAuthentication. Those are convenience
extension methods. UseCookieAuthentication is equivalent to this:
Click here to view code image
app.Use(typeof(CookieAuthenticationMiddleware), app, options);
Note
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 breakpoints or debug
messages. Here’s the pattern for a minimal middleware-like debug
implementation:
Click here to view code image
app.Use(async (Context, next) =>
{
// request processing - do something here
await next.Invoke();
// response processing - do something here
});
That’s pretty easy. Here’s the sequence from the sample app, modified
accordingly:
Click here to view code image
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefa
ults.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.co
m",
PostLogoutRedirectUri = https://localhost:44300/
}
);
The numbers in front of every debug message express the sequence you
should see when all the middlewares have a chance to fire. Any discontinuity
in the sequence will tell you that some middleware decided to short-circuit
the pipeline by not invoking its next middleware buddy.
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:
Click here to view code image
<add key="vs:EnableBrowserLink" value="false"></add>
That done, hit F5. As the home page loads, the output window will show
something like the following:
Click here to view code image
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.
Authenticate, 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:
Click here to view code image
1 ==>request, before cookie auth
2 ==>after cookie, before OIDC
5 <==after OIDC
6 <==response, after cookie auth
1 ==>request, before cookie auth
2 ==>after cookie, before OIDC
3 ==>after OIDC, before leaving the pipeline
4 <==after entering the pipeline, before OIDC
5 <==after OIDC
6 <==response, after cookie auth
This time you see a gap. As the request comes back with the token, notice
that the first part of the sequence stops at the OpenID Connect middleware—
the jump from 2 to 5 indicates that the last debug middleware was not
executed, and presumably the same can be said for the rest of the following
stages.
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.
IIS integrated pipeline events and middleware execution
By now you know that Katana runs its middleware pipeline in
an HttpModule, which participates in the usual IIS integrated
pipeline. If you are familiar with that, you also know that
HttpModules can subscribe to multiple predefined events,
such as AuthenticateRequest, AuthorizeRequest,
and PreExecuteRequestHandler.
By default, Katana middleware executes during
PreExecuteRequestHandler, although there are
exceptions. There is a mechanism you can use for requesting
execution of given segments of the middleware pipeline at a
specific event in the IIS integrated pipeline, and that’s by using
the extension method UseStageMarker.
Adding
app.UseStageMarker(PipelineStage.Authentica
te) tells Katana to execute in the AuthenticateRequest
IIS event all the middlewares registered so far, or as far as the
first preceding UseStageMarker directive.
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.
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
CookieAuthenticationDefaults.AuthenticationType,
which happens to be the string “Cookies”. When the OpenID Connect
middleware validates the incoming token and generates the corresponding
ClaimsPrincipal 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-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 implemented 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
populated 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 property, which in turn
contains a collection of AuthenticationTypes, including
“OpenIdConnect” and “Cookies”. When it’s their turn to process the
response, the middlewares in the pipeline check whether there is a nonnull
AuthenticationResponseRevoke entry containing their
AuthenticationTypes. If they find one, they have to execute their
sign-out logic. As you advance through the breakpoints in the response flow,
you can see that behavior unfolding. The OpenID Connect middleware
reacts by changing the return code to 302 and placing the sign-out message
for Azure AD in the Location header. The cookie middleware simply adds a
Set-Cookie entry that sets the session cookie expiration date to January 1,
1970, invalidating the session. Figure 7-11 provides a visual summary of the
operation.
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.
Add a reference to the NuGet package Microsoft.Owin.Diagnostics. In
your Startup.Auth.cs, add the associated using directive. Right on top of
your main configuration routine (in our sample, ConfigureAuth), add
something along the lines of the following:
Click here to view code image
app.UseErrorPage(new ErrorPageOptions()
{
ShowCookies = true,
ShowEnvironment = true,
ShowQuery = true,
ShowExceptionDetails = true,
ShowHeaders = true,
ShowSourceCode = true,
SourceCodeLineCount = 10
});
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.
The usual array of choices controlling all Katana middleware
mechanics.
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
OpenIdConnectAuthenticationOptions for our app, right after
initialization.
Middleware mechanics
Finally, here’s a list of options that are used for driving the general behavior
of the middleware in the context of the Katana pipeline:
SignInAsAuthenticationType This value determines the
value of the AuthenticationType property of the
ClaimsPrincipal/ClaimsIdentity generated from the
incoming token. If left unspecified, it defaults to the value passed to
SetDefaultSignInAsAuthenticationType. As you have
seen earlier in the section about authentication middleware, if the
cookie middleware finds this in an
AuthenticationResponseGrant, that’s what the cookie
middleware uses to determine whether such ClaimsPrincipal/
ClaimsIdentity should be used for creating a session.
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
AuthenticationMode to Passive.
UseTokenLifetime This property is often overlooked, but it’s
tremendously important. Defaulting to true, UseTokenLifetime
tells the cookie middleware that the session it creates should have the
same duration and validity window as the id_token received from
the authority. If you want to decouple the session validity window
from the token (which, by the way, Azure AD sets to one hour), you
must set this property to false. Failing that, all the session-duration
settings on the CookieMiddleware will be ignored.
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 authentication 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 former category
counts only one member, RedirectToIdentityProvider; all the
other notifications are included in the latter.
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.
Click here to view code image
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
Authority =
"https://login.microsoftonline.com/DeveloperTenant.onmicrosoft.co
m"
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);
},
},
}
);
I’ll discuss each notification individually in a moment, but before I do,
give the app a spin so that you can see in which order the notifications fire.
When you click the Sign In link, you can expect to see something like this in
the output window:
Click here to view code image
1 ==>request, before cookie auth
2 ==>after cookie, before OIDC
3 ==>after OIDC, before leaving the pipeline
4 <==after entering the pipeline, before OIDC
*** RedirectToIdentityProvider
5 <==after OIDC
6 <==response, after cookie auth
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 something like what’s shown in Figure 7-15.
Figure 7-15 The content of the context parameter on a typical
RedirectToIdentityProvider notification execution.
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:
Click here to view code image
RedirectToIdentityProvider = (context) =>
{
string appBaseUrl = context.Request.Scheme + "://"
+ context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
return Task.FromResult(0);
},
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:
Click here to view code image
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:
Click here to view code image
public static Task
RedirectToIdentityProvider(RedirectToIdentityProviderNotification
<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>
notification)
{
notification.ProtocolMessage.Prompt = "login";
return Task.FromResult(0);
}
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. The
OpenIdConnectMessage passed to
RedirectToIdentityProvider is an excellent example of that.
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 notification 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. Similar 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:
Click here to view code image
SecurityTokenValidated = (context) =>
{
string userID =
context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameId
entifier).Value;
if (db.Users.FirstOrDefault(b => (b.UserID == userID)) ==
null)
throw new
System.IdentityModel.Tokens.SecurityTokenValidationException();
return Task.FromResult(0);
},
The notification body retrieves a user identifier from the claims of the
freshly created AuthenticationTicket. That done, it verifies whether
that identifier is listed in a database of subscribers (whose existence I am
postulating for the sake of the scenario). If the user does have an entry,
everything goes on as business as usual. But if the user is not listed, the app
throws an exception that creates conditions equivalent to the ones you would
experience on receiving an invalid token. Simple!
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.
Click here to view code image
SecurityTokenValidated = (context) =>
{
string userID =
context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameId
entifier).Value;
Claim userHair = new
Claim("http://mycustomclaims/hairlength",
RetrieveHairLength(userID), ClaimValueTypes.Double,
"LocalAuthority");
context.AuthenticationTicket.Identity.AddClaim(userHair);
return Task.FromResult(0);
},
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 generated
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.
Click here to view code image
public ActionResult Index()
{
var userHair =
ClaimsPrincipal.Current.FindFirst("http://mycustomclaims/hairleng
th");
return View();
}
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.
AuthorizationCodeReceived
This notification fires only in the case in which the middleware emits a
request for a hybrid flow, where the id_token is accompanied by an
authorization code. I’ll go into more details in a later chapter, after fleshing
out the scenario and introducing other artifacts that come in handy for
dealing with that case.
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:
Click here to view code image
AuthenticationFailed = (context) =>
{
context.OwinContext.Response.Redirect("/Home/Error");
context.HandleResponse();
return Task.FromResult(0);
},
In this code I simply redirect the flow to an error route. Chances are you
will want to do something 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.
OpenIdConnectAuthenticationOptions has a property named
TokenValidationParameters, of type
TokenValidationParameters.
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 values from the metadata will be the only ones
used. However, if you do provide values directly in
TokenValidationParameters, the actual values used will be a
merger of the TokenValidationParameters and what is retrieved
from the metadata (using all the options you learned about in the “Authority
coordinates and validation” section).
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.
Figure 7-16 The TokenValidationParameters instance in
OpenIdConnectAuthenticationOptions, as initialized by the
sample application.
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),
TokenValidationParameters exposes a property for holding the
corresponding value: ValidIssuer, ValidAudience, and
IssuerSigningKey.
What is less known is that TokenValidationParameters also has
an IEnumerable for each of these—ValidIssuers,
ValidAudiences, and IssuerSigningKeys—which are meant to
make it easy for you to manage scenarios in which you need to handle a
small number of alternative values. For example, your app might accept
tokens from two different issuers simultaneously. Or you might use a
different audience for your development and staging deployments but have a
single codebase that automatically works in both.
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 individuals. 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 without 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:
Click here to view code image
TokenValidationParameters = new TokenValidationParameters
{
IssuerValidator = (issuer,token,tvp) =>
{
if(db.Issuers.FirstOrDefault(b => (b.Issuer == issuer))
== null)
return issuer;
else
throw new
SecurityTokenInvalidIssuerException("Invalid issuer");
}
}
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
implementation of ITokenReplayCache and assign an instance to
TokenReplayCache. It’s a very simple interface:
Click here to view code image
public interface ITokenReplayCache
{
bool TryAdd(string securityToken, DateTime expiresOn);
bool TryFind(string securityToken);
}
In a nutshell, you provide the methods for saving new tokens (determining
for how long they need to be kept around) and bringing a token up from
whatever storage technology you decide to use. The cache will be
automatically used at every validation—take that into account when you pit
latency and storage requirements against the likelihood of your app being
targeted by replay attacks.
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
CookieAuthenticationOptions are now in charge of session
duration: ExpireTimeSpan and SlidingExpiration 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 implementation 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:
Click here to view code image
public interface IAuthenticationSessionStore
{
Task<string> StoreAsync(AuthenticationTicket ticket);
Task RenewAsync(string key, AuthenticationTicket ticket);
Task<AuthenticationTicket> RetrieveAsync(string key);
Task RemoveAsync(string key);
}
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.
The application model in Azure AD is designed to sustain many different
functions:
It holds all the data required to support authentication at run time.
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 developer. 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 dig deep to
understand the building blocks and true motivation of each moving part—
and once we emerge, everything will make sense. Don’t worry, the model is
very manageable and, once you get the hang of it, even easy, but some
investment is required to understand it. This chapter is here to help you do
just that.
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.
Note
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:
Head to the Azure portal (https://manage.windowsazure.com), go to
the Azure AD section, select the Applications tab, search for your app,
select it, then click Configuration. You’ll see far more info there than
you provided at creation time. One example you are already familiar
with is the client_id, which is assigned by Azure AD to your app when
it’s created.
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 corresponding 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:
Click here to view code image
https://graph.windows.net/developertenant.onmicrosoft.com/applica
tions?$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:
Click here to view code image
{
"odata.metadata":
"https://graph.windows.net/developertenant.onmicrosoft.com/$metad
ata#directoryObjects/Microsoft.DirectoryServices.Application",
"value": [
{
"odata.type": "Microsoft.DirectoryServices.Application",
"objectType": "Application",
"objectId": "c806648a-f27d-43fd-9f18-999f7708fcfc",
"deletionTimestamp": null,
"appId": "e8040965-f52a-4494-96ab-0ef07b591e3f",
"appRoles": [],
"availableToOtherTenants": false,
"displayName": "WebAppChapter5",
"errorUrl": null,
"groupMembershipClaims": null,
"homepage": "https://localhost:44300/",
"identifierUris": [
"https://localhost:44300/WebProjectChapter5"
],
"keyCredentials": [],
"knownClientApplications": [],
"logoutUrl": null,
"oauth2AllowImplicitFlow": false,
"oauth2AllowUrlPathMatching": false,
"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"
}
],
"oauth2RequirePostResponse": false,
"passwordCredentials": [],
"publicClient": null,
"replyUrls": [
"https://localhost:44300/"
],
"requiredResourceAccess": [
{
"resourceAppId": "00000002-0000-0000-c000-
000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
}
]
}
],
"samlMetadataUrl": null
}
]
}
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.
The most “meta” properties here are objectId and
deletionTimestamp.
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.
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.
Note
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.
Figure 8-3 The oauth2PermissionGrant recording in the directory
that user 1 consented for the app represented by ServicePrincipal 1
to access ServicePrincipal N with the permission stored in the
property scope, in itself picked from one of the permissions exposed by
the original Application N oauth2Permissions section.
Figure 8-4 Subsequent consent operations create more
oauth2PermissionGrant entries in the directory, one for each new
user consenting for the application.
Important
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.
Group.Read.All, Group.ReadWrite.All
These new permissions are still in preview at this point, so I hesitate to give
too detailed a description here. The idea is that groups and group
membership are important information and deserve their own permissions so
that access can be requested and granted explicitly. Group.Read.All
allows an app to read the basic profile attributes of groups and the groups
they are a member of. Group.ReadWrite.All allows an app to access
the full profile of groups and to change the hierarchy by creating new groups
and updating existing ones. Both permissions alone won’t grant access to the
users in the groups—to obtain that, the app also needs to request some
User.Read* permission.
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.
Note
"requiredResourceAccess": [
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "78c8a3c8-a07e-4b9e-af1b-b5ccab50a175",
"type": "Scope"
},
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
}
]
}
Thanks to our magical Table 8-1, we know those to be the correct
permissions.
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
oauth2PermissionGrants 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 succeed: 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.
Figure 8-6 The consent prompt presented to an admin user.
Grant the consent—you’ll find yourself signed in to the application. That
done, take a look at what changed in oauth2PermissionGrants:
Click here to view code image
{
"odata.metadata":
"https://graph.windows.net/developertenant.onmicrosoft.com/$metad
ata#oauth2PermissionGrants",
"value": [
{
"clientId": "725a2d9a-6707-4127-8131-4f9106d771de",
"consentType": "Principal",
"expiryTime": "2016-02-26T18:17:06.8442687",
"objectId": "mi1acgdnJ0GBMU-
RBtdx3knIMYJNhOpOkFrsIuF86Y_VUmVPfKg_R6aK4EVKgQSW",
"principalId": "4f6552d5-a87c-473f-a68a-e0454a810496",
"resourceId": "8231c849-844d-4eea-905a-ec22e17ce98f",
"scope": "Directory.Write UserProfile.Read",
"startTime": "0001-01-01T00:00:00"
},
{
"clientId": "725a2d9a-6707-4127-8131-4f9106d771de",
"consentType": "Principal",
"expiryTime": "2016-02-26T00:50:43.3860871",
"objectId": "mi1acgdnJ0GBMU-
RBtdx3knIMYJNhOpOkFrsIuF86Y9KENMTkWjSRaS-glgajkZb",
"principalId": "13d3104a-6891-45d2-a4be-82581a8e465b",
"resourceId": "8231c849-844d-4eea-905a-ec22e17ce98f",
"scope": "UserProfile.Read",
"startTime": "0001-01-01T00:00:00"
}
]
}
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 applications 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.
Scopes can’t grant to the app more power than their user has!
I want to make sure you don’t fall for a common misconception
here. Scopes are a way of delegating to the app some of the
capabilities of their current user. In the most extreme case, this
means that an app can be as powerful as its current user (full
user impersonation). What can never happen via delegated
permissions is that an app can do more than what its user can. If
a user cannot write to the directory, the fact that the app obtains
Directory.ReadWrite.All does not mean that such user
can now use the app for writing to the directory! What that
scope really means is that if the current user of the app has that
capability, the app has that capability, too. If the user does not
have that capability, he or she cannot delegate it to the
application. As you will see later, applications can have their
own permissions (as opposed to delegated permissions) that are
independent from their current user and that can be used when
the app needs to perform things that would not normally be
within the possibilities of its users.
Let’s give it a try and see what happens. From Chapter 7, you now know
how to modify authentication 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
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.
Figure 8-7 The admin consent dialog.
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:
Click here to view code image
{
"odata.metadata":
"https://graph.windows.net/developertenant.onmicrosoft.com/$metad
ata#oauth2PermissionGrants",
"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"
},
{
"clientId": "725a2d9a-6707-4127-8131-4f9106d771de",
"consentType": "Principal",
"expiryTime": "2016-02-26T18:17:06.8442687",
"objectId": "mi1acgdnJ0GBMU-
RBtdx3knIMYJNhOpOkFrsIuF86Y_VUmVPfKg_R6aK4EVKgQSW",
"principalId": "4f6552d5-a87c-473f-a68a-e0454a810496",
"resourceId": "8231c849-844d-4eea-905a-ec22e17ce98f",
"scope": "Directory.Write UserProfile.Read",
"startTime": "0001-01-01T00:00:00"
},
{
"clientId": "725a2d9a-6707-4127-8131-4f9106d771de",
"consentType": "Principal",
"expiryTime": "2016-02-26T00:50:43.3860871",
"objectId": "mi1acgdnJ0GBMU-
RBtdx3knIMYJNhOpOkFrsIuF86Y9KENMTkWjSRaS-glgajkZb",
"principalId": "13d3104a-6891-45d2-a4be-82581a8e465b",
"resourceId": "8231c849-844d-4eea-905a-ec22e17ce98f",
"scope": "UserProfile.Read",
"startTime": "0001-01-01T00:00:00"
}
]
}
Note
Note
Multitenancy
How to develop apps that can be consumed by multiple organizations is such
a large topic that for some time I wondered whether I should devote an entire
chapter to it. I ultimately decided against that. Even if this is going to be a
very large section, it still is a logical extension of what you have been
studying so far in this chapter.
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 middleware 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.
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.
Consenting to an app across tenants
The section about the Application object earlier in this chapter, and
specifically the explanation of the availableToOtherTenants
property, already anticipated most of what you need to know about creating
multitenant applications. All apps are created for being used exclusively
within their own tenant, and only a tenant admin can promote an app to be
available across organizations. Today, this is done by flipping a switch
labeled “Application is multi-tenant” on the Configuration page of the
application on the Azure portal, and this has the effect of setting the
availableToOtherTenants app property to true. Also, an app is
required to have an App ID Uri (one of the elements in the
identifierUris collection in the Application object) whose host
portion corresponds to a domain registered for the tenant. In the sample I
have been using through the last couple of chapters, that means that you’d
need to set the App ID Uri to something like
https://developertenant.onmicrosoft.com/MarioApp1.
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.
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 tenant, as that’s the best way of experimenting with
admin-only features. If you already have a second tenant and an account in
it, great! If you don’t, create one tenant, create a user in it, then come back
and pick up the flow from here.
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 tenant 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 application. 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.
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).
If you use the Azure portal to assign more users to the app, you’ll see
corresponding new AppRoleAssignment entries appearing in the
application. By the way, the query I used for getting the list of
AppRoleAssignments for my app is:
Click here to view code image
https://graph.windows.net/developertenant.onmicrosoft.com/service
Principals/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:
“error=access_denied&error_description=AADSTS50105: The
signed in user ‘fabio@developertenant.onmicrosoft.com’ is not
assigned to a role for the application
‘developertenant.onmicrosoft.com’.”
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 necessarily 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:
Click here to view code image
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"description": "Approvers can mark documents as
approved",
"displayName": "Approver",
"id": "8F29F99B-5C77-4FBA-A310-4A5C0574E8FF",
"isEnabled": "true",
"value": "approver"
},
{
"allowedMemberTypes": [
"User"
],
"description": "Reviewers can read documents",
"displayName": "Reviewer",
"id": "0866623F-2159-4F90-A575-2D1D4D3F7391",
"isEnabled": "true",
"value": "reviewer"
}
],
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
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.
Note
From your own application’s code, you can find out the same information
through the usual
ClaimsPrincipal.Current.FindFirst("roles") or, given that
this is a multivalue claim, FindAll. Once you have the value, you can do
whatever the semantic you assigned to the role suggests that your code
should do: allow or deny access to the method being called, change
environment settings to match the preferences of the caller, and so on.
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:
Click here to view code image
TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "roles",
}
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.
While delegated permissions are represented in Azure AD via
oauth2Permission in the Application object and the
oauth2PermissionsGrants collection in the ServicePrincipal
table, Azure AD represents application permissions via
Application.appRoles and
ServicePrincipal.appRoleAssignedTo.
The AppRole entity is used to declare application permissions just as
you have seen for the application roles case, with the difference that
allowedMemberTypes must include an entry of value
“Application”. To clarify that point, let’s once again turn to the
Directory Graph API ServicePrincipal and examine its appRoles
collection:
Click here to view code image
"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
You can think of each of those roles as permissions that can be requested
by applications invoking 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.
A client application needs to declare in advance what application
permissions (that is, application roles) it requires. That is currently done via
the Azure portal, in the Permission To Other Application section of the
Configure tab. In Figure 8-5 earlier, you can see that the middle column of
the screen contains a drop-down labeled Application Permissions, in that
case specifying the options available for the Directory Graph API. It is
operated much as you learned about for the Delegated Permissions list, but
the entries exposed in Application Permissions are the ones in the target
resource from its appRoles collection, and specifically the ones marked as
Application in allowedMemberTypes.
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:
Click here to view code image
"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 provisioning, 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 Azure
AD: whereas every other resource has its consent settings recorded in the
entities described in this chapter, as of today clients accessing the Graph API
record the application permissions consent for it elsewhere. I won’t go into
further details for two reasons. One, it would not help you understand how
application permissions work in general, given that each and every other
resource does use appRoleAssignedTo. Two, there is talk of changing
the Directory Graph API behavior so that it will start acting like any other
resource—it’s entirely possible that this will already be the case once the
book is in your hands, but given that it’s not for sure, I am not taking any
chances.
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 administrators: 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",
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:
Click here to view code image
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:
Click here to view code image
{
"odata.metadata":
"https://graph.windows.net/developertenant.onmicrosoft.com/$metad
ata#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.
Consuming groups entails more or less the same operations described for
roles and ClaimsPrincipal. You can even assign groups as the
RoleClaimType if that’s the strategy you usually enact for groups
(traditional IsInRole actually works against Windows groups on-
premises, often creating a lot of confusion).
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 aggregated 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:
Click here to view code image
"_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 applications 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 administrators 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 interlocking 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.
Chapter 9. Consuming and exposing a web API
protected by Azure Active Directory
Application credentials
If you recall the description of the OAuth2 authorization-code grant from
Chapter 2, you know that in order to redeem an authorization code, your
application must perform an authenticated request against the token
endpoint. Applications authenticate with Azure AD by using application
credentials that are assigned directly through their Application object.
Those credentials are stored in the properties passwordCredentials
and keyCredentials, which you encountered in Chapter 8. A more
precise description would say that those properties provide references to the
actual credential values: for security reasons, once assigned, those values can
never be retrieved again. If you lose track of them, your only recourse is to
create and assign new credentials.
How do you assign credentials to an application? One very easy way is to
let Visual Studio do the work for you. If you created the app using the
ASP.NET project templates in Visual Studio 2015, selecting the Read
Directory check box in the authentication management portion of the project
wizard will generate and assign credentials for your app automatically.
However, for existing applications and for all the cases in which you are not
using Visual Studio, the most common way is to assign application
credentials via the Azure management portal. If you head to the Configure
tab of the application entry in the Azure AD area of the portal, you’ll find a
section labeled Keys. Here you can add a new key by selecting a duration
(choices vary from one to two years of validity) and saving the application.
Immediately after saving, the portal will display the value of the
autogenerated string key. As I mentioned, this is the only time you have to
save it. Your application will need to access it later on, during the
authorization-code redemption flow and, as you will see later in the chapter,
for any flow requiring the app to talk to the Azure AD token endpoint.
Note
This naming stems from the way in which this credential type is meant to
be used: the key string is simply included in the request in the
client_secret property of the request, kind of like a password.
The keyCredentials property, conversely, is meant to work with the
private/public key pairs from X.509 certificates. Azure AD stores a
certificate holding the public portion of the key pair. The application uses the
corresponding private key to sign a JWT assertion, which is attached to the
request to the token endpoint. Azure AD uses the public key to verify that
the assertion was signed by the private key owner, and if everything checks
out, the application is considered authenticated. From a security standpoint,
this method has clear advantages over the shared-secret model. Those
advantages come at the price of more complexity in the app (the certificate
must be stored and used for signing) and in provisioning. Although, for the
application side of things, the Azure AD libraries can keep things simple by
taking care of handling signatures and request management transparently, as
of today provisioning a certificate as an application credential in Azure AD
is possible only via Office 365 PowerShell cmdlets.
For the sake of keeping things simpler, in this chapter I will work with
key-string credentials. If you want to follow along, use any of the techniques
described previously to get a key string assigned to your application and be
sure to save the string’s bits somewhere—you’ll need them in the code soon
enough.
Handling AuthorizationCodeReceived
Without further ado, let’s go ahead and add code to retrieve and redeem the
authorization code (no pun intended). In Chapter 7, “The OWIN OpenID
Connect middleware,” you learned about the existence of
AuthorizationCodeReceived, a notification in the OpenID Connect
middleware that is invoked in case the authorization response from the
authority includes an authorization code. That’s where we are going to place
our code-redemption logic.
This is where the Active Directory Authentication Library (ADAL) comes
into play. As you learned in Chapter 4, “Introducing the identity developer
libraries,” the ADAL can transparently take care of handling
communications with Azure AD and cache tokens. You are about to see that
in action.
Important
Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -
Version 2.19.208020213
That is the latest package at the time of writing. However, updates and
bug fixes are released all the time, so be sure you also run the following to
get the latest 2.* release:
Click here to view code image
Update-Package Microsoft.IdentityModel.Clients.ActiveDirectory
Once you’ve done that, you are ready to add the implementation of
AuthorizationCodeReceived:
Click here to view code image
AuthorizationCodeReceived = (context) =>
{
Debug.WriteLine("*** AuthorizationCodeReceived");
string ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081";
string Authority =
"https://login.microsoftonline.com/DeveloperTenant.onmicrosoft.co
m";
string appKey =
"a3fQREiyhqpYL10OO6hfCW+xke/TyP2oIQ6vgu68eoE=";
string resourceId = "https://graph.windows.net";
var code = context.Code;
AuthenticationContext authContext = new
AuthenticationContext(Authority);
ClientCredential credential = new ClientCredential(ClientId,
appKey);
AuthenticationResult result =
authContext.AcquireTokenByAuthorizationCode(code,
new
Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path))
,
credential,
resourceId);
return Task.FromResult(0);
},
Remember, the goal of that code is to redeem the authorization code you
got during the sign-in via the OpenID Connect hybrid flow. To understand in
detail how that is accomplished, you need to brush up on the high-level
description of how ADAL works, and specifically the diagram in Figure 4-4
in Chapter 4. ADAL is a token-requestor library, which helps you to obtain
tokens from Active Directory to access resources. It does so by offering you
primitives that model the main actors and artifacts involved in such
transactions.
Ignore the various declarations at the beginning of the method and
consider the line initializing AuthenticationContext.
AuthenticationContext is a class meant to represent in your code the
directory tenant you want to work with. Here it is initialized with
“https://login.microsoftonline.com/DeveloperTenant.onmicrosoft.com”, the
same tenant I’ve been using for initializing the OpenID Connect
middleware. From now on, whenever I need something from my tenant, I
know I can use authContext to access the tenant’s authentication
capabilities.
The main primitive offered by AuthenticationContext is the
method AcquireToken and all its variants. Its function is simply to do
whatever is necessary to obtain a token from the tenant modeled by
AuthenticationContext, complying with the requirements
represented by the parameters passed to AcquireToken. There are as
many AcquireToken overloads and variants as there are supported
scenarios. All you need to do is pass everything you know about your
scenario (for example, the client_id of your app, the resource you need a
token for, and so on), and AcquireToken will do its best to retrieve a
suitable token, while minimizing user prompts and network traffic.
In this specific case, we know that we want to get a token by redeeming
an authorization code, we know that redeeming a code requires application
credentials, and we know that we want that token for accessing the Graph
API. In that light, the rest of the code is easy to understand:
The ClientCredential initialization instantiates a class meant to
represent application credentials. Note that it takes the app’s client_id
and the string key I discussed early on. In actual production code you
would not hardcode the key but retrieve it from a secure place (such as
encrypted storage or a service such as Azure Key Vault).
The intent of the call to AcquireTokenByAuthorizationCode
is self-explanatory, and so is the use of the code and credential
parameters. The second parameter represents the redirect_uri
registered for the client (although it won’t be used in this flow, Azure
AD expects that in the request). The resourceId is the identifier of
the resource we want a token for, in this case the Graph API. This can
be the value you find in the App ID URI field in the application entry
in the Azure portal, but in more general terms, it can be any of the
entries in the servicePrincipalNames property of the
ServicePrincipal representing the resource, or the union of the
identifierUris list and appId properties of the corresponding
Application object.
Note that the call can result in one exception, so you should plan your
code accordingly.
The outcome of the operation is recorded in one instance of
AuthenticationResult. Figure 9-2 shows what it looks like.
Figure 9-2 A typical AuthenticationResult instance.
Before I go into the details of the main properties of
AuthenticationResult, I want to highlight a key point: you can
ignore most of the properties shown here. As you will see in a few pages,
ADAL automatically and transparently takes care of storing tokens and
keeping sessions fresh without requiring any explicit action from you.
Developers who are used to operating at the protocol level or who use lower-
level libraries expect to have to use some of those properties to write logic
for handling token expiration, refreshes, and so on, but that is not necessary
when you use ADAL. ADAL will do that for you, and given that Azure AD
offers some special tricks, ADAL will do it better than you possibly could. In
fact, to remove all temptation, properties such as RefreshToken are
poised to disappear from AuthenticationResult in ADAL v3! That
clarified, here’s a cursory description of the main properties:
AccessToken is really the main result you are after—it is the
token referenced in AcquireToken*. We’ll use it for securing our
call to the Graph API later on.
AccessTokenType declares the type of token usage and
verification meant to be applied to the returned access token. Today it
is always “Bearer”; you’ll see in a few pages what that means.
ExpiresOn indicates the instant at which the access token will no
longer be valid. Today all Azure AD–issued access tokens last one
hour from the instant of issuance, but that will likely become
configurable in the future.
IdToken contains information about the authentication that had to
take place to lead to the issuance of the access token. In this particular
case, the user signing in to the web app and the user obtaining the
access token happen to be the same, but in generic OAuth2 scenarios
that is not the case. I’ll explore that scenario more in depth later.
IsMultipleRefreshToken is an Azure AD–specific property of
the refresh token, which signals whether the current refresh token can
be used for obtaining access tokens for multiple resources in the same
tenant. More details later.
RefreshToken holds the bits of the actual refresh token, whose
function I will describe shortly. Don’t get too attached to this property.
As I mentioned, ADAL automatically uses it behind the scenes, and in
ADAL v3 it will no longer be returned here.
TenantId carries the identifier of the Azure AD tenant that issued
the requested token. In Chapter 8 you learned about the existence of
the common endpoint: when you use common to initialize
AuthenticationContext,
AuthenticationResult.TenantId tells you which tenant the
end user ultimately chose to authenticate with.
UserInfo presents some of the information from the IdToken
property in a more readily consumable format, plus some occasional
extra information (such as the imminent expiration of the user
account’s password). Note that the name of this property is a bit
unfortunate, given that it is a namesake for the corresponding OpenID
Connect endpoint; however, the property predates the spec, so short of
breaking compatibility, it could not really be fixed. Remember that the
two have nothing to do with each other.
That’s it! Before using the resulting access token, however, let’s take a
look at the traffic our code generated.
resource=https%3A%2F%2Fgraph.windows.net&
client_id=c3d5b1ad-ae77-49ac-8a86-dd39a2f91081&
client_secret=a3fQREiyhqpYL10OO6hfCW%2Bxke%2FTyP2oIQ6vgu68eoE%3D&
grant_type=authorization_code&
code=AAABAAAAiL9K [..SNIP..] PPf7ErO6oDyZSeiD_UgAA&
redirect_uri=https%3A%2F%2Flocalhost%3A44300%2F
In the POST, you can identify all the parameters passed in. The POST
recipient is the token endpoint of the authority passed in
AuthenticationContext; the body indicates the resource,
client_id, client_secret, code, and redirect_uri as passed
to AcquireTokenByAuthorizationCode.
Just for kicks
What would this trace look like if you used a certificate instead
of a string key? Instead of a ClientCredential, you would
have used a ClientAssertionCertificate. And the
body of the request would have looked slightly different:
Click here to view code image
resource=https%3A%2F%2Fgraph.windows.net&
client_id= c3d5b1ad-ae77-49ac-8a86-dd39a2f91081&
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclien
t-assertion-type%3Ajwt-bearer&
client_assertion=eyJhbGciOi[...SNIP...]-j5UBo1A&
grant_type=authorization_code&
code=AAABAAAAiL9K [...SNIP...] PPf7ErO6oDyZSeiD_UgAA&
redirect_uri=https%3A%2F%2Flocalhost%3A44300%2F
One important thing to notice here is the use of the resource parameter,
which you encountered earlier in Chapter 6. Other providers implementing
this flow would likely not specify anything, given that the resource is almost
always colocated with the authorization server itself, or they would specify
scopes. Please take a moment to go back to Chapter 6, to the section
“Parameters omitted in the default request,” and refresh your memory on
why the current version of the Azure AD model uses resources instead of
scopes.
The response from the token endpoint is also not particularly surprising,
especially after our peek into AuthenticationResult.
Click here to view code image
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
x-ms-request-id: e551a34e-a2a5-4989-b537-cdb828830269
client-request-id: 172e30f9-54f9-4770-b61b-3aadcbbb8892
x-ms-gateway-service-instanceid: ESTSFE_IN_153
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
Set-Cookie: flight-uxoptin=true; path=/; secure; HttpOnly
Set-Cookie: x-ms-gateway-slice=productionb; path=/; secure;
HttpOnly
Set-Cookie: stsservicecookie=ests; path=/; secure; HttpOnly
X-Powered-By: ASP.NET
Date: Mon, 12 Oct 2015 20:44:09 GMT
Content-Length: 3809
{
"token_type":"Bearer",
"expires_in":"3599",
"scope":"User.Read",
"expires_on":"1444686250","not_before":"1444682350",
"resource":"https://graph.windows.net",
"pwd_exp":"641813","pwd_url":"https://portal.microsoftonline.com/
ChangePassword.aspx",
"access_token":"eyJ0eX [...SNIP...]HWE8aMjw",
"refresh_token":"AAABA [...SNIP...] OIuMXIAA",
"id_token":"eyJ0eXAi [...SNIP...] gZdORQ"
}
That’s all it takes to get a token for calling an API protected by Azure AD
for which your app requested permissions. Next, you’ll see what’s required
to actually use the token to gain access to a protected API.
The idea is that the resource will expect the token in such a header and
validate it before granting access. As is the tradition for OAuth2, no details
are given in the spec about the format of the token. As a result, there is no
prescriptive guidance on what validation looks like. Every provider and
protected resource will privately negotiate the details. In the second part of
this chapter, you will learn how that works for Azure AD, when you set up
the validation logic for your own web API.
A client should NEVER look inside an access token
I made this point in Chapter 4 while describing token-requestor
libraries, but it is worth stressing it here again. Clients
requesting an access token for accessing a protected resource
should treat that token as an opaque blob. It is extremely
tempting to peek into that token from the client app’s code,
given that it often contains interesting info, but that is truly a
recipe for disaster. From the client’s perspective, the token’s
only function is to gain access to a protected resource. Details
such as token format, what claims it contains, and so on are a
contract between the resource and the token issuer, a contract in
which the client plays no role. If you write client code that takes
a dependency on the content of the access token, as soon as that
content changes (for example, if the issuer starts encrypting the
token so that only the target API can access its content), your
client code will be broken with no recourse. This is one of the
worst antipatterns I have seen in my years working in the
identity space, and it often leads to the unrecoverable loss of
functionality. Don’t give in to the temptation to parse the access
token from the client.
The term “bearer” here hints at the only aspect of the token-validation
logic that the spec does provide. To use a bearer token, a client is simply
required to attach it to the request. This is a bit like money: to use a
banknote, all you need to do is hand it over—the recipient does not need to
do any verification other than knowing the authenticity of the banknote.
Incidentally, that’s why you need to be very careful when handling money
and bearer tokens alike, because if somebody else gets ahold of them, they
can use them with no limitations. Think about it the next time you are
tempted to forgo setting up HTTPS for your web apps!
Let’s add some simple code in AuthorizationCodeReceived to
call the Graph API just after the call to
AcquireTokenByAuthorizationCode:
Click here to view code image
//...
string callOutcome = string.Empty;
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response =
httpClient.GetAsync("https://graph.windows.net/me?api-
version=1.6").Result;
if (response.IsSuccessStatusCode)
{
callOutcome = response.Content.ReadAsStringAsync().Result;
}
//...
I’ll ignore the Graph API calling syntax for now. At a high level, the code
appears to be doing exactly what I described was necessary for accessing a
protected resource in accordance with the OAuth2 bearer token usage spec.
You add the access token in the Authorization HTTP header right after the
Bearer keyword, and then you perform your call (in this case a simple
GET of the profile of the account that was used for obtaining the token).
This is what the request looks like on the wire:
Click here to view code image
GET https://graph.windows.net/me?api-version=1.6 HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1Q[..SNIP..]jWxB3LG4UtyQ
Host: graph.windows.net
{"odata.metadata":"https://graph.windows.net/myorganization/$meta
data#directoryObjects/Microsoft.DirectoryServices.User/@Element",
"odata.type":"Microsoft.DirectoryServices.User","objectType":"Use
r","objectId":"13d3104a-6891-45d2-a4be-
82581a8e465b","deletionTimestamp":null,"accountEnabled":true,"ass
ignedLicenses":[{"disabledPlans":["bea4c11e-220a-4e6d-8eb8-
8ea15d019f90","0feaeb32-d00e-4d66-bd5a-43b5b83db82c","e95bec33-
7c88-4a70-8e19-b10bd9d0c014"],"skuId":"6fd2c87f-b296-42f0-b197-
1e91e994b900"}],"assignedPlans":[{"assignedTimestamp":"2014-03-
24T06:36:17Z","capabilityStatus":"Enabled","service":"exchange","
servicePlanId":"efb87545-963c-4e0d-99df-69c6916d9eb0"},
{"assignedTimestamp":"2014-03-
24T06:36:17Z","capabilityStatus":"Enabled","service":"SharePoint"
,"servicePlanId":"5dbe027f-2339-4123-9542-606e4d348a72"},
{"assignedTimestamp":"2014-03-
24T06:36:17Z","capabilityStatus":"Enabled","service":"MicrosoftOf
fice","servicePlanId":"43de0ff5-c92c-492b-9116-
175376d08c38"}],"city":null,"companyName":null,"country":null,"de
partment":null,"dirSyncEnabled":null,"displayName":"Mario
Rossi","facsimileTelephoneNumber":null,"givenName":"Mario","immut
ableId":null,"jobTitle":null,"lastDirSyncTime":null,"mail":"mario
@developertenant.onmicrosoft.com","mailNickname":"mario","mobile"
:null,"onPremisesSecurityIdentifier":null,"otherMails":
[],"passwordPolicies":null,"passwordProfile":null,"physicalDelive
ryOfficeName":null,"postalCode":null,"preferredLanguage":"en-
US","provisionedPlans":
[{"capabilityStatus":"Enabled","provisioningStatus":"Success","se
rvice":"exchange"},
{"capabilityStatus":"Enabled","provisioningStatus":"Success","ser
vice":"MicrosoftOffice"},
{"capabilityStatus":"Enabled","provisioningStatus":"Success","ser
vice":"SharePoint"}],"provisioningErrors":[],"proxyAddresses":
["SMTP:mario@developertenant.onmicrosoft.com"],"sipProxyAddress":
null,"state":null,"streetAddress":null,"surname":"Rossi","telepho
neNumber":null,"thumbnailPhoto@odata.mediaContentType":"image/Jpe
g","usageLocation":"US","userPrincipalName":"mario@developertenan
t.onmicrosoft.com","userType":"Member"}
Promptly, you get back a nice JSON representation of Mario’s profile in
the directory. Congratulations! You just successfully concluded your first
REST API call protected by Azure AD.
If you compare this trace with the ones you studied earlier for web apps,
one thing should jump out: here, there’s not a trace of cookies. Each and
every call is expected to present a suitable bearer token, which the client
obtained before performing the call. For the fun of it, comment out the lines
adding the access token in the header and run the app again. Here’s what
you’ll get as a response:
Click here to view code image
HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: application/json;odata=minimalmetadata;charset=utf-
8
Server: Microsoft-IIS/8.5
ocp-aad-diagnostics-server-name:
JOcOImGbsHySgKlAQbtemgj5KuX+mrNNzouN4cLWfY8=
request-id: 8bb21bef-13bd-401a-87dd-b96b7cd6bfb0
client-request-id: 73eebaa6-e588-472a-98c5-215cba480c42
x-ms-dirapi-data-contract-version: 1.6
Strict-Transport-Security: max-age=31536000; includeSubDomains
Access-Control-Allow-Origin: *
WWW-Authenticate: Bearer realm="myorganization",
error="invalid_token", error_description="Access Token missing or
malformed.",
authorization_uri="https://login.microsoftonline.com/common/oauth
2/authorize", client_id="00000002-0000-0000-c000-000000000000"
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Duration: 328488
X-Powered-By: ASP.NET
Date: Wed, 14 Oct 2015 18:14:52 GMT
Content-Length: 143
{"odata.error":
{"code":"Authentication_MissingOrMalformed","message":
{"lang":"en","value":"Access Token missing or
malformed."},"values":null}}
Get it? Whereas you get a 302 in the case of web sign-on against a
redirect-based web app, here you get a cold 401. In the web sign-on case, the
authentication happens in the context of the browser, hence an
unauthenticated request can be handled by redirecting the user to the place
where he or she can authenticate. However, in the case of a web API, the
client is responsible for obtaining the necessary token out of band:
furthermore, the client isn’t really a browser (here the call is performed from
the app’s code-behind on the server), so the 302 cannot be used to prompt
some kind of action.
Using cookies for protecting a web API is a very common antipattern.
People often use it when performing AJAX calls: they secure the entire web
app by using redirect-based mechanisms such as the OpenID Connect or
WS-Federation middlewares, and then they simply make AJAX calls that
leverage the fact that the browser automatically attaches cookies to requests
and that the cookie middleware validates them. It is an antipattern because it
does not work very well. When cookies expire, the AJAX calls receive 302s,
but those can’t be exploited directly. As soon as you try to access the API
from a different client (a back end or a mobile app), you suddenly discover
that there is no mechanism to obtain suitable cookies. As soon as you need to
call the API from different domains, you find out that you don’t have
suitable cookies for those domains, no mechanism for obtaining them, and
so on. I’ll talk more about this in the sections about exposing your own API.
The Directory Graph API
Although the Azure AD Graph API is not strictly an
authentication feature, it plays such a pivotal role in all things
related to Azure AD that I cannot avoid giving you at least a
quick overview. For more details, I recommend that you refer to
the comprehensive online documentation pages at
https://msdn.microsoft.com/en-
us/Library/Azure/Ad/Graph/api/api-catalog.
The Graph API is an OData API that offers programmatic
access to the entities that constitute an Azure AD tenant and all
that it contains. By “programmatic access,” I mean performing
HTTP GET, POST, PUT, PATCH, and DELETE requests
against directory entities, accompanied by a suitable access
token. (Nearly) every resource in the directory can be
represented as a URL by using the Graph API—as long as you
structure the URL according to the base template:
Click here to view code image
https://graph.windows.net/<tenant>/<resource path>?<api
version>[odata parameters]
if (response.IsSuccessStatusCode)
{
ViewBag.Message =
response.Content.ReadAsStringAsync().Result;
}
return View();
}
The code is nearly the same as what you wrote earlier. The main
difference is that you invoke AcquireTokenSilent instead of obtaining
a token via an authorization code. AcquireTokenSilent is a method
that attempts to retrieve the requested token only by using the artifacts
already present in the ADAL cache. It has the suffix “silent” because the
method is guaranteed not to throw up any UI, which is somewhat moot in
this particular scenario because no flavor of AcquireToken* shows any
UI on the server side. However, ADAL works both with web apps on the
server and native clients on devices, and the latter can interactively prompt
the user when requesting a token.
AcquireTokenSilent’s parameters can be thought of as conditions
that must hold true for the requested token. It has to be scoped for the
resource specified (in our case, the Graph API), it has to be issued for the
specified client_id (passed indirectly through the ClientCredential
instance), and it has to have been issued for the specified user account (in
our case, we declare that any user is fine, via the
UserIdentifier.AnyUser constant).
By default, ADAL uses an in-memory cache. When the code in About()
executes, that cache has already been primed by the code in
AuthorizationCodeReceived. The call to
AcquireTokenByAuthorizationCode has the effect of saving in the
cache a token for accessing the Graph. Hence, assuming that the call to
About() takes place within an hour from initialization and that the process
does not recycle, the call to AcquireTokenSilent will find a matching
access token in the cache and return it right away. Go ahead, try this
yourself. Start Fiddler, and then start the app, sign in, and click About. You’ll
see that the call to AcquireTokenSilent correctly returns the desired
token, but Fiddler will show no network traffic toward Azure AD for that
call.
This functionality is pretty handy, but the serious return of investment
from using ADAL emerges when refresh tokens come into play. Allow me to
spend a moment to describe how refresh tokens work in OAuth2 and Azure
AD. After that, I’ll come back to the code and describe how it all fits
together.
Access tokens are short-lived for security reasons. Say that you issue an
access token for Mario, who can now use it to access your company
resources. Imagine that you discover that Mario is stealing and you decide to
terminate him. Given that access tokens have no revocation mechanism,
Mario will still be able to access resources with that access token until it
expires; clearly, it is in your interest to issue access tokens with a short
validity period. At the same time, forcing Mario to go through the credential-
gathering dance to obtain a new token every time the old one expires leads to
unacceptable experiences, or to antipatterns such as clients caching user
credentials (defeating OAuth2’s purpose). How do you reconcile those
seemingly contrasting requirements? Enter the refresh token.
The refresh token is an artifact that is issued alongside the access token.
Whenever an access token expires, the client can use the refresh token to go
back to the authorization server’s token endpoint (without requiring any user
interaction) and ask for a new access token. If the conditions are right (the
user still exists in the system, consent has not been revoked, and so on), the
client will be issued a new access token. Problem solved. The flow, dubbed a
“refresh token grant” in the OAuth2 core specs, is shown in Figure 9-3.
resource=https%3A%2F%2Fgraph.windows.net&
client_id=c3d5b1ad-ae77-49ac-8a86-
dd39a2f91081&client_secret=a3fQREiyhqpYL10OO6hfCW%2Bxke%2FTyP2oIQ
6vgu68eoE%3D&
grant_type=refresh_token&
refresh_token=AAA[...SNIP...]MIAA
As you can see in the request, the application must authenticate with the
token endpoint by presenting its credentials alongside the refresh token bits.
That explains why AcquireTokenSilent required a
ClientCredential parameter. Note that overloads of
AcquireTokenSilent that do not require application credentials exist,
but they are meant to be used with public clients (that is to say, native and
mobile apps). Web applications are modeled as confidential clients in Azure
AD and can act autonomously; hence, they are required to authenticate.
The response is equivalent to the one received from the authorization-code
grant flow, although here we don’t get an id_token:
Click here to view code image
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
x-ms-request-id: 05af78be-c6fd-44aa-95b2-42ab4ee77b5d
client-request-id: 5bbaccbb-82d6-483a-b224-14301c9bfbd7
x-ms-gateway-service-instanceid: ESTSFE_IN_226
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
Set-Cookie: flight-uxoptin=true; path=/; secure; HttpOnly
Set-Cookie: x-ms-gateway-slice=productionb; path=/; secure;
HttpOnly
Set-Cookie: stsservicecookie=ests; path=/; secure; HttpOnly
X-Powered-By: ASP.NET
Date: Wed, 14 Oct 2015 22:43:50 GMT
Content-Length: 2371
{"token_type":"Bearer","expires_in":"3599","scope":"User.Read","e
xpires_on":"1444866230","not_before":"1444862330","resource":"htt
ps://graph.windows.net","pwd_exp":"461833","pwd_url":"https://por
tal.microsoftonline.com/ChangePassword.aspx","access_token":"eyJ0
e
[...SNIP...]XAoUPJPqYQ","refresh_token":"AAAB[...SNIP...]MOIAA"}
The nice thing about this exchange is that it all happens transparently
within the call to AcquireTokenSilent. Technically, as long as you use
ADAL, you don’t even need to know that refresh tokens exist—you reap the
benefits of long sessions without having to deal with the associated
complexity.
Note
The advantage of this approach becomes even more evident when you
consider another special property of Azure AD refresh tokens—their ability
to be used to get access tokens for multiple resources.
Multiresource refresh tokens As you have learned throughout this book,
and in particular in Chapter 8, Azure AD models very precisely the
relationships between applications and resources. Requests for access tokens
need to specify the resource for which the desired token is meant, and the
resulting access token is scoped down so that it can be used only against the
resource it has been issued for. In the section “Exposing a protected web
API,” you’ll learn in detail how that takes place.
In the sample app we are calling only the Graph API. But what if you
want to also call Office 365 and the Azure API? You already know how
you’d handle that from the permissions-configuration perspective—you’d
just add the required resources and permissions in the client’s
Application. In turn, that would cause the consent prompt to ask for all
the required permissions at once. But here’s the important part: the refresh
token you receive from Azure AD knows about all the resources you granted
consent for. As a result, it doesn’t matter what resource you ask for on your
first AcquireToken* call. Once ADAL gets the refresh token, it can use it
to obtain access tokens for any of the other resources your client is
configured to have access to.
In practice that means that you can make new calls to
AcquireTokenSilent, each time passing the resourceId of any of the
other resources you want. ADAL will transparently use the refresh token
grant to obtain and cache the requested access token. This nice property
earns for Azure AD–issued tokens the moniker multiresource refresh tokens,
or MRRTs. In a sense, you can think of MRRTs as the OAuth2 equivalent of
ticket granting tickets (TGTs) in Kerberos: they are artifacts that allow a user
to obtain tokens to access the resources the directory decides she or he has
access to.
Note that even for those new resources, refresh tokens remain tied to a
particular client ID and user: refresh tokens can only be used together with
the client ID of the application that is used to obtain the refresh token in the
first place, and the user will always be the one that granted the consent
recorded or referenced by the refresh token itself.
Just a closing note on the topic: Until recently, the use of MRRTs was also
limited to the tenant that originally issued the first refresh token. Thanks to a
recent change in Azure AD, however, now you can use MRRTs to ask for
access tokens from any tenant in which the user has a guest account and has
already granted consent for the client app originally used to obtain the first
refresh token. In practice, say that I have an Microsoft account user who is
an administrator for an Azure subscription. All Azure AD tenants created
under that subscription will have a guest account for that user. Say also that I
have tenant A and tenant B and a multitenant web app that needs Graph API
(or any other API) access for which I consented with the same Microsoft
account user in both tenants. I can now obtain a token for the Graph API of
tenant A and then use the refresh token so obtained to request an access
token for the Graph API in tenant B. All I need to do is repeat the
AcquireTokenSilent call, but against an instance of
AuthenticationContext initialized for tenant B, and ensure that the
token cache in the new AuthenticationContext is the same as the
one that was primed with tokens from tenant A. Clear as mud? That’s a good
segue to the next section.
ADAL cache considerations for web applications ADAL began its
existence as a library for native applications, apps meant to be run on
devices and operated by a user engaged in an interactive session. The default
ADAL cache is aligned with that original mission: it is an in-memory cache
that relies on a static store, available process-wide. That means that by
default, each and every AuthenticationContext instance you
initialize within a process will read and write against the same token cache.
However, what works for native clients doesn’t work too well for
applications meant to be executed on midtiers and back ends. Namely:
These applications are accessed by many users at once. Saving all
access tokens in the same store creates isolation issues and presents
challenges when operating at scale: many users, each with as many
tokens as the resources the app accesses on their behalf, can mean
huge numbers and very expensive lookup operations.
These applications are typically deployed on distributed topologies,
where multiple nodes must have access to the same cache.
Cached tokens must survive process recycles and deactivations. Think
of the scenario I already mentioned in Chapter 2, in which you connect
your Facebook account to Twitter so that every time you tweet your
Facebook Wall posts the same update. That is only possible if Twitter
saves in persistent storage the access tokens necessary for calling the
Facebook API—or you’d have to reauthenticate to Facebook to
reacquire the delegated token every time you sign in to Twitter.
For all those reasons, when you are implementing web apps, it is a good
idea to override the default ADAL token cache with a custom
implementation. Unfortunately, the library’s developers cannot predict at
design time what persistent storage you’ll use at run time, hence they can’t
provide a default cache for midtier applications, but the good news is that
implementing a custom cache in ADAL is surprisingly easy.
ADAL’s cache extensibility model isolates you from all the details of its
internal structure, which is always maintained in memory: its primitives are
meant to allow you to persist the token cache on an arbitrary store; hence,
they are mostly concerned about warning you when a read or write operation
is about to happen or just concluded. That gives you an opportunity to
update the private ADAL in-memory copy of the cache from your custom
storage right before a read, or to reflect in persistent storage any changes that
just occurred in the in-memory copy. And, of course, by controlling how the
token cache is instantiated, you can also control its scope: for example, you
can enforce that every web application session have its own cache instance
so that the content of the cache is limited to all the access tokens that a given
web app user accumulated for calling the web API. This breaks down
individual caches into manageable chunks, instead of having to save
potentially millions of sessions in a flat store as the default cache would.
For completeness, here’s a super quick explanation of how the main cache
primitives operate. However, I would recommend that you study the custom-
cache-classes feature in the .NET samples at https://github.com/azure-
samples?utf8=%E2%9C%93&query=openidconnect.
You create a custom cache by deriving from TokenCache. You tell
AuthenticationContext that you want to use your custom cache by
passing an instance of your cache class at construction time, as shown in the
following. Note that you have to pass the same cache instance, or instances
designed to work against the same store, to all the
AuthenticationContext occurrences that you want to share the
cache. In our current sample, that means passing the same custom cache to
the AuthenticationContext constructor calls both in Startup and
in About().
Click here to view code image
AuthenticationContext authContext = new
AuthenticationContext(Authority, new CustomADALCache
(whateverInitDataINeed));
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
Load();
}
public void Load()
{
lock (FileLock)
{
this.Deserialize((byte[])HttpContext.Current.Session[
CacheId]);
}
}
public void Persist()
{
lock (FileLock)
{
// reflect changes in the persistent store
HttpContext.Current.Session[CacheId] =
this.Serialize();
// once the write operation took place, restore the
HasStateChanged bit to false
this.HasStateChanged = false;
}
}
// Empties the persistent store.
public override void Clear()
{
base.Clear();
System.Web.HttpContext.Current.Session.Remove(CacheId);
}
public override void DeleteItem(TokenCacheItem item)
{
base.DeleteItem(item);
Persist();
}
// Triggered right before ADAL needs to access the cache.
// Reload the cache from the persistent store in case it
changed since the last access.
void BeforeAccessNotification(TokenCacheNotificationArgs
args)
{
Load();
}
// Triggered right after ADAL accessed the cache.
void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (this.HasStateChanged)
{
Persist();
}
}
}
The main thing I’ll point out about this implementation is that the store is
initialized per user, so every session with the web app has its own cache
instance. If you want to see a more complete implementation, based on the
Entity Framework, you can look at the more advanced samples (such as the
multitenant ones). Alternatively, you can simply create a new project in
Visual Studio 2015 and be sure to select the Read Directory check box in the
authentication settings wizard. Visual Studio will automatically generate a
custom cache class for you, also based on the Entity Framework.
What to do when a refresh token expires The call to
AcquireTokenSilent can fail, and your code needs to be prepared for
that.
Excluding the obvious case in which the cache is empty, there are two
main reasons that AcquireTokenSilent will fail for the scenario we’ve
examined so far:
There are multiple cached tokens that can satisfy the requirements
imposed by the parameters of the call.
Both the access token and all the suitable refresh tokens have expired.
Let’s get the first case out of the way because it’s the simplest. You
recognize that you are in this situation from the fact that
AcquireTokenSilent fails with an AdalException warning you
that “multiple_matching_tokens_detected: The cache contains multiple
tokens satisfying the requirements. Call AcquireToken again providing more
requirements (e.g. UserId)”. How did you end up with multiple tokens in the
cache for the same client ID, meant for the same resource? Sometimes that’s
intentional: our scenario so far expects the user of the web application and
the user calling the web API to be the same, but that’s definitely not the only
valid scenario. Think of an app showing you multiple Exchange Online
calendars: an administrative assistant might sign in to the web app with his
or her own account and then proceed to get an access token for his or her
own calendar and another access token for his or her boss’s calendar. I’ll
examine similar scenarios more closely later on.
In our sample app, having multiple tokens is more likely the result of a
mistake. For example, if you did not override the default cache with one that
has better user isolation, this is your punishment: multiple concurrent users
access your app and all get an access token for themselves via the call to
AcquireTokenByAuthorizationCode in Startup. Once the
execution reaches the AcquireTokenSilent call in About(), the
cache will contain as many access tokens for the Graph API as there are
concurrent users. Given that in our call we don’t specify for which user we
want the token (we pass UserIdentifier.AnyUser), ADAL does not
know which token should be returned and errors out.
For this specific case the solution is to use a better cache, but in general
you deal with this situation first by inspecting the cache to see whether you
indeed have multiple entries for multiple users. For this task I like to use
Visual Studio’s Immediate window and perform LINQ queries against the
cache to see how many tokens matching my requirement (same client ID for
the same resource) are stored. For example:
Click here to view code image
var usrz = authContext.TokenCache.ReadItems().Where(p =>
p.Resource == "https://graph.windows.net" && p.ClientId ==
"c3d5b1ad-ae77-49ac-8a86-dd39a2f91081");
The fields DisplayableId and UniqueId are the ones that you can
use for indicating to AcquireToken* which user you want a token for.
The former can contain the user’s UPN or email address, depending on what
Azure AD sends. The latter can contain the users’ ObjectId in the directory
when available, or the NameIdentifier in case it is not. You pass one of those
values to a new instance of the UserIdentifier class, and then you pass
that instance in AcquireToken*. For example:
Click here to view code image
authContext.AcquireTokenSilent(resourceId, credential, new
UserIdentifier("mario@developertenant.onmicrosoft.com",UserIdenti
fierType.OptionalDisplayableId);)
If you want to use the UniqueId value—a great idea given that it is
nonreassignable—you’d use UserIdentifierType.UniqueId. If, as
I am doing in the snippet, you want to use the DisplayableId, you can
use either UserIdentifierType.OptionalDisplayableId or
UserIdentifierType.RequiredDisplayableId.
Note
On the midtier,
UserIdentifierType.OptionalDisplayableId and
UserIdentifierType.RequiredDisplayableId are
fully equivalent. If you were using them with an interactive
flavor of AcquireToken* in a native client, that call might
trigger a dialog box in which the user can enter credentials.
RequiredDisplayableId would enforce that the resulting
token match the ID that was passed when calling
AcquireToken*, so it would error out if the end user enters
different credentials; OptionalDisplayableId would
instead accept the outcome of entering new user credentials, as
the UserIdentifier instance would be used only as a way
of inspecting the cache and prepopulating the username field.
Figure 9-4 How Klout handles the reauthorization experience with the
APIs it integrates with.
Now here’s a quick-and-dirty way of modifying the About() action to
achieve a similar effect. Again, the “same user” assumption is of huge help
here. I’ve highlighted the new and interesting bits:
Click here to view code image
public ActionResult About(string reauth)
{
if (reauth == null)
{
string ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081";
string Authority =
"https://login.microsoftonline.com/DeveloperTenant.onmicrosoft.co
m";
string appKey =
"a3fQREiyhqpYL10OO6hfCW+xke/TyP2oIQ6vgu68eoE=";
string resourceId = "https://graph.windows.net";
try
{
ClientCredential credential = new
ClientCredential(ClientId, appKey);
AuthenticationContext authContext = new
AuthenticationContext(Authority);
AuthenticationResult result =
authContext.AcquireTokenSilent(resourceId,
credential,
UserIdentifier.
AnyUser);
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer",
result.AccessToken);
HttpResponseMessage response =
httpClient.GetAsync("https://graph.windows.net/me
?api-version=1.6").Result;
if (response.IsSuccessStatusCode)
{
ViewBag.Message =
response.Content.ReadAsStringAsync().Result;
}
}
catch (AdalException ex)
{
if (ex.ErrorCode ==
"failed_to_acquire_token_silently")
{
Response.Write("<a href=\"./About?
reauth=true\">Your tokens are expired. Click here to
reauth</a>");
}
else
{
// more error handling
}
}
}
else
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri =
"/Home/About", },
OpenIdConnectAuthenticationDefaults.AuthenticationTyp
e);
}
return View();
}
The token acquisition and API call logic is wrapped in a try-catch block.
In the case in which the app fails because AcquireTokenSilent does
not succeed in obtaining a token, we crudely display a message and provide
a link to the same action—but with an extra parameter (of nullable type, so
that nothing happens when the action is invoked without parameters, as is
usually the case). If the end user clicks that link, we know that he or she
intends to perform the reauthentication flow, hence the redirect won’t be a
surprise and won’t disrupt the experience.
At the beginning of the method, the code checks for the presence of that
parameter. If we find it, we trigger a sign-in, specifying that we intend to
come back to the original action once we’re done. Assuming that the sign-in
goes well and that the AcquireTokenByAuthorizationCode runs
successfully to seed the cache, this time around the
AcquireTokenSilent call will succeed. Back to business as usual.
You can experience that flow in action by signing in, placing a breakpoint
right after the AuthenticationContext construction, and then using
the Immediate window for cleaning up the cache (via
authContext.TokenCache.Clear();). That will also show you that
if there’s still a valid session with the web app, token acquisition will take
place without throwing any UI—the user will only experience a quick flash
as the redirect goes through.
Let me stress that the preceding snippet truly is quick and dirty—it’s
meant to make the flow as clear as possible, with the expectation that once
you understand the gist of it, you’ll write the proper error-management code,
break down functionality across multiple actions if it fits your app, and so
on.
NuGets
Once the project is ready, you can take a look around. First, head to
packages.config. If you compare the list of NuGet packages with the list
from the web app project, you’ll see that whereas the latter references
Microsoft.Owin.Security.Cookies and
Microsoft.Owin.Security.OpenIdConnect, the protocol middleware
components used for web API are these:
Microsoft.Owin.Security.ActiveDirectory
Microsoft.Owin.Security.Jwt
Microsoft.Owin.Security.OAuth
As of today, they are all at version 3.0.1.
At first you might find it strange that the middleware implementing the
OAuth2 bearer token usage lives in an Active Directory–specific NuGet
package. But think of what you know about that flow, and the reason will
become immediately obvious. The function of a middleware implementing
that protocol is to validate access tokens, but the format of such access
tokens (hence their validation logic) is left as an exercise to the reader. When
you connect to Azure AD (or ADFS), it is well known in what format the
tokens will be and where to retrieve the metadata containing the validation
coordinates. Hence, we can enshrine such knowledge in ready-to-use
components. Validation for other providers will follow the same rough
functional steps (retrieve the token from the message, validate it,
manufacture a ClaimsPrincipal with the content) but will have to
differ in the actual validation bits.
Let’s see what this all means in concrete terms.
Middleware initialization code and API controllers
Head to the Startup.Auth.cs. Here's the interesting bit:
Click here to view code image
Remember how in the OpenID Connect options you had to specify the
ClientId of the application? The OpenID Connect middleware uses the
ClientId for two purposes—to identify the client app when generating
the sign-in request to the provider and to validate the audience claim in
incoming tokens—to be sure that the caller isn’t simply replaying a token
stolen from someone else. If you need a refresher, head to Chapter 6 and
search for the aud claim in the section “The JWT format.”
Access tokens are different. Whereas id_tokens are tokens meant to be
consumed by the requesting app itself—so the audience of the token is the
requestor itself—access tokens are requested by a client that is (most of the
time) distinct from the resource it is requesting a token for. You already saw
this in action, albeit indirectly, in the calls to AcquireToken* for gaining
access to the Graph API. You had to pass in the call the identifier of the
resource you wanted, which in our case is “https://graph.windows.net”.
What you have not seen yet is that the same resource identifier is placed by
Azure AD in the aud claim from the access token it issues back. The
OAuth2 bearer middleware needs to check that incoming tokens carry the
correct audience, which is why you are required to set that value in the
ValidAudience property at initialization. The value you want to put in
there has to be one of the identifiers that Azure AD uses for representing
your web API app as a resource—that is to say, as a potential recipient of a
token. In most of the developer guidance, the value put there is the App ID
URI, the value you find in the Azure portal on the Configure tab of the app
entry. That corresponds to one of the values from the identifierURIs
property of the corresponding Application object. In our case, the
project template generated a unique URI
(https://developertenant.onmicrosoft.com/SimpleWebAPI) and used it both
while creating the app and for initializing the corresponding value in
web.config. Another possible value is the ClientId of the app itself, as you
have seen in the OpenID Connect case, or the appId property of the
Application object. In general, all the acceptable audience values for the
app are listed in the servicePrincipalNames property of the
ServicePrincipal, which normally is the union of the
identifierURIs and appId property.
Important
From the code generated by the project template wizard you can already
see that ValidAudience is a property of
TokenValidationParameters, a class you should already be very
familiar with thanks to the detailed analysis of its usage in Chapter 7. The
things you learned there about valid values, validation flags, and validators
can be applied here as well. That includes what you learned in Chapter 8
about how to use TokenValidationParameters for manipulating
issuer validation in the case of multitenant apps.
Note
Here’s one last thing I want to point your attention to. If you take a look at
the sample API controller generated by the template,
ValuesController, you’ll see that there’s a blanket [Authorize] on the
entire class. That means that any caller better be authenticated, or they will
be denied access. You’ll see in a few pages what that means in terms of bits
on the wire.
Directory entries
The Application object for the web API contains a default entry in
oauth2Permissions, just like you have seen for portal-created web
apps in Chapter 8. It’s worth taking another quick look at that property here:
Click here to view code image
"oauth2Permissions": [
{
"adminConsentDescription": "Allow the application to
access SimpleWebAPI on behalf of the signed-in user.",
"adminConsentDisplayName": "Access SimpleWebAPI",
"id": "f41d8346-0715-4728-83f2-6ee1d817167c",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allow the application to
access SimpleWebAPI on your behalf.",
"userConsentDisplayName": "Access SimpleWebAPI",
"value": "user_impersonation"
}
],
You’ve already seen that those are the permissions (in this case
permission, singular) that a client can ask for when requesting access to your
API. Without an entry in oauth2Permissions, your app could not
operate as a web API, which is why Azure AD provides a default one. One
thing I have not yet mentioned is that if you want your app and permissions
to show up in the drop-down list of potential clients, your application must
already have a ServicePrincipal in the same tenant. This is why the
web API template provisions one ServicePrincipal for the app
directly at creation time.
The value property of an oauth2Permissions entry is especially
interesting when you’re developing a web API because it holds the value of
the scope that a token will carry to indicate it has been granted the
corresponding permission. As a web API developer, it is your responsibility
to verify the scope values in incoming tokens and decide whether they grant
to the caller the right to do with your API what they are attempting to do at
the moment. Just to make that operation a bit more interesting, I downloaded
the manifest of my web API via the Azure portal, added a new entry, and
then uploaded it again. Now it looks like the following:
Click here to view code image
"oauth2Permissions": [
{
"adminConsentDescription": "Allow the application to
use SimpleWebAPI in write mode.",
"adminConsentDisplayName": "Use SimpleWebAPI in write
mode",
"id": "ae08ca44-4241-449a-abbf-a9a0e0ce2730",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allow the application to use
SimpleWebAPI in write mode.",
"userConsentDisplayName": "Use SimpleWebAPI in write
mode",
"value": "SimpleWebAPI.Write"
},
{
"adminConsentDescription": "Allow the application to
access SimpleWebAPI on behalf of the signed-in user.",
"adminConsentDisplayName": "Access SimpleWebAPI",
"id": "f41d8346-0715-4728-83f2-6ee1d817167c",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allow the application to
access SimpleWebAPI on your behalf.",
"userConsentDisplayName": "Access SimpleWebAPI",
"value": "user_impersonation"
}
],
{
"clientId": "b128da6c-5570-43fa-ab2e-6bc5abb8e6e1",
"consentType": "AllPrincipals",
"expiryTime": "2016-04-14T23:56:39.9710775",
"objectId": "bNoosXBV-kOrLmvFq7jm4Ye5XNQ8LzhPkSsr0-GWByM",
"principalId": null,
"resourceId": "d45cb987-2f3c-4f38-912b-2bd3e1960723",
"scope": "SimpleWebAPI.Write user_impersonation",
"startTime": "0001-01-01T00:00:00"
}
]
}
That’s exactly what we want. The first entry is the old permission, which
allows every user on the tenant to get a token for signing in and for
accessing the profile. The second is the permission establishing that every
tenant user will also be able to access SimpleWebAPI through the web app,
with both permissions granted.
Let’s add a bit of code to the web app to make a vanilla call to our web
API. In fact, you can reuse the code for invoking the Graph API; you just
need to change a couple of lines. Here’s the relevant fragment:
Click here to view code image
{
string ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081";
string Authority =
"https://login.microsoftonline.com/DeveloperTenant.onmicrosoft.co
m";
string appKey =
"a3fQREiyhqpYL10OO6hfCW+xke/TyP2oIQ6vgu68eoE=";
//string resourceId = "https://graph.windows.net";
string resourceId =
"https://developertenant.onmicrosoft.com/SimpleWebAPI";
try
{
ClientCredential credential = new
ClientCredential(ClientId, appKey);
AuthenticationContext authContext = new
AuthenticationContext(Authority);
AuthenticationResult result =
authContext.AcquireTokenSilent(resourceId,
credential,
UserIdentifier.AnyUser);
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer",
result.AccessToken);
HttpResponseMessage response =
//httpClient.GetAsync("https://graph.windows.net/me?
api-version=1.6").Result;
httpClient.GetAsync("https://localhost:44301/api/valu
es").Result;
if (response.IsSuccessStatusCode)
{
//..more stuff
The only items that changed are the resource ID of the token we are
requesting and the URL of the web API itself (which I got from the property
pages of the project in Visual Studio). As expected, the refresh token cached
from the Startup invocation of
AcquireTokenByAuthorizationCode is a MRRT, capable of
obtaining access tokens for any resource the app has been configured to gain
access to. If you set up Fiddler and take a look at the traffic, you’ll see the
same pattern you observed with the calls to the Graph. The only thing that
changes is the recipient of the REST call.
Note
Before I get to how to handle scopes on the web API side, let’s simulate a
couple of error situations you are likely to encounter while working with a
web API.
First, comment out the client code that adds the Authorization header and
try the call again. The API call is going to come back with a 401; the
response object will carry a StatusCode 401, and the ReasonPhrase will
be the dreaded “Unauthorized.” Just as expected, the middleware won’t let
through any calls not carrying a valid token from the intended issuer.
Let’s try something else. Add back the lines that include the token in the
request headers, and then go to the web API project. In the web.config file,
locate the audience value in AppSettings, and change the value somehow
(for example, add some trailing characters). Now go through the call flow
again. You’ll end up with the same error.
That gives you a taste of a hard truth: diagnosing issues with a web API
on the client side is hard. When the bearer token middleware encounters
issues, it doesn’t usually send back errors; rather, it lets the call proceed
without adding to it the identity of the caller. The error message is generated
when the execution reaches the controller decorated with [Authorize], and at
that point the reason that you weren’t able to get a ClaimsPrincipal for
an authenticated user is lost. All you know is that the caller is unauthorized,
and the message returned to the client reflects that. This means that
troubleshooting issues with a web API is best done statically through a series
of checks that in my experience catch the vast majority of the issues:
Does the aud claim in the incoming token match exactly (down to
casing, trailing slashes, HTTP vs. HTTPS) the value used to initialize
ValidAudience (or Audience in the options) in the middleware
initialization?
Does the iss claim in the incoming token match exactly the issuer
indicated in the metadata referenced by Tenant or
MetadataAddress in the middleware initialization logic? Special
attention should be reserved for a multitenant case, as discussed in
Chapter 8.
Is the API route correctly configured for using the OAuth2 bearer
middleware, or is it being protected by some other middleware? More
about this a bit later.
When all those static checks fail, it’s time to turn on tracing or implement
some of the validators in TokenValidationParameters just to place
some breakpoints.
Another thing that you already know but is worth stressing is how a web
API protected via OAuth2 bearer tokens doesn’t do much to help you get a
token. Whereas a failed request against a web app results in a 302 and a web
sign-on request—in other words, an attempt to remediate the situation—here
you get a cold 401, an invitation to get your act together in your own client
logic. It is up to your code to decide how to react to the 401, and if the
reaction entails obtaining a new token, it’s up to your code to drive all the
token-request work.
Note
Processing requests
Let’s now take a look at how to process incoming requests, from dealing
with scope-driven authorization to performing some common
customizations.
Scopes are, as you might imagine, represented as claims. If you fire up
Fiddler again and capture an access token (you learned in Chapter 6 how to
extract JWTs and their claims content from traces), you should see
something such as the following:
Click here to view code image
{
"acr" : "1",
"amr" : [ "pwd" ],
"appid" : "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
"appidacr" : "1",
"aud" :
"https://developertenant.onmicrosoft.com/SimpleWebAPI",
"exp" : 1445208111,
"family_name" : "Rossi",
"given_name" : "Mario",
"iat" : 1445204211,
"ipaddr" : "73.169.211.13",
"iss" : "https://sts.windows.net/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/",
"name" : "Mario Rossi",
"nbf" : 1445204211,
"oid" : "13d3104a-6891-45d2-a4be-82581a8e465b",
"scp" : "SimpleWebAPI.Write user_impersonation",
"sub" : "wrFY8NpHyppkDsmTbQV0ZXRkkAtT2sIhnU1LoJYvYZU",
"tid" : "6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e",
"unique_name" : "mario@developertenant.onmicrosoft.com",
"upn" : "mario@developertenant.onmicrosoft.com",
"ver" : "1.0"
}
This does look remarkably similar to the id_token you saw in Figure 6-3
in Chapter 6. The differences I’ve already mentioned are the audience value
(though technically you could use the client_id of the web API, provided that
you use it consistently in the token request on the client and in the audience
setting on the API) and the presence of a scope (scp) claim. The id_token
also has some extra cryptography tricks used for validation (nonce and
c_hash), but the middleware takes care of low-level validations, so for our
current purposes we can ignore those.
The bearer middleware validates that token and uses it to create a
ClaimsPrincipal. However, you might be surprised by how different
the two look. To see that, go to the ValuesController class on the API,
find the parameterless overload of Get, and add the following line to it:
Click here to view code image
ClaimsPrincipal cp = ClaimsPrincipal.Current;
Now place a breakpoint on that line and run the solution again. Once the
execution reaches the breakpoint, take a look at the ClaimsPrincipal’s
Claims list as shown in Figure 9-6.
Figure 9-6 The list of claims extracted by the middleware from the
incoming access token.
The claim types used in the ClaimsPrincipal are much longer than
the ones found in the actual token. In Chapter 1 you learned that this
normalization is performed for the purpose of helping you write code that
queries claims without worrying about which protocol or token format was
used.
Checking the scopes in the API means ensuring that for each of the
methods offered, the caller possesses the necessary scopes representing the
permissions for accessing the feature. Here’s a brute- force example:
Click here to view code image
public IEnumerable<string> Get()
{
string [] scopes = ClaimsPrincipal.Current.FindFirst(
"http://schemas.microsoft.com/identity/claims/scope").Val
ue.Split(' ');
if (scopes.Contains("user_impersonation"))
{
return new string[] { "value1", "value2" };
}
else
{
throw new HttpResponseException(new HttpResponseMessage {
StatusCode = HttpStatusCode.Unauthorized,
ReasonPhrase = "The Scope claim does not contain
'user_impersonation' or scope claim not found"
});
}
}
// ...
// POST api/values
public void Post([FromBody]string value)
{
string[] scopes = ClaimsPrincipal.Current.FindFirst(
"http://schemas.microsoft.com/identity/claims/scope").Val
ue.Split(' ');
if (scopes.Contains("user_impersonation")&&
scopes.Contains("SimpleWebAPI.Write"))
{
// do stuff
}
else
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.Unauthorized,
ReasonPhrase = "The Scope claim does not contain
'user_impersonation' and 'SimpleWebAPI.Write' or scope claim not
found"
});
}
}
In this case, we impose a rule that every call to any of our actions be
performed by applications that have full faculty for impersonating the user,
as confirmed by the presence of the user_impersonate scope. For
example, this means that calls coming from a web app that acquired tokens
through a client-credentials flow would not be able to invoke any actions.
Moreover, we impose a rule that all actions that can alter the API’s state
(here represented by the Post method) can be performed only by a caller
presenting the scope SimpleWebAPI.Write as well. If you want to test
this, create a new client app and ask only for the user_impersonate
permission. You’ll see that users going through the new client app will not
be able to perform Post calls.
Repeating the scope-verification logic in each and every method might not
be the most efficient way of handling the problem. You might consider
adding that logic in an AuthorizeAttribute.
Customizations As I mentioned, the presence of
TokenValidationParameters in the bearer middleware means that
you can apply to a web API all the tricks it enables for web apps. That
includes everything described in the “TokenValidationParameters” section in
Chapter 7 and the features mentioned through Chapter 8 (such as the use of
RoleClaimType).
One thing that the bearer middleware lacks is the rich notifications
delegates pipeline you encountered studying the OpenID Connect
middleware. That’s mostly because the idea wasn’t fully fleshed out when
the bearer token middleware came out—and, in fact, in ASP.NET 5 the new
bearer middleware sports notifications as well. However, not everything is
lost in Katana 3: there is a mechanism that can be used for injecting your
code in the validation flow, and that’s by specifying a Provider. A Provider is
an artifact used in the bearer middleware to supply a rudimentary
counterpart for notifications. I am reluctant to go too deeply into the details,
given that this has already changed in ASP.NET 5, so I hope you’ll forgive
me if I for once do a bit of cargo cult programming. Suffice to say that
specifying a Provider as shown in the following code gives you an
opportunity to use OnValidateIdentity to make any last-minute
changes you want to make to the identity about to be passed to the
application. For example, here I am using it to add a custom claim—an
analogy with what I did in Chapter 7 in the SecurityTokenValidated
notification of the OpenID Connect middleware:
Click here to view code image
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant =
ConfigurationManager.AppSettings["ida:Tenant"],
},
Provider = new OAuthBearerAuthenticationProvider()
{
context =>
{
context.Ticket.Identity.AddClaim(
new Claim("http://myclaimtypes/hairlength",
"pretty awesome"));
}
}
});
Exposing both a web UX and a web API from the same Visual
Studio project
Imagine that you have a web application that is meant to be consumed both
through a web browser and by active clients such as mobile apps or the
code-behind of other web apps.
You can see how this presents an interesting challenge. From the pure
REST perspective, all resources are kind of the same, regardless of whether
the representation sent back from a given route is meant to be rendered by a
browser or parsed programmatically. If you consider the identity angle,
however, there are important differences. Consuming a resource through a
web browser entails the usual dance of 302s, token requests and responses
performed by jerking the browser around, and session cookies. Conversely,
consuming a web API entails sending an access token every time, obtaining
that token out of band, and dealing with 401s and 403s when the API isn’t
satisfied with the token it receives. Say that an unauthenticated user requests
a given route. Should you trigger a 302 with a sign-in request? That
wouldn’t make sense for a programmatic client. Should you send back a
401? That would cut the navigation experience short.
Very well. Maybe having individual routes that work for both
consumption models is problematic; however, you should at least be able to
partition your app into routes meant to serve back UX and routes meant to
expose an API. This is the moment for which all the deep study of the
Katana pipeline you did in Chapter 7 pays off! Consider the following
middleware initialization pipeline:
Click here to view code image
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthentication
Defaults.AuthenticationType);
app.UseCookieAuthentication(new
CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "c3d5b1ad-ae77-49ac-8a86-dd39a2f91081",
Authority =
"https://login.microsoftonline.com/DeveloperTenant.onmicrosoft.co
m"
}
);
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new
WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant =
ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new
TokenValidationParameters {
ValidAudience =
ConfigurationManager.AppSettings["ida:Audience"]
},
AuthenticationType = "OAuth2Bearer",
});
This pipeline includes the cookie, OpenID Connect, and OAuth2 bearer
middlewares. The first two are initialized as usual, and coupled together by
the same AuthenticationType. The highlighted line shows that you
change the AuthenticationType of the bearer middleware to a unique
value, OAuth2Bearer. You can use that value from resources to elect to
work with this specific middleware. For a web API, this is done through the
HostAuthenticationFilter attribute.
HostAuthenticationFilter lives in the
Microsoft.AspNet.WebApi.Owin NuGet package. You can use it for
decorating the actions or controllers you want to use as an API. Passing to it
a specific AuthenticationType will cause the user’s principal to be set
from the corresponding middleware. Assuming that your app has a
ValueController like the web API we’ve been playing with so far,
you’ll want to decorate it as follows:
Click here to view code image
[HostAuthentication("OAuth2Bearer")]
[Authorize]
public class ValuesController : ApiController
{
//...
All the other routes with [Authorize] alone will keep working with a
browser as usual.
A web API calling another API: Flowing the identity of the
caller and using “on behalf of”
It is exceedingly common for an API to have to call another API as part of
implementing its functions.
The client credentials grant is an option, although it has the shortcoming
of creating a trusted subsystem: the client API accesses the resource API
always with the same rights regardless of the user accessing the client API—
so enforcing what that user can or cannot do is left to the client API instead
of relying on the directory. Another limitation of the client credentials
approach is that granting consent for application permissions always requires
administrator consent, which might not be ideal if you want to maximize the
reach of your solution.
The section “More API consumption scenarios” in Chapter 2 introduced
another approach to address this scenario, the on-behalf-of flow defined by
the OAuth2 Token Exchange extensions (which you can find at
https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-02). If you want to
refresh your knowledge of the approach, turn back to Chapter 2 and refer to
Figure 2-9.
Let’s take a look at the code required to make the on-behalf-of flow work.
Ultimately, the client API needs to send to the authority the access token it
receives from its caller, along with the client API’s credentials and the
resourceId of the API it wants to access. The authority is expected to
examine the request and issue a new access token scoped for the new API,
which the client API can use for gaining access. The operation is
summarized in the diagram in Figure 9-7.
Figure 9-7 Swim-lane diagram of the token request call in the on-behalf-
of flow, as detailed in the OAuth2 Token Exchange extensions
specification.
Let’s modify our sample API to perform a call to the Graph on behalf of
the caller. The first step is to retrieve the bits of the access token that our
client sent to our API so that our API can use the original access token to
request a new access token for accessing the next layer. We already know
that the content of the incoming access token is deserialized in
ClaimsPrincipal.Current, but that’s not good enough: we need the
original, unmodified token bits so that the authority can examine them (and
possibly recheck signatures and so on). Since .NET 4.5, the .NET
Framework features a mechanism for preserving the bits of tokens used for
gaining access to the current app: it is simply an option in the authentication
pipeline. In the Katana middleware, that option is driven by the
SaveSigninToken property of TokenValidationParameters.
You activate it by setting it to true, as shown here:
Click here to view code image
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant =
ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience =
ConfigurationManager.AppSettings["ida:Audience"],
SaveSigninToken = true,
},
// ...more stuff
resource=https%3A%2F%2Fgraph.windows.net&
client_id=fdb34bf3-74e6-4da7-bf97-de4cb664e261&
client_secret=z5qE%2Bs2gnDkA8R8TGzisjh4EfSP1ZPjCw9EU7ZVtp7Y%3D&
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&
assertion=eyJ0eXA[...SNIP...]YBWg&
requested_token_use=on_behalf_of&
scope=openid
The parameters that are sent are pretty much what you’d expect:
client_id, client_secret, the assertion itself, the grant_type,
and requested_token_use=on_behalf_of as dictated by the
OAuth2 Token Exchange spec. The scope parameter is perhaps the only
surprise. It is included so that the token endpoint will also send back an
id_token—an id_token containing user information that ADAL uses for
creating the correct cache entry for the resulting tokens.
And now look at what you find in the response:
Click here to view code image
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
x-ms-request-id: bbfbc87c-4dd0-43e6-a32f-d7c5f9c5e0e1
client-request-id: a97cd78d-f147-45d0-b64b-59ea4a99b916
x-ms-gateway-service-instanceid: ESTSFE_IN_228
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
Set-Cookie: flight-uxoptin=true; path=/; secure; HttpOnly
Set-Cookie: x-ms-gateway-slice=productiona; path=/; secure;
HttpOnly
Set-Cookie: stsservicecookie=ests; path=/; secure; HttpOnly
X-Powered-By: ASP.NET
Date: Mon, 19 Oct 2015 02:43:57 GMT
Content-Length: 3017
{"token_type":"Bearer","expires_in":"3898","scope":"Directory.Rea
d
User.Read","expires_on":"1445226536","not_before":"1445222337","r
esource":"https://graph.windows.net","pwd_exp":"101826","pwd_url"
:"https://portal.microsoftonline.com/ChangePassword.aspx","access
_token":"eyJ0eXA[...SNIP...]IvNq4w","refresh_token":"AAABA[...SNI
P...]wtyAA","id_token":"eyJ0[...SNIP...] CJ9."}
As predicted, you get back a new token triplet. But how do you know that
the token you get back is actually for the Graph? Well, using it successfully
would be a pretty good indication, but just to be on the safe side, let’s decode
the access token and verify that it’s what we expected:
Click here to view code image
{
"acr" : "1",
"amr" : [ "pwd" ],
"appid" : "fdb34bf3-74e6-4da7-bf97-de4cb664e261",
"appidacr" : "1",
"aud" : "https://graph.windows.net",
"exp" : 1445226536,
"family_name" : "Rossi",
"given_name" : "Mario",
"iat" : 1445222337,
"ipaddr" : "73.169.211.13",
"iss" : "https://sts.windows.net/6c3d51dd-f0e5-4959-b4ea-
a80c4e36fe5e/",
"name" : "Mario Rossi",
"nbf" : 1445222337,
"oid" : "13d3104a-6891-45d2-a4be-82581a8e465b",
"puid" : "10037FFE894016DA",
"scp" : "Directory.Read User.Read",
"sub" : "VD3MBzqKX_DFcJjwq5K9xa1ODW5AXYNgnci589pLLb8",
"tid" : "6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e",
"unique_name" : "mario@developertenant.onmicrosoft.com",
"upn" : "mario@developertenant.onmicrosoft.com",
"ver" : "1.0"
}
The token does carry the information for the original user, the one who
invoked the web app that invoked our web API, which in turn is about to
invoke the Graph as the same user. The appid corresponds to the client_id
of the client API, so we are good there. To be extra certain about
distinguishing the various tokens, I changed the resources and permissions
requested by the client API to include Directory.Read, an extra
permission with respect to the other project in the solution. Sure enough, the
extra entry appears in the scope.
The on-behalf-of flow is an extremely powerful one. This is a simple
sample, probably the simplest you can encounter. I encourage you to
experiment with different combinations of APIs, permissions, client types,
and consent models to explore the boundaries of what can be accomplished
with it.
In plain English, this command creates a new relying party trust, which is
to say it tells ADFS about one new app to issue tokens and claims for. As
part of that, it also establishes that tokens for that app should contain one
single claim,
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.
ADFS “3” OAuth2 support is limited to public clients—native and mobile
apps that do not have their own credentials. As such, I cannot adapt the web
app sample we’ve been using in this chapter to give the web app a test run—
the web app is a confidential client and must use its credentials when going
through the code grant flow, but ADFS “3” won’t accept it. The good news
is that ADFS in Windows Server 2016 will work with confidential clients,
too.
Summary
The ability to invoke an API is a feature of paramount importance for
modern web applications. For the same device, making your resources
available to all sorts of clients via the REST API, while maintaining robust
and flexible authentication capabilities, is table stakes in today’s mobile-
first, cloud-first market.
This chapter walked you through some of the most fundamental
topologies involving web APIs and Azure AD. To understand how things
unfold, you had to recall many of the topics you’ve studied throughout the
book: how protocols work; how OWIN middleware extensibility operates;
and how Azure AD represents applications, grants and denies permissions,
and exposes its own capabilities in the form of the Graph API. I hope you’ve
started to reap a good return for your hard work so far!
Chapter 10. Active Directory Federation Services
in Windows Server 2016 Technical Preview 3
On the welcome page, shown in Figure 10-3, enter one application name
that you’ll remember. I am calling my app "Simple ADFS web app." Once
you’ve done that, click Next.
Figure 10-3 The first screen of the web application creation wizard
gathers essential protocol coordinates of the application.
The Server Application screen shown in Figure 10-3 gathers all the
information about your app that’s required to perform the sign-in protocol
dance. The Client Identifier field holds the client_id, discussed earlier. If you
were following the instructions, here you have to paste the value you used in
the middleware initialization code.
The Redirect URI field holds the list of the URLs to which ADFS is
allowed to return tokens. A typical rookie mistake here is to paste the address
in the text box and not click Add; your job isn’t finished until you have at
least one URL in this list. Once you are done, click Next. You’ll see the
Configure Application Credentials screen, shown in Figure 10-4.
Figure 10-4 The application credentials screen offers you various options
to provision credentials for your web app.
The Configure Application Credentials screen is one of the areas where
the difference between Azure AD and ADFS is the most significant. You can
see this in Figure 10-4.
You don’t need to assign an application credential for supporting web
sign-on, but you will need it later on for calling the web API. But because
we are already here, we can just as well explore the options now.
At the bottom of the page you can see the section dedicated to the
generation of a shared secret. This is the near-perfect analogy to the
corresponding functionality in the Azure AD portal. The approach you are
expected to follow for dealing with secrets is the same, too: you are able to
see the secret bits only at creation time, and then they’re invisible forever. If
you don’t save the secret or if you lose it, your only recourse is to create a
new secret.
In the middle of the page you are offered the option to define a type of
application credential that is unique to ADFS, Windows Integrated
Authentication. If your app’s process is running as a given account—as
might be the case for a Windows service, for example—you can use that fact
to fulfill the credential obligations of OAuth2 and OpenID Connect flows for
confidential clients. Very neat.
Finally, the top option allows you to specify a key to be used for signing
an assertion, the counterpart of the certificate credentials in Azure AD.
Although in Azure AD using this credential requires you to use PowerShell
cmdlets and X.509 certificates, ADFS offers a full range of options that you
can reach by clicking the Configure button. The dialog that pops up offers
you a selection of certificate files from the file system or the option to
periodically download key information from a JWKS (JSON Web Keys set)
feed where you can publish and roll keys. This uses the same technology you
studied in the discovery sections of Chapter 6, "OpenID Connect and Azure
AD web sign-on," but this time it is the app (as opposed to the authority) that
advertises the keys it will use to sign tokens to prove its application identity.
The same dialog also offers fine-grained control on how to perform
revocation checks on certificates.
That’s all very nice and sophisticated, but for this sample we’ll stick with
what’s simple. The shared secret is the credential type we’ll use in the next
section: select the corresponding check box, as shown in Figure 10-4, save
the secret string somewhere, and click Next.
We’re done with the wizard. Keep clicking Next until you reach the last
screen.
If you are part of the old ADFS guard, you might be wondering about
claim issuance rules at this point. How does ADFS know which claims
should be sent to the application? The answer is that the token sent in the
OpenID connect web sign-on flow is the id_token, which has a fixed
structure. Let’s give the app a spin and see what that looks like.
Note
If you take a look at the traffic captured in Fiddler, you’ll see the usual
dance: middleware reaching out to the discovery document, retrieval of the
keys, authorization request to the authorization endpoint, automatic POST to
the app with the id_token and code, a 302 for setting the session
cookies, and finally a 200. Business as usual.
If you peek at the id_token, however, you’ll find it significantly
skinnier than the one issued by Azure AD:
Click here to view code image
{
"aud" : "98ff52e2-6deb-4029-99e4-6c15486d9c56",
"auth_time" : 1445677071,
"c_hash" : "5h5QGlrTWmSUxVH09sf5AQ",
"exp" : 1445680671,
"iat" : 1445677071,
"iss" : "https://WS2016TP3.vibrodomain.net/adfs",
"nonce" :
"635812738685261506.MDczY2RkYzAtMzEyNy00YjRiLWJkZDUtZTdjNTMxNjZlZ
jkzNm Y4ZDc3OTctN2
Q0MC00YzYxLWJlOGYtMzdhZGUwMmRlZjk2",
"sub" : "/P6RGnF6Q9FbVfyjFY6whvkQIbzQR4z2WurnPHfUSME=",
"unique_name" : "VIBRODOMAIN\\mario",
"upn" : "mario@vibrodomain.net"
}
That is pretty bare-bones, but it’s all it takes to sign on with the web
application. Very easy!
Figure 10-6 The access control policy regulates which users can request a
token for the API.
All the access policies offered out of the box are quite self-explanatory.
Given that most options there would require more setup work, I am just
going to go with Permit Everyone. That applies only to the token-issuance
operation, of course. The API is still responsible for inspecting incoming
tokens for validation and authorization purposes.
After you select Permit Everyone, click Next. You’ll reach the application
permissions section, shown in Figure 10-7.
Figure 10-7 The application permissions screen lists which clients are
allowed to request tokens for the API and the possible scope values the
clients can request.
This screen summarizes which client apps are allowed to request this
ADFS instance for the web API we are provisioning and what scopes clients
are allowed to request.
I will defer the discussion about callers to the end of the chapter. Here it
should be enough to say that given we are creating this API in the same
application group, the sample web API is automatically listed as an allowed
client.
The Permitted Scopes section lists all the scopes that a client is allowed to
request. Note that a client is free to request a subset of these. Here I am
picking up both openid and user_impersonation.
If your web API has its own scopes, it is easy to add them as custom ones.
Click New Scope. You will be prompted to add a scope through the dialog
shown in Figure 10-8.
if (response.IsSuccessStatusCode)
{
callOutcome =
response.Content.ReadAsStringAsync().Result;
}
return Task.FromResult(0);
}
If you don’t want to code this manually, it is also worth stressing that you
can ready up a web API project that’s hooked up to ADFS in less than 30
seconds if you use the ASP.NET project templates in either Visual Studio
2013 or 2015. Just create a new web API project, click Change
Authentication, choose Organizational Accounts (if using Visual Studio
2013) or Work And School Accounts (in Visual Studio 2015), select On-
Premises, paste in the ADFS metadata address and the desired audience
value, and you’re done. Figure 10-9 shows you the Visual Studio 2013
dialog box; the one in Visual Studio 2015 looks nearly the same.
Figure 10-9 The dialog you use to set authentication preferences for
ASP.NET projects in Visual Studio 2013. Visual Studio 2015 projects
offer a similar dialog box.
Don’t forget that ADFS does not offer any API for automating the app
provisioning from Visual Studio; the template can only emit the right
configuration code and add the right NuGet references for you, but you still
need to have access to the ADFS management UX and provision the web
API manually before being able to call it.
We’re finally ready to test our scenario end to end.
Once again, the token issued by ADFS is quite bare-bones, but it contains
everything required for authenticating the call. Issuer, audience, scopes . . .
it’s all there. In fact, the OAuth2 token bearer middleware should be happy
with it and allow the API to be invoked. You can apply to this ADFS-based
scenario the same considerations about scope validation and middleware
customization you studied in the Azure AD case, with the obvious
differences (for example, multitenancy for on-premises AD does not really
apply).
Additional settings
I hope this chapter has provided you with guidance for solving the core
modern authentication scenarios with ADFS and given you a solid
scaffolding as you decide to leverage new ADFS features. ADFS in
Windows Server 2016 Technical Preview 3 is chockful of new features, but
many of them are still very fluid, and I don’t want to mislead you about their
stability by describing them in detail here. Instead, I’ll just mention a couple
of additional features that are especially important for developers—adding
arbitrary claims to access tokens and exercising finer control over which
clients can call the API.
The screens in the application creation wizard tend to ask for little more
than the essential settings ADFS needs to create a functional entry for the
application. The management UX you can access after creation gives you
some more interesting options that are worth exploring.
Open the application group of your solution, and double-click the entry
for the web API. You’ll see a classic multitabbed properties dialog (visible in
the background in Figure 10-11). Go to the Issuance Transform Rules tab.
This screen allows you to specify more claims for ADFS to add to access
tokens issued for this API. Click Add Rule, and you’ll be presented with the
dialog in Figure 10-10.
Figure 10-10 The Add Transform Claim Rule Wizard allows you to
define more claims to be included in the token for the web API.
Figure 10-11 The web API property dialog allows you to extend the list
of clients that are allowed to request a token for your web API.
If you have used ADFS before, you are probably familiar with these
settings. You can choose from where to source the claims values you want to
issue: those typically range from Active Directory itself to custom stores you
hook up to ADFS. Once you have done that, you have a simple tabular
interface where every row determines the attribute you want to retrieve and
what claim types you want to use for representing that value in the token. In
Figure 10-10 you can see that I chose a few user attributes, just to show
something new in the token. Be sure that you give a name to your rule, and
then click Finish. You’ll see the new rule listed on the Issuance Transform
Rules tab.
Claims or attributes?
After about 300 pages of subtleties and fine points, allow me to
bother you with (seemingly) philosophical matters one last time.
People often confuse the concept of attribute with the concept
of claim. The two things are very tightly related, but they are
not the same. Whereas an attribute is a free-floating piece of
information, a claim is information stated by a verifiable source
(as in it travels in a token signed by the source). This is the
same difference that applies to your name written on a random
Post-it note and your name printed on your passport. The string
is the same, but the uses you can make of it change dramatically
—the latter carries all the strength that its source’s credibility
can lend. For many conversations, the two terms might be used
interchangeably without immediate bad consequences, but
occasionally the difference will be relevant, and
misunderstandings are often hard to troubleshoot. I always try
to use the correct term.
I know you are itching to try the flow that will issue new claims in the
token, but given that we have the app properties dialog open, I want to show
you one last thing. Go to the Client Permissions tab: you’ll see a screen
similar to the one shown in Figure 10-11.
That property page allows you to edit the list of scopes that your sample
web client can request for this API, as we have seen at creation time.
More interestingly, this page allows you to manage the list of known
clients that can access the web API. By default, only client apps within the
same application group can access the API. If you click Add, you will be
presented with a list of potential new clients. That list includes clients you
created in this instance under different application groups and some built-in
clients. I won’t describe the built-in clients here, as they mostly come with
heavy infrastructural considerations; please refer to the ADFS online
documentation for that. The main built-in setting I want to be sure you are
aware of is All Clients, which allows you to drop the restriction for specific
clients and opens up ADFS to issue tokens for this API to any registered
requestor. That’s analogous to how ADFS “3” operated.
Let’s not change the client list right now. Click Cancel to get back to the
main properties dialog. Here, click OK.
Make sure that Fiddler is still running, go back to Visual Studio, and hit
F5 again.
Looking at the web API call and decoding the token, you should see
something like the following:
Click here to view code image
{
"_sso_data" : "D3QoxP[..SNIP..]W_o6VBCA",
"appid" : "98ff52e2-6deb-4029-99e4-6c15486d9c56",
"apptype" : "Confidential",
"aud" : "https://myservices/myAPI",
"auth_time" : "2015-10-24T23:29:50.181Z",
"authmethod" :
"urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTranspor
t",
"exp" : 1445737813,
"family_name" : "Rossi",
"given_name" : "Mario",
"iat" : 1445734213,
"iss" :
"http://WS2016TP3.vibrodomain.net/adfs/services/trust",
"scp" : "MyService.Write user_impersonation openid",
"upn" : "mario@vibrodomain.net",
"ver" : "1.0"
}
As shown in the highlighted lines, ADFS applies our rule and has injected
the claims we wanted in the access token, ready for the web API to consume.
Summary
This chapter gave you a quick introduction to leveraging ADFS directly for
implementing modern authentication with your web apps and web APIs.
Although there are some differences with respect to the code you write when
you work with Azure AD, those are largely syntactic sugar. What you have
learned through the book applies nearly verbatim to ADFS, which is what
made it possible to cover so much functionality in a relatively short chapter.
Please remember that the version of ADFS discussed here is a preview,
and it is very likely that some of the instructions provided here will no
longer apply. If you try something and it doesn’t work as expected, before
you add breakpoints and traces take a quick look at
http://www.cloudidentity.com/blog/books/book-updates/ to see if there is a
known change.
Appendix: Further reading
The chapters in this book went deep into one specific scenario, modern
authentication for web applications and web APIs. All the code samples
were presented in C# and developed in Visual Studio (although apart from
the Visual Studio wizards, you could have used any other IDE). Many
scenarios and technologies are just as important, but they didn’t make it into
the book for various reasons—sometimes because they are still too early in
the development cycle, but more often because of a lack of time. This
appendix is meant to ensure that you are aware of these important topics
and give you pointers if you want to know more.
If you follow just one link, be sure it’s http://aka.ms/aaddev, which is the
entry point for the online developer guide for Azure AD and offers the most
comprehensive set of links you can find on identity and development for
Active Directory. For keeping up with changes affecting what the book
covers, please refer to http://www.cloudidentity.com/blog/books/book-
updates/.
Other platforms The Azure AD developer experience team produces
development libraries for an ever-growing list of popular platforms.
You have explored .NET in depth in this book, but there are
counterparts in the pipeline for popular server stacks such as Node.JS,
Java, Ruby, Python, and more. All the libraries are open source and
can be found at https://github.com/azuread/. Feel free to explore the
libraries themselves and the test cases. The site I mentioned earlier,
http://aka.ms/aaddev, has various quick starts that can get you up and
running. Finally, https://github.com/azure-samples?query=active-
directory has a very comprehensive list of samples. The convention is
that the sample repo name includes the platform being demonstrated
—for example, active-directory-node-webapp-openidconnect
indicates a sample showing how to do web sign-on via OpenID
Connect with Node.JS.
Single-page applications Single-page applications, or SPAs, are a
very popular application development style I touched on in Chapter 2,
“Identity protocols and application types.” Azure AD offers
comprehensive support for this style of development, from the
protocol features necessary to implement the token flow to a handy
JavaScript library (ADAL JS; source at
https://github.com/AzureAD/azure-activedirectory-library-for-js) and
accompanying samples (https://github.com/azure-samples?
utf8=%E2%9C%93&query=singlepage). I originally considered
adding a chapter on SPAs, but as I started writing, it became clear that
the chapter would have been too much of a detour from the main flow
of the book. You can find more information on this scenario on my
blog (http://www.cloudidentity.com/blog/tag/adaljs/) and, as usual, in
the guide at http://aka.ms/aaddev. Finally, on the web you can find
many samples and labs published from Office, as SPAs are a very
popular way of consuming the Office API.
Native clients Modern authentication for native clients, as mentioned
in Chapter 2, is a topic that deserves an entire book (or two) of its
own. The Azure AD team supports lots of platforms through dedicated
libraries: you can find .NET, iOS, Android, Xamarin, Cordova,
Windows Store, Universal Windows Platform, and others at
https://github.com/AzureAD. There are lots of samples at
https://github.com/azure-samples and comprehensive guidance at
http://aka.ms/aaddev.
Business to consumer (B2C) The flavor of Azure AD I discussed in
this book is meant to address classic business-organization scenarios,
such as internal app portfolios, cross-organization collaboration, or
software developer vendors targeting the business world.
As I write, Azure AD has announced an entirely new offer, dubbed
B2C, or business to consumer, which is meant to help businesses
handle authentication for their customer-facing applications and
assets. The new offer makes use of the same infrastructure as classic
Azure AD and is based on the same protocols (OpenID Connect,
OAuth2); however, it tweaks the offer to support the features that
B2C scenarios require. Simple sign-up, fully customizable
authentication experiences, social identity provider integration, and
profile management are examples of new features B2C offers. At this
point, B2C is still in preview, but you can experiment with it by
developing web apps with the same middleware you learned about in
this book. Head to http://aka.ms/b2c for more details.
Azure AD vNext and convergence with Microsoft accounts The
Azure AD team is hard at work to deliver a new version of Azure AD,
which will introduce some key features currently missing. In the new
system, the team is aiming at allowing you to get tokens from Azure
AD or from Microsoft accounts by using the same protocol and
developer libraries. Furthermore, you will no longer be strictly bound
by the static permissions and consent rules described in Chapter 9—
you will be able to ask for scopes on the fly. This is going to open up
scenarios that are impossible or really hard to achieve today, and
members of the identity team are all very excited about it. The new
endpoints and libraries are in preview: you can read about them at
http://aka.ms/aadconvergence.
Index
A
About() action, 238–239, 241, 249–250
access control
for applications, 216–219
enforcing, 82
groups, 219–221
risk levels, 59
for web APIs, 283
Access Control Service (ACS), 78–79
access delegation, 31–33
AccessToken property, 229
access tokens, 35, 256. See also tokens
claims, 263, 289–290, 292
invoking web API with, 232–251
JWT format, 271
life span, 238–239
opacity to token requestors, 72, 233
refresh tokens, 238–251. See also refresh tokens
renewing, 70–71
requests, 268–269
responses, 269–270
scope, 116. See also scopes
AccountController sign-in and sign-out logic, 104
AcquireTokenByAuthorizationCode method, 229, 244–245, 287–288
AcquireToken method, 228, 269
AcquireToken* methods, 238–239, 241, 247
AcquireTokenSilent method, 239, 241, 243–244
failed calls, 246
acr claims, 133
Active Directory (AD)
access token representation, 255
introduction, 15
as new directory in the cloud, 58
on-premises, 15–16
on-premises vs. cloud approach, 58–59
projection in the cloud, 58
setup in Windows Server 2016, 273–274
token requests, 70
Visual Studio integration, 85–87
Active Directory Authentication Library (ADAL), 76–78
accessing APIs as application, 251–252
accessing APIs as arbitrary user, 252
cache, 243–247
handling AuthorizationCodeReceived notification, 227–230
JavaScript versions, 80
midtier client libraries, 81
native apps libraries, 48, 80–81
.NET NuGet package, referencing, 227–228
refresh tokens, 238–251
session management, 238–251
token-acquisition pattern, 77
token caches, 238–239
Active Directory Federation Services (ADFS), 9, 25, 52–56
access control policies for web APIs, 283
access token representation, 255
access tokens for web APIs, 285–288
API and UX entries, 282
application groups, 274–275
application permissions for web APIs, 284
app provisioning, 287
Client-Server Applications section of management UX, 275
credentials gathering, 280–281
endpoints, 276–277
federated tenants, 65–66
JWT format for access tokens, 271
management UX, 274–276
multiresource refresh tokens, 286
Native Application and Web API template, 275
OAuth2 authorization code grants, 55
OpenID Connect support, 103
protocol support, 55–56
Server Application and Web API template, 275
setting up, 54, 273–274
signing keys, 280
tokens issued by, 289
web API identifiers, 282
web API invocation, 288–289
web API setup, 281–285
web app setup, 277–280
web sign-on with OpenID Connect, 276–281
Windows Integrated Authentication credential, 279
in Windows Server 2016, 56, 103, 273–292
workplace-joined device detection, 56
Active Directory Federation Services (ADFS) “3”
application provisioning, 271
client entity, 274
OAuth2 support, 272
web APIs, protecting, 271–272
ActiveDirectoryFederationServicesBearerAuthentication method, 271
ADAL4J, 81
ADAL Android, 81
ADAL Cordova, 81
ADAL iOS, 80
ADAL JS, 72, 85, 294
ADAL .NET, 78–80
Add Transform Claim Rule Wizard, 290
admin consent, 173, 200–204. See also consent
dialog box for, 202
requests, 210
administrators
ADFS management, 53–54, 57
application creation, 204
Azure portal, 64, 66
claims issued, managing, 9, 57
consent prompts, 199
control over trust establishment, 57
directory resource access, 173
directory sync, 65
global, 93
guest, 93
permissions, 59, 198–200
AJAX calls, 235
allowedMemberTypes collection, 214, 216–217
amr claims, 133
AngularJS, 47
anonymous access, 97
APIs. See web APIs
<api version> component in URL template, 237
AppBuilder type, 141–142
appId property, 180, 188
App ID Uri, 209, 256
Application.appRoles object, 216–219
application groups, 274–275, 281–282
application identifiers, 181
application-level authentication messages, 25
application model, Azure AD. See Azure Active Directory application
model
Application object, 175, 177–186
authentication properties, 180–183
deletion timestamp, 180
JSON file, 178–180
object ID, 180
properties by functional group, 186
for web APIs, 257–258
Application Proxy, 67
applications
access directory as user permission, 196
accessing resources as, 45
accessing web APIs, 252
access through Azure Active Directory, 66
actions, 177
adding to application groups, 281–282
ADFS code, libraries, protocol support, 53, 55
admin consent, 173, 200–204, 210
admin creation, 204
admin-level permissions, 198–200
app-level permissions, 216–219
assigned users, 188
authenticate users permission, 195–196
authentication options, 177
availability to other tenants, 182–183
client role, 70–72. See also clients
credentials, 226–227, 279
decoupling from web servers, 138
delegated permissions, 192–197
directory read and write permissions, 196
display name, 181
enumerate users permissions, 196–197
group read and write permissions, 197
homepage, 181
identifiers, 177, 188
identifying authentication protocols of, 64
IdP metadata, reading, 21
IdP trust, 18
initialization, 140–141
isolated and independent, 14
iss (issuer) value, 120–121, 208
key string assignments, 226–227
multitenancy, 205–211
nonadmin user creation, 189–192
OAuth2 permissions, 183–185
partitioning for consumption routes, 265–266
protecting with Azure AD, 60–61
protocol coordinates, 61, 177, 276, 278
provisioning, 53–54, 57, 189
public vs. confidential clients, 181
relying parties, 18. See also relying parties (RPs)
resource protectors, 69, 73–74
resources, 177, 185–187
roles, 182, 213–216
scopes, 201. See also scopes
single-page, 45–47
as token requestors, 69–72, 74
token validation, 22. See also token validation
user assignment, 211–213
app manifest files, 214, 219
appOwnerTenantId property, 188
app parameter, 140–141
app permissions, 216–219
AppRoleAssigment entity, 212
appRoleAssignedTo property, 215
appRoleAssignmentRequired property, 188, 212
AppRole entity, 216–217
appRoles property, 182, 188
ASP.NET
Katana. See Katana
membership providers, 14
project templates for web APIs, 287
support for web sign-on, 137. See also Open Web Interface for .NET
(OWIN) middlewares
templates in Visual Studio 2015, 87
ASP.NET 4.6
vs. ASP.NET 5, 90
initialization code, 95
web API projects, 254. See also web APIs
ASP.NET 5, 85, 90
ASP.NET applications, 3–7. See also web applications
building, 89
claims-based identity support, 82
MVC project type, 90–91
OWIN components, 83–84
assembly:OwinStartup attribute, 139
assertions, 26
attributes, 20, 59, 290
audience claims, 132, 282
authentication
Application object properties, 180–183
Azure AD for, 1
claims-based identity, 17–23
default process, 4–7
defined, 12
failure notification, 166
indicating success, 98
mechanisms for, 7
mode, 152, 158–159
modern, 31–48
multitenant systems, 58
native apps vs. web apps, 94
pre-claims techniques, 12–16
round-trip web apps, 23–31
steps of, 73
triggering, 97–98, 100
type setting, 158
AuthenticationContext class, 228
initialization, 287
AuthenticationFailed notification, 167
authentication flows
across multiple tenants, 205–208
authorization-code, 42–43
hybrid, 40–42, 108
OWIN middlewares pipeline, 148–153
state, preserving, 116–117
AuthenticationManager instance, 148
authentication middlewares, 148–153
Authentication property, 146
AuthenticationMode property, 159
Active option, 149
AuthenticationProperties settings, 100
Authentication property, 146, 150, 152–153
AuthenticationReponseGrant, 150
authentication-request message type, 39
authentication requests, 98, 113–119
authorization endpoints, 114
clientID, 114
nonce, 117
omitted parameters, 117–119
response mode and response type, 114–116
scope, 116
state, 116–117
AuthenticationResult instance, 229
AuthenticationTicket store, 171
AuthenticationType property, 159
authorities, 18
/adfs/, 287
control over user authentication experience, 122
validation in ADFS, 287
authority coordinates, validation and, 157–158
Authority property, 155
authority types. See Active Directory Federation Services (ADFS); Azure
Active Directory
authorization, 33–39, 116
OAuth2 grants, 55, 252
<Authorization>, 216
AuthorizationCodeReceived notification, 167, 227–230, 286
authorization codes
acquiring, 225
client secrets, 156
code-redemption logic, 227–230
OpenID Connect flow, 42–43
redeeming, 224–232, 286
authorization endpoint, 35, 63, 114, 207, 285
Authorization HTTP headers, tokens embedded in, 232–234
authorization requests, 149
authorization server (AS), 34–35
[Authorize] attribute, 97–98, 148–149
on entire class, 257
role information, 216
scope-verification logic, 264
auth_time claims, 133
availableToOtherTenants property, 182–183, 209
Azure Access Control Service (ACS), 41, 78–79
Azure Active Directory, 1, 56–67
access token representation, 255
application access, 66
application entry permissions, 224–225
application model. See Azure Active Directory application model
Application Proxy, 67
apps, adding entry for, 60
authorization endpoints, 114
B2C (business to consumer), 294–295
client-credentials grants, 251
client IDs, 94, 97
cloud workload functionality, 59
consent prompt, 5–6
cookies on user browser, 124
credential gathering, 122–123
credentials prompt, 5
default domain, 62–63
development and, 2, 60–61
Directory Graph API, 10, 59. See also Directory Graph API
directory sync, 65–66
discovery, 119–120
functional components, 59–60, 63–65
group information in tokens, 219–220
libraries, 75–86
multitenancy, 58, 205–208
oauth2PermissionGrants collection, 189–192
obtaining tenant, 61–62
online developer guide, 293
OpenID Connect endpoints, 109
permissions for directory access, 193–197
private/public key pair information, 227
programmatic access to entities, 236–237
programmatic interface, 64–65. See also Directory Graph API
projection of on-premises, 65
protocol endpoints, 63–64
protocols supported, 58
redirect URIs, 100
refresh tokens, 240–243
registering apps, 93–94
resource identifiers in token requests, 256
resource-protector library references, 92
response to POST, 123–125
response types, 124
service deployments, 63
sessions, cleaning, 135
synchronizing users and groups to, 65–66
tenantID, 63
tenants, 62, 93
tokens, 61. See also tokens
token-signing keys, 120–122
token validation, 149–151
trial, 2
tying to Visual Studio, 2–3
user information, accessing, 7–10
Visual Studio 2015 connected services, 87
web API provisioning, 253
Azure Active Directory application model, 64, 173–221
admin consent, 200–204
admin-level permissions, 198–200
admin user application creation, 204
app-level permissions, 216–219
Application object, 175, 177–186
app roles, 213–216
app user assignments, 211–213
consent, 175, 189–192
delegated permissions, 192–197
functions, 173
groups, 219–221
multitenancy, 205–211
provisioning flow, 175–176
ServicePrincipal object, 187–188
service principals, 174–177
Azure Active Directory Basic, 62, 66–67
Azure Active Directory Connect, 65
Azure Active Directory Free tier, 61–62
Azure Active Directory Premium, 62, 66–67, 215
Azure Active Directory vNext, 295
Azure management portal, 60–61, 64
application configuration section, 178
application credentials, assigning, 226
Application entity JSON file, 178
application permission selection UI, 198
application permissions screen, 217–218
application tags, 188
manifest management section, 178
multitenancy setting, 209
provisioning apps in Azure AD, 93–94
Users tab, 212
Azure subscription, 2, 93
B
back ends, HTTP requests to, 46
Balfanz, Dirk, 41
BaseNotification class, 161
bearer token middleware
diagnosing issues, 261
notifications, 264
Provider, specifying, 265
tokens from ADFS, validation, 271
bearer tokens, 232–237, 262
extraction and validation, 255
BootstrapContext property, 268
broker apps, 48
browsers
hosting prompting logic in, 48
network tracing features, 110
presentation layer, 45–46
business to consumer (B2C) Azure AD, 294–295
C
CallbackPath property, 158
caller identity class, 7–10
callers
attributes, 7
identifying, 23–24
retrieving names of, 7–8
Caption property, 159
Challenge method, 99–100
Challenge sequence in OpenID Connect middleware, 152
claims, 7, 20
from access tokens, 263
adding to access tokens, 289–290, 292
vs. attributes, 290
group information, 182, 219–220
in ID tokens, 132–134
information in, 57
JWT types, 131–132
OAuth2 and, 36–37
sourcing values, 52
type identifiers, 8–9
claims-based identity, 17–23
authentication process, 21–22
identity providers, 17–18
just-in-time identity information, 57
protocols, 20–23
tokens, 18–20
trust and claims, 20
claims-oriented protocols, communication across boundaries, 36–37
ClaimsPrincipal class, 7–10, 82
Claims list, 263
Current.FindFirst(“roles”), 215–216
Current property, 8
in OWIN, 83
saving, 151
source location, 8
ClaimsPrincipalSelector delegate, 8
claims rules engine, 52
claims transformation engine, 60
ClaimTypes enumeration, 8–9
ClientAssertionCertificate, 231
ClientCredential class, 229, 251, 287
client credentials grants, 44–45, 251, 266
client IDs, 94, 97, 114, 155, 190
of application, 256
overriding at registration, 276
refresh tokens and, 243
client-resource interactions, tokens for, 70–71
clients, 70. See also token requestors
access control policies for web APIs, 283
in ADFS “3,” 274
ADFS support, 55
application permissions for web APIs, 284
confidential, 181, 275
definitions of term, 72
entries in target directories, 183
granted permissions, 189–192
identity and resource consumption, 265–266
public, 181, 275
scopes, 284–285
as token requestors, 72
of web APIs, 291
client secrets, 156, 227
cloud applications, 57–58
cloud-based Active Directory, 58–59. See also Azure Active Directory
cloud-based authentication, 56. See also Azure Active Directory
cloud-based directories, 60
cloudidentity.com blog, 80
cloud services, 58. See also Azure Active Directory
cloud stores, 59
code reuse, 71
common endpoint, 121, 207
confidential clients, 181, 275
ConfigurationManager class, 157–158
ConfigureAuth, 141
consent, 189–192
across tenants, 209–211
admin, 173, 200–204, 210
AppRoleAssigment entries, 212
provisioning flow, 175–176
for resource access, 186
revoking, 259
settings, 211
for web APIs, 258
consent prompts, 44, 191
for admin users, 199
for multitenant pages, 210
constrained delegation, 43
context
AuthenticationManager instance, 148
Authentication property, 146, 150, 152–153
environment dictionary, 147
middlewares, 142, 145–148
Request and Response properties, 147–149
TraceOutput property, 148
contracts, 23
controllers, MVC 5 Controller, 100
cookie-based sessions, 92
cookie middleware
adding to pipeline, 96
adding to web apps, 92
ClaimsPrincipal, saving, 151
collaboration with protocol middleware, 148
response processing, 150
sessions, managing, 171
sessions, saving, 150
sign-out, 100–101
cookies
domain-bound, 24–25
life cycles, 24, 46
limitations, 46
nonce value, tracking, 124–125
session. See session cookies
on user browser, 124
for web API protection, 235–236
Cordova ADAL library, 81
credentials
application, 226–227
assigning, 226
gathering, 122–123
grants, 44–45, 251, 266
keys, 181. See also keys
passwords, 181
in ServicePrincipal, 188
sharing among apps, 32–33
storage, 226
types, 13
credentials validation and session cookie authentication pattern, 23–24
cross-collaboration scenarios, 17
cross-domain single sign-on, problems, 23–25
Current property, 8
D
decoupling web servers from apps, 138. See also middlewares; Open Web
Interface for .NET (OWIN)
default authentication process, 4–7
delegated access, 34–36
delegated permissions, 185, 192–197
scopes, 201
deletionTimestamp property, 180, 188
Devasahayam, Samuel, 15
developer-assigned application identifiers, 181
development certificates, 91
development libraries
in Active Directory, 75. See also libraries
for native clients, 294
for other platforms, 293
development on dedicated machines, 91
diagnostic middleware, 153–154
digital signatures, 19
directories, defined, 62
Directory.AccessAsUser.All permission, 196
directory access permissions, 193–196
directory entities, programmatic access to, 236–237
directory entries for web APIs, 257–258
Directory Graph API, 10, 59–60, 64–65, 236–237
Application object JSON file, 178–180
application permissions, 217–218
calling, 233–234
group information, 219
directory permissions, 193–197
directory queries in cloud applications, 57–58
Directory.Read.All permission, 196
Directory.Read permission, 196
Directory.ReadWrite.All permission, 196
directory services for multitenant systems, 58
directory sync, 65–66
directory tenants, 58
Directory.Write permission, 196
discovery document, 119, 208
keys document, 120–121
location, 277
displayName property, 181, 188
distributed sign-out, 27, 29, 101, 109
domain-based identifiers, 63
domain controllers (DCs), 15, 20, 23
domain_hint parameter, 118
domain-joined servers, ADFS on, 54
domain-joined workstations, 14–16
domains, 14–16, 62
E
email claims, 133
endpoints, 18
ADFS, 276–277
common, 207
multitenancy and, 206–207
network, 52
OAuth2, 64
protocol, 60, 63–64
protocol/credential type, 60
turning on and off, 52
entities, 22–23, 70
environment dictionary, 138, 147
errorUrl property, 188
exp claims, 132
ExpiresOn property, 230
F
family_name claims, 133
federated tenants, 65–66, 122
federation. See also Active Directory Federation Services (ADFS)
for integrating with Azure AD, 66
for synchronized deployments of Azure AD, 65
Fiddler, 110
capturing trace, 112
HttpClient traffic tracing, 261
setup, 111
Fiddler inspector, 127
first-name claim type, 8
form post response mode, 115
fragment response mode, 115
functions, creating, 163–164
G
GET operations
of Account/SignOut, 134
for authenticated resource requests, 125
requests through, 182
given_name claims, 133
Goland, Yaron, 41
grants
admin consent, 203–204
AuthenticationReponseGrant, 150
client credentials, 44–45, 251, 266
implicit, 46–47
OAuth2 grants, 252
oauth2PermissionGrants collection, 189–192
refresh token, 239–240, 242
Graph API. See Directory Graph API
groupMembershipClaims property, 182, 219
Group.Read.All permission, 197
Group.ReadWrite.All permission, 197
groups, 219–221
assigning, 215
consuming, 220–221
names, 220
number of, 221
guest Microsoft account users, 122
H
HandleResponse method, 162
hero apps, 48
homepage property, 181, 188
HostAuthenticationFilter attribute, 266
hosts in OWIN pipeline, 140
HTTP 302s, 98
redirects, 113–119, 145, 149–150
requests, 29–30, 44, 46
responses, 125
HTTP 401 responses, 149, 235, 261–262
HTTP claims-based identity, 22
HttpClient traffic tracing, 261
HttpContext.Current.User, 8
HttpContext.GetOwinContext().Authentication method, 100
HttpContext.GetOwinContext().Authentication.SignOut method, 134
HttpModules, 83, 137
as host for OWIN pipeline, 140
predefined events, 145
HTTP requests to back end, 46
HTTPS URL for projects, 91, 94
HttpWatch, 110
hybrid authentication flow
APIs, obtaining tokens, 224–232
authorization code redemption, 227–232
authorization codes, 166
initialization, 113
OpenID Connect, 40–42, 108
token validation requirements, 133
hybrid token-requestor and resource-protector role development libraries,
85–86
I
IAppBuilder interface, 140
iat claims, 132
IAuthenticationSessionStore interface, 171
identifierUris property, 181
identity libraries. See libraries
identity party trusts in ADFS, 274
identity providers (IdPs), 17–18, 20
endpoints, 18
metadata, 18, 21, 108
public-private key pairs, 18–19
redirecting to, 160
SAML, 25–26
string identifiers, 18
WS-Federation, 28–29
identity transactions, 17–23
ID tokens, 39–40, 116, 127–134, 230, 280–281, 286
claims in, 133–134
decoding, 127–129
from server-to-server calls, 42
user information in, 269
validating, 42, 133
IIS Express, 91
IIS integrated pipeline, 145
impersonation, 44
implicit flow, 182
implicit grants, 46–47
integrated authentication, 14–16
interceptors, 74
intranets, authentication on, 14–16
Intune API, 61
Invoke method, 142
IOwinContext wrapper, 142
IsInRole() role information, 216
IsMultipleRefreshToken property, 230
iss claims, 132
IssuerSigningKey property, 167
issuer validation, 208–209
iss (issuer) value, 120–121, 208
J
JavaScript
HTTP requests to back end, 46
logic-layout management, 45–46
native apps, 81
token bits, retrieving, 46–47
Jones, Mike, 41
JSON Tokens, 41
JSON Web Algorithms (JWA), 110, 131
JSON Web encryption (JWE), 129
JSON Web Keys set (JWKS), 280
JSON Web Signature (JWS), 129–131
JSON Web Token (JWT), 19, 40
access token representation as, 255
for access tokens, 271
ADFS support, 55
claim set, 131–132
components, 129–130
handlers, 84, 92
header types, 131
specification, 110, 129
tokens, 84
just-in-time provisioning, 58
K
Katana, 139–154. See also Open Web Interface for .NET (OWIN)
assembly:OwinStartup attribute, 139
context, 145–148
diagnostic middleware, 153–154
appSettings entry, 139
middleware behavior settings, 158–159
middleware execution, 145
notifications, 159–166
OwinStartup attribute, 139
Startup class, 139–141
UseStageMarker method, 145
“Katana” 3.x, 83–84
“Katana” vNext, 84
Kerberos
native applications and, 47
service principals, 174
Kerberos federation, 17
keyCredentials property, 181, 188, 226–227
keys
assigning to applications, 226–227
credentials, 181
IssuerSigningKey property, 167
JSON Web Keys set, 280
keyCredentials property, 181, 188, 226–227
public-private, 18–19, 227
RefreshOnIssuerKeyNotFound property, 158
signing, 280
symmetric, 19
token-signing, 120–122, 158
token-validation, 167
ValidateIssuerSigningKey property, 168
keys document, 120–121
Klout web application, 248–249
knownClientApplications property, 183, 258
L
libraries
in Active Directory, 75
authentication tasks, 73–74
for hybrid token-requestor and resource-protector role, 74–75, 85–86
for native clients, 294
open source, 76
for other platforms, 293
reasons for using, 71
for resource-protector role, 73–74, 82–85
for token-requestor role, 70–71, 76–81
line-of-business (LOB) applications, 4–5
local networks, authentication on, 14–15
localStorage, 47
login_hint parameter, 117
logoutUrl property, 188
M
managed tenants, 65–66, 122
manifest files, 214, 219
/me alias, 237
MessageReceived notification, 165
messages
SAML, 26–27
signed, 26
WS-Federation, 28–31
metadata, 18, 21
MetadataAddress, 104, 277
metadata documents, 158
discovery document, 119
OpenID Connect format, 39
SAML format, 26
WS-Federation format, 29
MetadataEndpoint, 287
Microsoft.AspNet.WebApi.Owin NuGet package, 266
Microsoft Azure. See Azure Active Directory
Microsoft cloud service, 61
Microsoft Enterprise Agreement, 62
Microsoft.IdentityModel.Protocol.Extensions NuGet package, 84, 92
Microsoft Office 365. See Office 365
Microsoft Online Directory Service (MSODS), 60
Microsoft.Owin.Diagnostics NuGet package, 154
Microsoft.Owin NuGet package, 92
Microsoft.Owin.Security.ActiveDirectory NuGet package, 83, 254
Microsoft.Owin.Security.Jwt NuGet package, 254
Microsoft.Owin.Security.OAuth NuGet package, 254
Microsoft.Owin.Security.OpenIdConnect NuGet package, 84
Microsoft.Owin.Security NuGet package, 92
Microsoft.Owin.Security.WsFederation NuGet package, 83
Microsoft Visual Studio. See Visual Studio
_middleware entry, 141
middleware initialization options class, 155–159
middlewares
activation sequence, 142–145
behavior settings, 158–159
building, 138. See also Open Web Interface for .NET (OWIN)
caption setting, 159
context, 142, 145–148
environment dictionary, 138
initialization pipeline, 265–266
Invoke method, 142
message received notification, 164
observing pipeline, 143–145
pipeline of web APIs, 254–255
pointers to next entries, 142
requesting execution, 145
resource protectors, 74, 81
response handling, 161
security token received notification, 164
security token validated notification, 164–165
sign-in and sign-out flow, 99–103
skipping to next, 161
stopping processing, 142, 145
UseStageMarker method, 145
midtier clients ADAL libraries, 81
MMC (Microsoft Management Console), 60
mobile operating systems, native apps on, 80
modern authentication techniques, 31–48
multiple authentication factors (MFA), 122
Multiple Response Type specifications, 109
multiresource refresh tokens (MRRT), 242–243, 260
multitenancy, 205–211
MVC 5 Controller, 100
/myorganization alias, 237
N
native applications, 47–48
ADAL libraries, 48, 80–81
ADFS support, 55
ADFS template, 275
admin creation in Azure portal, 204
authentication flows, 94
broker apps and, 48
development libraries, 75–76
Kerberos and, 47
modern authentication for, 294
popularity, 47–48
tokens, obtaining, 21–22
nbf claims, 132
.NET-based applications, 78
.NET core, OWIN middleware for, 84
.NET Framework
caller identity class, 7–10
SAML and, 25
version 4.5, 82
Windows Identity Foundation classes, 82–83
.NET JWT handler, 84
.NET web development, 138
network endpoints, 52
network tracing features, 110
nickname claims, 133
Node.JS, 81
nonadmin users, application creation, 189–192. See also users
nonce value
of authentication requests, 117
cookie tracking, 124–125
OpenID Connect, 149
notifications, 159–166
AuthenticationFailed, 166
AuthorizationCodeReceived, 166
in bearer token middleware, 264
MessageReceived, 164
RedirectToIdentityProvider, 162–164
SecurityTokenReceived, 164
SecurityTokenValidated, 164–165
sequence, 159–161
of TokenCache class, 244–245
Notifications property, 155
NuGet packages
adding references, 92
Microsoft.AspNet.WebApi.Owin, 266
Microsoft.IdentityModel.Protocol.Extensions, 84, 92
Microsoft.Owin, 92
Microsoft.Owin.Diagnostics, 154
Microsoft.Owin.Security, 92
Microsoft.Owin.Security.ActiveDirectory, 254
Microsoft.Owin.Security.Jwt, 254
Microsoft.Owin.Security.OAuth, 254
Microsoft.Owin.Security.OpenIdConnect, 83
.NET, 227–228
System.IdentityModel.Tokens.Jwt, 84, 92
SystemWeb, 92
for web APIs, 254
web apps referencing, 92
O
OAuth, 33–37
OAuth2, 33–37
ADAL and, 76–77
ADFS “3” support, 272
authorization grants, 55, 252
bearer token usage, 232–237, 262
claims and, 36–37
client credentials grants, 44–45
endpoints, 64
ID token, 39
interoperability, 37
limitations, 118
Multiple Response Type, 109
“on-behalf-of” security token requests, 44
OpenID Connect extensions, 39, 110
permissions in applications, 183–185
Post Response Mode, 109
refresh token grants, 239–240
refresh tokens, 238–251
scope, 116
support for, 37
Token Exchange extensions on-behalf-of flow, 267–270
web sign-in, 37–39
oauth2AllowImplicitFlow property, 182
oauth2AllowUrlPathMatching property, 182
OAuth2 Authorization Framework specification, 110
OAuth2 bearer token middleware, 287
OAuth2 Bearer Token Usage specification, 110
oauth2PermissionGrants collection, 189–192
admin consent, 203–204
consent entries, 210
oauth2Permissions collection, 183–185, 188, 192–195
default entry for web APIs, 257
value property, 257–258
oauth2RequirePostResponse property, 182
OAuth WRAP (Web Resource Authorization Profile), 33, 40–41
objectId property, 180, 188, 190
odata parameters in URL template, 237
Office 365, 61
cloud-based issues, 58
Visual Studio 2015 tools, 87
oid claims, 133
on-behalf-of flow, 267–270
security token requests, 44
on-premises Active Directory, 15–16, 58–59
on-premises directories
functional components, 60
querying protocols, 59
OnValidateIdentity, 265
opaque channels, 72, 91
OpenID, 37–38
OpenID Connect, 9, 38–43, 108–109
authentication, 122–123
authentication-request message type, 39
authentication requests, 113–119
authorization-code flow, 42–43
authorization requests, 98, 149
discovery, 119–122
document format, 39
endpoints, advertising by Azure AD, 109
ID token, 127–134
initialization code, 95–97
JWT format, 129–132
nonce, 149
opaque channels, 91
response, 123–125
session management, 109
sign-in sequence, 110–112, 126–127
sign-out, 134–136
support for, 43
supporting specifications, 110
web sign-on with ADFS, 276–281
OpenIdConnectAuthenticationOptions class, 159
OpenIdConnectAuthenticationOptions parameter, 96, 155–159, 276
TokenValidationParameters property, 166–169
OpenID Connect Core 1.0, 108
OpenID Connect Discovery 1.0, 109
OpenID Connect hybrid flow, 40–42, 224–232
OpenID Connection Session Management specification, 109
OpenID Connect middleware, 92, 155–166
ADFS and, 276–277
authentication flow control, 96
authority value, 97
Challenge sequence, 152
client ID, 94, 97, 256
distributed sign-out, 101
initializing, 95–97, 277
notifications, 159–166
OpenIdConnectAuthenticationOptions, 155–159
outgoing 401s, 98
Passive authentication mode, 152, 159
postlogout redirects, 102
session management, 149–151, 171
sign-out, 100–101, 152–153
token validation, 149–151
TokenValidationParameters property, 166–169
OpenIdConnectNotifications class, 159–166
OpenIdConnectProtocolValidator class, 158
OpenID Connect Session Management specification, 135
openid scope, 116, 286
open redirector attacks, 182
open source libraries, 76
Open Web Interface for .NET (OWIN), 83–84, 137–138. See also
middlewares
ASP.NET-specific implementation, 138
context, 145–148
defined, 138
environment dictionary, 138
Katana and, 139–154. See also Katana
Open Web Interface for .NET (OWIN) middlewares
adding to web apps configuration, 92
authentication capabilities, 146
authentication flow, 148–153
for claims-based identity, 83
core status, 147
diagnostic middleware, 153–154
environment dictionary, 147
hosting, 92, 95–96
for .NET core, 85
OpenID Connect, 137–170
sign-in flow, 148–152
sign-out flow, 152–153
WS-Federation support, 103
Open Web Interface for .NET (OWIN) pipeline
adding middleware, 141–142
hosts, 140
initializing, 139–141
_middleware entry, 141
servers, 140
OS X apps, ADAL libraries, 81
OwinMiddleware class, 142–143
OwinStartup attribute, 139
P
parametric STS, 205–208
password-based authentication, 12–14
passwordCredentials property, 181, 188, 226
passwords, 13–14
password sharing antipattern, 32–33
path matching, 182
permissions
admin-level, 198–200
app-level, 216–219
on application entry in Azure AD, 224–225
consented, 186
delegated, 192–197
directory, 193–197
for directory access, 193–196
fine-grained, 59
granted, storage of, 189–192
roles and, 213
Permissions To Other Applications, 259
platform as a service (PaaS), 57
postlogout redirects, 102, 156
PostLogoutRedirectUri property, 102, 156, 276
Post Response Mode specifications, 109
pre-claims authentication techniques, 12–16
principalType property, 212
private/public key pairs, 227
profile scope value, 116
profile stores, 12–14, 20
programmable web, 31–33
Programming Windows Identity Foundation, 82, 137
prompt=admin_consent flag, 200–201, 218
prompt parameter, 117–118
Properties dictionary, 140–141
protected APIs. See also web APIs
accessing, 232–251
exposing, 253–272
refresh tokens, 238
protected clients, 78
protocol coordinates, 73–74
protocol/credential type endpoints, 60
protocol endpoints, 60, 63–64
protocol enforcement, 73
protocol libraries, 77
protocol middleware. See also OpenID Connect middleware
collaboration with cookie middleware, 148
protocols, application identifiers, 181
protocol URLs, 63, 94
protocol validation, 158
ProtocolValidator property, 158
providers
claims issued, 9
specifying, 265
provisioning
in ADFS, 271, 287
applications, 53–54, 57, 189
in Azure management portal, 93–94
just-in-time, 58
relying parties, 52
ServicePrincipal, 186
web APIs, 253
provisioning flow, 175–176
provisioning resources, 183
proxy role, 52
proxy utilities, 110
publicClient property, 181
public clients, 78, 275
public key cryptography, 19
public-private key pairs, 18
publisherName property, 188
pwd_exp claims, 133
pwd_url claims, 133
Q
querying protocols, 59
query response mode, 115
R
reauthorization, 248–249
redirects, 35
RedirectToIdentityProvider notification, 160–164
modifying authentication requests, 201
redirect URIs, 100, 115, 117, 135, 156, 180–181, 276
for web apps, 278
RefreshOnIssuerKeyNotFound property, 158
refresh token grants, 239–240, 242
RefreshToken property, 230
refresh tokens, 35, 238–251
in Azure AD, 240–242
expiration, 246–251
invalidating, 240
multiresource, 286
opacity to client, 242
validity times, 240
relying parties (RPs), 18
distributed sign-out, 109
IdP metadata, 108
provisioning, 52
user sign-in status inquiries, 109
WS-Federation, 29
relying party trusts, 274–276
renewal operations, 71
replyUrls property, 180–181
Request and Response methods, 148–149
Request and Response properties, 147–148
requests
ClientAssertionCertificate, 231
client_secret property, 227
interception, 73
redirect URI, 156
resource for authorization code, 156
response type, 156
scope parameter, 156
through GET operations, 182
through middleware pipeline, 138
token inclusion, 70
requiredResourceAccess, 198–199
RequiredResourceAccessCollection, 185–187
Role type entries, 218
resource apps
configuring by IdP’s metadata, 73
token acquisition, 73
token validation, 73
resource consumption
identity of clients, 265–266
patterns, 43–45
resource identifiers in token requests, 256
resourceId property, 190
Resource parameter, 118, 156, 231
<resource path> component in URL template, 236–237
resource protectors, 69, 73–74
development libraries, 81–85
interceptors, 74
resources
accessing, 185–187
accessing as application, 44–45
access requests, 70–71. See also requests
authorizing access, 97–98
client libraries, 71–72
multiple, refresh tokens for, 242–243
third-party access, 34
type of access scope, 185–186
resource STS, 205–206
response mode and response type parameters of authentication requests,
114–116
Response object, 149–150
responses
handling, 161
ID token, 127–134
OpenID Connect message, 123–125
through middleware pipeline, 138
ResponseType parameter, 156
response types, 124
REST API calls, 233–235
REST-based protocols, 28
REST operations for directory queries, 59
RoleClaimType property, 216
groups as, 220
roles
allowedMemberTypes property, 214
application, 212–219
assigning, 213–214
claims, 133, 215
displayName and description strings, 214
id property, 214
value property, 214
WS-Federation, 28–29
round trips
performance and, 45
request-response pattern, 22–23, 45
web apps, 23–31
RS256 signatures, 131
S
samlMetadataUrl property, 182, 188
SaveSignInToken property, 171, 268
scope-driven authorization, 262–265
scopes, 116, 118, 156, 201
openid, 286
of web APIs, 284–285
security
HTTPS, 91
nonce values, 117
for web API calls, 46–47
Security Assertion Markup Language (SAML), 8, 25–27, 55, 182
security code, custom, 71
security groups, 219
SecurityTokenHandlers property, 158
SecurityTokenReceived notification, 165
Security Token Service (STS), 29
Access Control Service, 78–79
resource, 205–206
SecurityTokenValidated notification, 165–166
server applications, ADFS template, 275
servers
in OWIN pipeline, 140
server-to-server calls, 42
ServicePrincipal, 174–177, 187–188
AppId, 193
oauth2Permissions, 193–196
ObjectId, 193
properties, 187–188
provisioning, 186
for web APIs, 257
ServicePrincipal.appRoleAssignedTo object, 216–219
servicePrincipalNames property, 188
service providers (SPs), 26
session artifacts, 73
session cookies, 24–25, 45, 92, 122
discarding, 135
in OpenID Connect hybrid flow, 42
persisting, 150
validation, 73, 125
session data, 24
session management, 70–71
by ADAL, 238–251
in OpenID Connect middleware, 109, 171
sessions
ClaimsPrincipal, saving, 151
cleaning, 135
ending, 134–136
establishing, 22, 73, 149–151
properties, 151
request token validation, 152
saving, 150
validation, 73
sessionStorage, 47
Set-Cookie value, 135, 149–151
shared secrets, 279–280, 287
signatures, 19
signature verification, SAML and, 26
signed tokens, 20
sign-in, 37–39, 99–103, 126. See also web sign-on
notifications, 159–160
response phase, 224–225
sequence, 126–127, 224
UI elements, 102–103
user credentials prompts, 163
sign-in and sign-out flow, 110–112
SignInAsAuthenticationType property, 159
sign-in flow
access in context of session, 148, 152
challenge generation, 148–149, 152–153
OpenID Connect for, 107–134
response processing, 149–151
session generation, 149–151
specifications and dependencies, 107–108
WS-Federation, 29–31
signing keys for web apps, 280
sign-in messages
generation of, 149
redirects, 148–149
request generation, 73–74
sign-out, 99–103
distributed, 101, 109
flow sequence, 136
ID hint, 135
notifications, 161
OpenID Connect, 134–136
postlogout redirects, 156
PostLogoutRedirectUri property, 102
redirect URI, 135
request syntax, 135
state preservation, 135
target endpoint, 135
UI elements, 102–103
user credentials prompts, 163
sign-out flow, 152–153
SignOut method, 99
Simple Web Token (SWT), 40–41
Single Logout messages, 27
single-page applications (SPAs), 45–47, 294
ADAL JS library for, 85
single sign-on, hack for, 38
single sign-out, 27
SkipToNextMiddleware method, 161
software as a service (SaaS) apps, 17
_sso_data claim, 289
SSO sessions, 27
stage markers, 145
Startup.Auth.cs file
ADFS identity provider code, 103–104
identity pipeline initialization code, 96–97
Startup class, 139–141
Startup.Configure, 140
Startup.cs file, 95
call to activate authentication, 97
state, preserving at sign-out, 135
state parameter
of authentication requests, 116–117
local URL of resource, 125
storing tokens, 70–71
string identifiers, 18
verification, 19
sub claims, 132
subjects, 25
symmetric keys, 19
synchronized deployments of Azure AD, 65
synchronizing users and groups to Azure AD tenants, 65–66
System.IdentityModel.Tokens.Jwt NuGet package, 84, 92
System.Security.Claims namespace, 7
SystemWeb NuGet package, 92
System.Web pipeline, 140
T
tags property, 188
target directories
consent, recording, 186
resource entries in, 183
target platforms, native libraries for, 81
<tenant> component in URL template, 236
tenant IDs, 63, 188, 230
Tenant parameter, 255
tenants
application availability, 182–183
defined, 62
display name, 188
federated and managed, 65–66
ServicePrincipal and, 176, 193
tenant IDs, 63, 188, 230
third-party access to resources, 34
Thread.CurrentPrincipal, 8
tid claims, 133
token acquisition, 70
ADAL pattern, 77
TokenCache class, 244–245
token endpoint, 35
authenticated requests against, 226
response to token request, 231–232
token handlers, 158
token replay attacks, 117
TokenReplayCache property, 170
token requestors, 69–72, 74
access token format and, 72
client applications as, 72
development libraries, 76–81
token requests, 70
on-behalf-of, 44
resource identifiers in, 256
user consent, 64
tokens, 18–20. See also access tokens
accessing independent of protocol, 268
acquiring by authorization code, 227–229
assertions, 26
audience claims, 282
in Authorization HTTP headers, 232–234
Azure AD, 61
bearer, 232–237, 262
broker apps, 48
caching, 70–71, 238
callback path, 158
for client-resource interactions, 70
cross-domain, 25
group information, 219–220
group membership claims, 182
HTTP carrier mechanisms, 109
ID. See ID tokens
issuance of, 21–22
issuers, 120
JWT format, 40, 129–132
life-cycle management, 159, 238
refresh, 35, 238–251, 286
replaying, 169
in requests, 70–71
response mode, 114
response type, 109
SAML structure, 26
saving, 169
scope, 257–258
security of, 42–43
signed, 20, 120–122
Simple Web Token, 40–41
with user attributes from cloud store, 59
user information, 42, 268
validation. See token validation
for web API calls, 46–47
token validation, 22, 73, 119, 149–151
audience, 167
discovery of criteria, 119–120
issuer, 167
key for signing, 167
notification of, 164–165
parameters, 166–169
signature check, 129–130
validation flags, 168
validator delegates, 168–169
validity interval, 167
TokenValidationParameters class, 155, 157, 167–170, 256–257, 261, 264
IssuerValidator property, 208
ValidIssuers property, 208
TraceOutput property, 148
traces
capturing, 110–112
exposing, 148
traffic, capturing in trace, 110–112
trusts, 18
between app and IdP, 20–21
establishment, 57
type identifiers, claim, 8–9
U
UI, sign-in and sign-out, 102–103
unique identifiers, 18
unique_name claims, 133
upn claims, 133
URI fragments, 46–47
UseCookieAuthentication method, 96, 141
UseErrorPage method, 154
Use method, 140, 142
UseOpenIdConnectAuthentication method, 96, 141
UserAssertion class, 268–269
user assignment, 211–213
user attributes, 7, 12
user consent for token requests, 64
user credentials. See also credentials
for synchronized deployments of Azure AD, 65
synching to cloud, 65–66
user_impersonation permission, 196
UserInfo property, 42, 230
username-password-profiles authentication, 13–14
UserProfile.Read permission, 195–196
User.Read.All permission, 197
User.ReadBasic.All permission, 196
User.Read permission, 195–196
users
accessing web APIs, 252
application creation, 189–192
assignment, 211–213
authentication experience, 122–123
Azure AD landing page, 66
consent prompts, 191
identity, 12–13
life cycles, 14
roles and, 213
Use* sequence, 143
UseStageMarker method, 145
UseTokenLifetime property, 159
UseWindowsAzureActiveDirectoryBearerAuthentication method, 255, 271
UseXXX extension methods, 96
V
validate-and-drop-a-cookie approach, 23–24
ValidateAudience property, 168
ValidateIssuer property, 168
ValidateIssuerSigningKey property, 168
validation
authority coordinates and, 157–158
components, 9
flags, 169
of ID tokens, 133
issuer, 208–209
session, 73
of session cookies, 73
token, 73
validator delegates, 169–170
ValidAudience property, 167, 256
ValidIssuer property, 167
verification, 19, 24
Visual Studio
application credentials, assigning, 226
ASP.NET 4.6 Web API projects, 254, 287
authentication preferences settings, 288
Browser Link, 144
creating new web app, 90–91
F5 verification procedure, 91
identity-integration features, 86–87
Immediate window, 247
Multiple Startup Projects option, 288
MVC 5 Controller, 100
Package Manager Console, 92
Startup.cs file, 95
using directives, 98
web API project setup, 253–258
web API project template on-premises option, 271
Windows Identity Foundation tools, 82
Visual Studio 2013, 86
Visual Studio 2015, 2–3
accounts, associating with, 3
AD integration features, 86
keychain, 87
tying to Azure user account, 2–3
W
web API calls
handling, 258–265
securing, 46–47
web APIs
access control policies, 283
accessing as an application, 251–252
accessing as arbitrary user, 252
application permissions for, 284
calling, 260
calling another API, 266–270
claims in token, 289–290
client access, 291
clients, adding, 291
client setup, 258–262
consent for, 258
directory entries, 257–258
exposing, 253–272
failed token requests, 261–262
identifiers, 282
invoking from web app, 223–252, 285–289
invoking with access tokens, 232–251
invoking with bearer tokens, 232–237
middleware pipeline, 254–255
modeling, 177
NuGet packages for, 254
project setup, 253–258
protecting with ADFS, 271–272, 281–292
request processing, 262–265
scope-driven authorization, 262–265
scopes of, 284–285
ServicePrincipal, 257
tokens, obtaining, 21–22
troubleshooting, 261
unauthorized caller errors, 261
web applications
ADAL cache considerations, 243–246
ADFS as identity provider, 103–104
ADFS support, 55
application credentials, 279
authentication flows, 94
claims, 98
client ID, 94, 278
creating, 90–91
delegated access, 34–36
HTTPS, 91
hybrid role of token requestor and resource protector, 74–75
interaction pattern, 22–23
invoking web API from, 285–289
middlewares, adding and initializing, 95
OpenID Connect initialization code, 95–97
OWIN pipeline, adding, 95
Permissions To Other Applications, 259
protocol coordinates, 276, 278
redirect URIs, 278
referencing NuGet packages, 92
registering in Azure AD, 93–94
roundtrip-based request-response pattern, 22–23, 45
running, 98–99, 103
setup in ADFS, 277–280
shared secrets, 279–280
sign-in and sign-out, 99–103
signing keys, 280
sign-in message generation, 73
single-page applications, 46
single sign-on, 38
SSL Enabled, 91
third-party access to resources, 34
triggering authentication, 97–98
unique resource identifier, 94
user authentication, 21
web API, consuming, 223–252
Windows Integrated Authentication credential, 279
web browser–based SSO, 25–27
web.config files, 83
web servers, decoupling from apps, 138
web sign-on, 29–31. See also sign-in
ASP.NET support for, 137. See also Open Web Interface for .NET
(OWIN) middlewares
hybrid authentication flow, 108
OpenID Connect Core 1.0, 108
with OpenID Connect in ADFS, 276–281
testing, 280–281
URLs, 94
web UX, exposing, 265–266
Wells, Dean, 15
WindowsAzureActiveDirectoryBearerAuthenticationOptions initialization,
255
Windows Identity Foundation (WIF), 82–83
Windows Internal Database (WID), 52
Windows Server. See also Active Directory Federation Services (ADFS)
ADFS server role, 54
Windows Server 2016, ADFS in, 56, 103, 273–292
workplace-joined device detection, 56
WS-Federation, 8, 27–31
ADFS support, 55
messages, 29–31
metadata document format, 29
OWIN middlewares support, 103
relying parties, 29
roles, 28–29
Security Token Service, 29
sign-in flow, 29–31
support, 31
support in .NET core, 85
tokens, 29
WS-Federation middleware. See OpenID Connect middleware
WS-* specifications, 27–28
native apps and, 47
WS-Trust, ADFS support, 55
Wtrealm, 104
WWW-Authenticate header, 262
X
X.509 certificates, 18–19
Xamarin, 80
About the author