Guest Access using Azure Active Directory B2C custom policies

Avishay Balter
Microsoft Azure
Published in
9 min readNov 4, 2021
Photo by Akira Hojo on Unsplash

Abstract

Authenticating and authorizing users is very common and almost an everyday task for software engineers. Using Azure Active Directory B2C makes working with the standard protocols simple and will save much effort when developing the user experiences around getting the tokens required to access the system.

Some flows required from an identity provider, such as signing up users, signing them in, changing a password, or other account properties, are well known, well documented, and easy to get started with. Designing more complex scenarios is made possible thanks to the powerful customization capabilities of Azure Active Directory B2C custom policies that make this service into an open platform to create OAuth/OIDC tokens that can be obtained according to almost any application logic.

Our Microsoft team, the Commercial Software Engineering (CSE) had recently met with customer scenarios in which a guest-access to a system was required.

In a system where users are authenticated and signed in, there is typically some sort of a sign-up process where users’ details are written to a directory, along with their preferred sign-in method (password, social account, etc.). Those users can then re-enter the system using a sign-in flow.
However, what about cases where users don’t want to sign-up?

As engineers, it’s easy to forget that users are already burdened with too many accounts, in too many applications just like ours, where they may go to only once to see some data that had been shared with them, never to come back. Those users are concerned about losing track of their online presence, and they may choose not to use the application just because it makes them sign-up, causing us to lose potential users and the usability of our application.
In such cases, accessing the system as an invited guest can solve much of the pain around usability for those users, the overall security and access to the system, and relieving us from maintaining and managing those user accounts in the directory.

I couldn’t have written this post without the effort of the hacking team that had been working on this solution: Nava Vaisman Levy and Shiran Rubin Manaev, and the AAD B2C Product Manager who’d been working closely with us, Yoel Horvitz. Please find the complete source code sample for this solution at the AAD B2C samples repository on GitHub, here.

Guest Access

The term “guest user” is not defined within the authentication/authorization protocols; indeed, it is a term rooted in the application’s logic. However, since it interfaces so closely with the user’s directory and how those users will acquire their access tokens, it’s a feature we’d like to see supported by the identity management solution we’re using, AAD B2C in this case.

A guest user in an application will usually have the following attributes:

  • They are invited to the application.
  • They will have a single interaction/session with the application.
  • They will not return.

It’s important to note that an additional property for a guest user is probably some key by which we’d like to identify a guest user once they are signed in to the application. For instance, an objectId by which we can declare that once they are signed in, they will have access to a specific data set or location in the application. Another example may be using a campaign id or code which is generated by a backend marketing tool and used by the application for its functionality.

A use case for guest access pattern

You may think of any consumer application where documents that are owned by the application’s users (media, mail, financial/medical records, etc.) and are shared with users outside the identity boundary of the application.

To illustrate such an application in this post we’ll use ContosoCloud; a cloud storage service for consumers who are mainly interested in easy, yet secure and audited, sharing of access to specific files in their private cloud storage with just about anyone who has a phone or mail account.

Signing up as a user to ContosoCloud uses a standard sign-up policy, and is not covered in this post.

The following swim-lane diagram describes a GET request for a specific document and the sign-in flow for a previously-signed-up user in ContosoCloud

Signed-up user sign in flow

To create an invite link for a guest user to a ContosoCloud hosted document , a user will go through the following flow.

Create invite link

Finally, once the invite link had been shared with the guest (through mail or QR-code), this is the flow that the guest user will follow:

Guest User sign in flow

A guest user journey

In AAD B2C custom policies, a functional flow that results in issuing a token with a set of claims is referred to as a User Journey, which is made of the steps (Orchestration Steps) to get an access token. For instance, a sign-up process is a user journey, a sign-in process is another, and a combination of the two (as in most applications) is yet another journey you can define. The User Journey is exposed as a relying-party endpoint, with a known URI, once it is uploaded to the B2C tenant.

A guest user is invited to use the application, using a link which is generated by the application’s permanent user and may be sent to him through the mail, or scanned using a QR code. The link URI refers to the application’s domain (www.contosocloud.com), where a redirect is driven by the authentication-middleware to the application’s configured sign-up/sign-up User Journey URI to begin his fake sign-up process.

During the invitation process we should determine what is the ID we expect the invited user to have once signed-in. In ContosoCloud that refers to a database record of the person being invited by the app’s user to view a specific file, but in other scenarios this can be determined based on existing records of the user in a back-end system or any other application logic. For AAD B2C to know what is that expected ID during the fake sign-up process and to issue that ID as a claim on the output access token, we’re going to use an encrypted id_token_hint parameter.

An id_token_hint is a standard optional parameter in the OIDC protocol “used to pass ID tokens previously issued by an Authorization Server, being passed as a hint about the end-user’s current or past authenticated session with the client.”

In our case, for invited guest users, we’d like AAD B2C to issue an access token where the name-identifier, or objectId, claim of the access token is taken from the id_token_hint’s UserIdentifier property.

The id_token_hint JSON looks like this:

{
“alg”: “HS256”,
“typ”: “JWT”
}.{
“UserIdentifier”: “123-4567-89”,
“nbf”: 1599482515,
“exp”: 1600087315,
“iss”: “https://localhost",
“aud”: “a489fc44–3cc0–4a78–92f6-e413cd853eae”
}

To guard the token from being changed after it was created, the link which the guest user uses contains encryption of this JSON, using either symmetric or asymmetric keys. For simplicity, this sample code uses symmetric keys that are managed in AAD B2C’s policy key’s store, but this can be improved using an Azure Key Vault. The following C# code generates the link for inviting users.

Generate an invite URI

Once the invited user uses the link, they are forwarded to the B2C policy URI, with the id_token_hint, where he will be issued an access token and redirected back to the application bearing the token. To copy the query parameter id_token_hint to the authentication-middleware redirect, we use the following code in Startup.cs

Forward id_token_hint to the sign up policy

Signing up with an ID Token hint

The user had been redirected to the policy’s endpoint and is now following the User Journey described in the custom policy’s XML.

Building Blocks

We first define the following custom claims:

  • UserIdentifier — an input claim from the id_token_hint JWT.
  • ErrorMessage — an output claim where error messages are stored.

Custom claims

Next, Define the following claim transformations:

CopyUserIdentifier: Copies the content of UserIdentifier claim into objectID claim.

CreateUnsolicitedErrorMessage: Error shown for uninvited guests trying to use the application without an id_token_hint.

Claim Transformations

Technical Profiles

Technical profiles are the functional elements of AAD B2C custom policy. We define the following features:

IdTokenHintExtractClaims: Extract UserIdentifier claim from an id_token_hint.

Note that this profile uses the cryptographic symmetric keys we’ve stored in the tenant’s key store with the name “B2C_1A_EncryptionKey”. During decryption, both audience and issuer claims from the id_token_hint JSON will be validated against the keys in the policy’s Metadata element.

GuestAccountSignup: This is the fake sign-up technical profile where the platform asks the user for their name, display name, and surname.
Note that there is no mail or password exchange with the user since we’re not storing them in the AAD B2C directory.

This technical profile uses the output claims transformation CopyUserIdentifier to copy the value of claim UserIdentifier to objectID resulting with a list of claims that is similar to that of a signed up user.

SelfAssertedUnsolicited: A technical profile that runs the transformations required to output an error message to the user id they try to sign up to the application when they were uninvited.

The User Journey

Putting all the technical profiles and transformation into a single flow, using a single user journey, SignUpWithHintOrSignIn. It has the following orchestration steps:

GetClaims: First, we extract input claim from id_token_hint query parameter, if it exists.

Sign In (not guest) users: Next, if id_token_hint was not provided, we display the sign-in page for the standard application users, who are registered to the B2C tenant.

Check for uninvited guests: If by now neither objectID nor UserIdentifier claims were not populated, it means that the user did not sign in and also did not have an id_token_hint. This indicates that they are uninvited, and the following step will terminate the flow, displaying an error message.

Sign up invited guests: Finally, if objectId is not yet set, it is time to fake-sign-up the invited user using the sign-up technical profile, given we know that the UserIdentifier claim had been read from the id_token_hint.

Return claims: The final stage constructs the access token, which is output through the redirect back to the application.

The claims are defined in the technical profile under the Relying Party element, which establishes the policy endpoint.

Final Thoughts and pro tips

With custom policies, AAD B2C is a code-less platform for identity and authentication/authorization protocol implementation that is a part of your application’s codebase. This enables proper software engineering fundamentals to what is usually one of the most challenging elements to manage in a system. It further promotes investing effort in implementing complex identity scenarios that fit your application logic.

The documentation of AAD B2C, and specifically the custom policies’ is incredibly detailed and contains most, if not all, the code snippets that were required to form this sample. Working with AAD B2C custom policies, as with any XML-based configuration, can sometimes be challenging.

To make your lives a bit easier, we encourage you to follow some of the troubleshooting guide’s suggestions, such as:

Finally, It’s worth keeping in mind that since guest users are not managed by the AAD B2C directory, some of the service features will not apply. For instance, refresh token revocation, conditional access and in some cases MFA. Auditing log for guest user sign-in will not show an object-id, since the user does not have one issued by the tenant.

--

--

Avishay Balter
Microsoft Azure

Code, Cloud, Ops and Analog synthesizers. Software Engineer and Architect @ Microsoft.