Using OAuth 2.0 and OpenID Connect with Azure AD

Harinderjit Singh
ITNEXT
Published in
17 min readFeb 7, 2023

--

In the early days of the Internet, information between services was shared simply by giving your username and password for one service to another so they could login to your account and grab whatever information they wanted!

You should never be required to share your username and password, your credentials, with another service. There’s no guarantee that an organization will keep your credentials safe, or guarantee their service won’t access more of your personal information than necessary. It might sound crazy, but some applications still try to get away with this!

Today we have an agreed-upon standard to securely allow one service to access data from another. The goal of this post is to explain how these standards work in the Azure ecosystem.

Authentication

Authentication is the process of proving that you are who you say you are. This is achieved by verification of the identity of a person or device. It’s sometimes shortened to AuthN. The Microsoft identity platform uses the OpenID Connect protocol for handling authentication.

Authorization

Authorization is the act of granting an authenticated party permission to do something. It specifies what data you’re allowed to access and what you can do with that data. Authorization is sometimes shortened to AuthZ. The Microsoft identity platform uses the OAuth 2.0 protocol for handling authorization.

Tokens

The parties in an authentication flow use bearer tokens to assure, verify, and authenticate a principal (user, host, or service) and to grant or deny access to protected resources (authorization). Bearer tokens in the Microsoft identity platform are formatted as JSON Web Tokens (JWT).

Three types of bearer tokens are used by the Microsoft identity platform as security tokens:

  • Access tokens — Access tokens are issued by the authorization server to the client application. The client passes access tokens to the resource server. Access tokens contain the permissions the client has been granted by the authorization server.
  • ID tokens — ID tokens are issued by the authorization server to the client application. Clients use ID tokens when signing in users and to get basic information about them.
  • Refresh tokens — The client uses a refresh token, or RT, to request new access and ID tokens from the authorization server. Your code should treat refresh tokens and their string content as opaque because they’re intended for use only by the authorization server.

App registration

Your client app needs a way to trust the security tokens issued to it by the Microsoft identity platform. The first step in establishing that trust is by registering your app with the identity platform in Azure Active Directory (Azure AD).

When you register your app in Azure AD, the Microsoft identity platform automatically assigns it some values, while others you configure based on the application’s type.

Two of the most commonly referenced app registration settings are:

  • Application (client) ID — Also called application ID and client ID, this value is assigned to your app by the Microsoft identity platform. The client ID uniquely identifies your app in the identity platform and is included in the security tokens the platform issues.
  • Redirect URI — The authorization server uses a redirect URI to direct the resource owner’s user-agent (web browser, mobile app) to another destination after completing their interaction. For example, after the end-user authenticates with the authorization server. Not all client types use redirect URIs.

Your app’s registration also holds information about the authentication and authorization endpoints you’ll use in your code to get ID and access tokens.

Endpoints

The Microsoft identity platform offers authentication and authorization services using standards-compliant implementations of OAuth 2.0 and OpenID Connect (OIDC) 1.0. Standards-compliant authorization servers like the Microsoft identity platform provide a set of HTTP endpoints for use by the parties in an auth flow to execute the flow.

The endpoint URIs for your app are generated for you when you register or configure your app in Azure AD. The endpoints you use in your app’s code depend on the application’s type and the identities (account types) it should support.

Two commonly used endpoints are the authorization endpoint and token endpoint. Here are examples of the authorize and token endpoints:

# Authorization endpoint - used by client to obtain authorization from the resource owner.
https://login.microsoftonline.com/<issuer>/oauth2/v2.0/authorize
# Token endpoint - used by client to exchange an authorization grant or refresh token for an access token.
https://login.microsoftonline.com/<issuer>/oauth2/v2.0/token

To find the endpoints for an application you’ve registered, in the Azure portal navigate to:

Azure Active Directory > App registrations > <YOUR-APPLICATION> > Endpoints.

OAuth 2.0

OAuth 2.0 is a security standard where you give one application permission to access your data in another application. The steps to grant permission, or consent, are often referred to as authorization or even delegated authorization. You authorize one application to access your data, or use features in another application on your behalf, without giving them your password. Sweet!

OAuth 2.0 is a specification as to how to issue access tokens. It is defined in RFC 6749 (The OAuth 2.0 Authorization Framework). (c.f. The Simplest Guide To OAuth 2.0)

As an example, let’s say you’ve discovered a website named “Medium article of the Day” and created an account to have it send top medium article links as a text message every day to your phone. You love it so much, you want to share this site with everyone you’ve ever met online.

However, writing an email to every person in your contacts list sounds like a lot of work. Good thing “Medium article of the Day” has a feature to invite your friends! You can grant “Medium article of the Day” access to your contacts stored in another application (some web API) and send out emails for you!

  1. Pick your contact provider application
  2. Redirect to your provider apication and login if needed
  3. Give “Medium article of the Day” permission to access your contacts
  4. Redirect back to “Medium article of the Day”

Applications that use OAuth to grant access also provide a way to revoke access.

OAuth 2.0 Terminology

Before we dive into more details on what OAuth is doing, let’s map some of the OAuth terminologies.

Resource Owner: You! You are the owner of your identity, your data, and any actions that can be performed with your accounts.

Client: The third-party application (e.g. “Medium article of the Day”) that wants to access data or perform actions on behalf of the Resource Owner.

Authorization Server: The application that knows the Resource Owner, where the Resource Owner already has an account.

Resource Server: The Application Programming Interface (API) or service the Client wants to use on behalf of the Resource Owner.

Redirect URI: The URL the Authorization Server will redirect the Resource Owner back to after granting permission to the Client. This is sometimes referred to as the “Callback URL.”

Response Type: The type of information the Client expects to receive. The most common Response Type is code, where the Client expects an Authorization Code.

Scope: These are the granular permissions the Client wants, such as access to data or to perform actions. Custom scopes can be defined as well.

Consent: The Authorization Server takes the Scopes the Client is requesting and verifies with the Resource Owner whether or not they want to give the Client permission.

Client ID: This ID is used to identify the Client with the Authorization Server.

Client Secret: This is a secret password that only the Client and Authorization Server know. This allows them to securely share information privately behind the scenes.

Authorization Code: A short-lived temporary code the Client gives the Authorization Server in exchange for an Access Token.

Access Token: The key the client will use to communicate with the Resource Server. This is like a badge or key card that gives the Client permission to request data or perform actions with the Resource Server on your behalf.

Note: Sometimes the “Authorization Server” and the “Resource Server” are the same server. However, there are cases where they will not be on the same server or even part of the same organization. For example, the “Authorization Server” might be a third-party service the “Resource Server” trusts.

Now that we have some of the OAuth 2.0 vocabulary handy, let’s revisit the example with a closer look at what’s going on throughout the OAuth flow.

Client ID and Secret

Long before you give “Medium article of the Day” permission to access your contacts, the Client and the Authorization Server established a working relationship. The Authorization Server generated a Client ID and Client Secret sometimes called the App ID and App Secret and gave them to the Client to use for all future OAuth exchanges

As the name implies, the Client Secret must be kept secret so that only the Client and Authorization Server know what it is. This is how the Authorization Server can verify the Client.

Authorization Code Grant Type Flow

NOTE: Here Microsoft IDP is the authorization server. The native application is the “Medium article of the Day” and the web API is the provider having the contact list means a custom scope to restrict access to contacts has been defined in the app registration of web API. An application (“Medium article of the Day”) that requires access to parts of this API can request that a user or admin consent to this.

  • You, the Resource Owner, want to allow “Medium article of the Day” the Client, to access your contacts from Web API so they can send invitations to all your friends.
  • The Client redirects your browser to the Authorization Server and includes with the request the Client ID, Redirect URI, Response Type, and one or more Scopes it needs.
  • The Authorization Server verifies who you are, and if necessary prompts for a login.
  • The Authorization Server presents you with a Consent form based on the Scopes ( contact list in web API) requested by the Client. You grant (or deny) permission.
  • The Authorization Server redirects back to the Client using the Redirect URI along with an Authorization Code.
  • The Client contacts the Authorization Server directly (does not use the Resource Owner’s browser) and securely sends its Client ID, Client Secret, and the Authorization Code.
  • The Authorization Server verifies the data and responds with an Access Token.
  • The Client can now use the Access Token to send requests to the Resource Server (Web API) for your contacts.

Applications that support the auth code flow

Use the auth code flow paired with Proof Key for Code Exchange (PKCE) and OpenID Connect (OIDC) to get access tokens and ID tokens in these types of apps:

Example :

Documentation:

Request an authorization code

The authorization code flow begins with the client directing the user to the /authorize endpoint. In this request, the client requests the openid, offline_access, and https://graph.microsoft.com/mail.read permissions from the user.

Some permissions are admin-restricted, for example, writing data to an organization’s directory by using Directory.ReadWrite.All. If your application requests access to one of these permissions from an organizational user, the user receives an error message that says they're not authorized to consent to your app's permissions. To request access to admin-restricted scopes, you should request them directly from a Global Administrator. For more information, see Admin-restricted permissions.

Unless specified otherwise, there are no default values for optional parameters. There is, however, the default behavior for a request omitting optional parameters. The default behavior is to either sign in the sole current user, show the account picker if there are multiple users, or show the login page if there are no users signed in.

// Line breaks for legibility only

https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=query
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
&state=12345
&code_challenge=YTFjNjI1OWYzMzA3MTI4ZDY2Njg5M2RkNmVjNDE5YmEyZGRhOGYyM2IzNjdmZWFhMTQ1ODg3NDcxY2Nl
&code_challenge_method=S256

At this point, the user is asked to enter their credentials and complete the authentication. The Microsoft identity platform also ensures that the user has consented to the permissions indicated in the scope query parameter. If the user hasn't consented to any of those permissions, it asks the user to consent to the required permissions. For more information, see Permissions and consent in the Microsoft identity platform.

Once the user authenticates and grants consent, the Microsoft identity platform returns a response to your app at the indicated redirect_uri, using the method specified in the response_mode parameter.

Successful response

This example shows a successful response using response_mode=query:

#HTTP
GET http://localhost?
code=AwABAAAAvPM1KaPlrEqdFSBzjqfTGBCmLdgfSTLEMPGYuNHSUYBrq...
&state=12345

Prefer the auth code flow

With the plans for removing third-party cookies from browsers, the implicit grant flow is no longer a suitable authentication method. The silent single sign-on (SSO) features of the implicit flow do not work without third-party cookies, causing applications to break when they attempt to get a new token. It is strongly recommended that all new applications use the authorization code flow that now supports single-page apps in place of the implicit flow.

Client Credential Grant Type Flow

You can use the OAuth 2.0 client credentials grant specified in RFC 6749, sometimes called two-legged OAuth, to access web-hosted resources by using the identity of an application. This type of grant is commonly used for server-to-server interactions that must run in the background, without immediate interaction with a user. These types of applications are often referred to as daemons or service accounts.

The OAuth 2.0 client credentials grant flow permits a web service (confidential client) to use its own credentials, instead of impersonating a user, to authenticate when calling another web service. For a higher level of assurance, the Microsoft identity platform also allows the calling service to authenticate using a certificate or federated credential instead of a shared secret. Because the application’s own credentials are being used, these credentials must be kept safe — never publish that credential in your source code, embed it in web pages, or use it in a widely distributed native application.

In the client credentials flow, permissions are granted directly to the application itself by an administrator. When the app presents a token to a resource, the resource enforces that the app itself has authorization to perform an action since there is no user involved in the authentication

An app typically receives direct authorization to access a resource in one of two ways:

These two methods are the most common in Azure AD and we recommend them for clients and resources that perform the client credentials flow. A resource can also choose to authorize its clients in other ways. Each resource server can choose the method that makes the most sense for its application. I used the same auth flow in my older post

Access token request with a shared secret

curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' \
https://login.microsoftonline.com/f1821f56-8d20-4902-bf65-bc7502e367e7/oauth2/token \
-d 'client_id=<AppID_of_appregistration>' \
-d 'grant_type=client_credentials' \
-d 'client_secret='$client_secret_sp \
-d 'resource=<AppID_of_appregistration>' | jq -r .access_token

Successful Response

{
"token_type": "Bearer",
"expires_in": 3599,
"access_token": "eyJ0eXAi######BVGZNNXBP..."
}

OpenID Connect

OAuth 2.0 is designed only for authorization, for granting access to data and features from one application to another. OpenID Connect (OIDC) is a thin layer that sits on top of OAuth 2.0 that adds login and profile information about the person who is logged in. OpenID Connect has been developed by extending OAuth 2.0. Establishing a login session is often referred to as authentication, and information about the person logged in (i.e. the Resource Owner) is called identity. When an Authorization Server supports OIDC, it is sometimes called an identity provider, since it provides information about the Resource Owner back to the Client.

OpenID Connect is a specification as to how to issue ID tokens. The main part is defined in OpenID Connect Core 1.0.

OpenID Connect enables scenarios where one login can be used across multiple applications, also known as single sign-on (SSO). For example, you want to use Azure AD authentication for your Jenkins application.

As with the OAuth flow, the OpenID Connect Access Token is a value the Client doesn’t understand. As far as the Client is concerned, the Access Token is just a string of gibberish to pass with any request to the Resource Server, and the Resource Server knows if the token is valid. The ID Token, however, is very different.

An ID Token is a JWT

An ID Token is a specifically formatted string of characters known as a JSON Web Token or JWT. JWTs are sometimes pronounced “jots.” A JWT may look like gibberish to you and me, but the Client can extract information embedded in the JWT such as your ID, name, when you logged in, the ID Token expiration, and if anything has tried to tamper with the JWT. The data inside the ID Token are called claims.

With OIDC, there’s also a standard way the Client can request additional identity information from the Authorization Server, such as their email address, using the Access Token.

Protocol flow: Sign-in

This diagram shows the basic OpenID Connect sign-in flow. The steps in the flow are described in more detail in later sections of the article.

Enable ID tokens

The ID token introduced by OpenID Connect is issued by the authorization server (the Microsoft identity platform) when the client application requests one during user authentication. The ID token enables a client application to verify the identity of the user and to get other information (claims) about them.

ID tokens aren’t issued by default for an application registered with the Microsoft identity platform. Enable ID tokens for an app by using one of the following methods.

To enable ID tokens for your app, navigate to the Azure portal and then:

  1. Select Azure Active Directory > App registrations > <your application> > Authentication.
  2. Under Implicit grant and hybrid flows, select the ID tokens (used for implicit and hybrid flows) checkbox.

Requesting an ID token by specifying a response_type of id_token is explained in Send the sign-in request later in the article.

Find your app’s OpenID configuration document URI

You can also find your app’s OpenID configuration document URI in its app registration in the Azure portal.

To find the OIDC configuration document for your app, navigate to the Azure portal and then:

  1. Select Azure Active Directory > App registrations > <your application> > Endpoints.
  2. Locate the URI under OpenID Connect metadata document.

Send the sign-in request

To authenticate a user and request an ID token for use in your application, direct their user-agent to the Microsoft identity platform’s /authorize endpoint. The request is similar to the first leg of the OAuth 2.0 authorization code flow but with these distinctions:

  • Include the openid scope in the scope parameter.
  • Specify id_token or code+id_token in the response_type parameter.
  • Include the nonce parameter.

Example sign-in request (line breaks included only for readability):

https://login.microsoftonline.com/<tenantid>/oauth2/v2.0/authorize?
nonce=ayU3rYdzZU&
response_mode=form_post&
response_type=id_token&
client_id=026a5df7xxxxxxxxxxx8b369464b95&
redirect_uri=https%3A%2F%2Fjenkins-infra.con.local%3A8443%2FsecurityRealm%2FfinishLogin&
scope=openid%20profile%20email

At this point, the user is prompted to enter their credentials and complete the authentication. The Microsoft identity platform verifies that the user has consented to the permissions indicated in the scope query parameter. If the user hasn't consented to any of those permissions, the Microsoft identity platform prompts the user to consent to the required permissions. You can read more about permissions, consent, and multi-tenant apps.

After the user authenticates and grants consent, the Microsoft identity platform returns a response to your app at the indicated redirect URI by using the method specified in the response_mode parameter.

Successful response

A successful response when you use response_mode=form_post is similar to:

POST /myapp/ HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNB...&state=12

Access token acquisition

Many applications need not only to sign in users but also access a protected resource like a web API on behalf of the user. This scenario combines OpenID Connect to get an ID token for authenticating the user and OAuth 2.0 to get an access token for a protected resource.

The full OpenID Connect sign-in and token acquisition flow looks similar to this diagram:

Get an access token for the UserInfo endpoint

In addition to the ID token, the authenticated user’s information is also made available at the OIDC UserInfo endpoint.

To get an access token for the OIDC UserInfo endpoint, modify the sign-in request as described here:

GET https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e // Your app registration's Application (client) ID
&response_type=id_token%20token // Requests both an ID token and access token
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F // Your application's redirect URI (URL-encoded)
&response_mode=form_post // 'form_post' or 'fragment'
&scope=openid+profile+email // 'openid' is required; 'profile' and 'email' provide information in the UserInfo endpoint as they do in an ID token.
&state=12345 // Any value - provided by your app
&nonce=678910 // Any value - provided by your app

You can use the authorization code flow, the device code flow, or a refresh token in place of response_type=token to get an access token for your app.

Successful token response

A successful response from using response_mode=form_post:

POST /myapp/ HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
access_token=eyJ0eXAiOiJKV1QiLCJub25jZSI6I....
&token_type=Bearer
&expires_in=3598
&scope=email+openid+profile
&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI....
&state=12345

Send a sign-out request

To sign out a user, perform both of these operations:

  • Redirect the user’s user-agent to the Microsoft identity platform’s logout URI
  • Clear your app’s cookies or otherwise end the user’s session in your application.

If you fail to perform either operation, the user may remain authenticated and not be prompted to sign-in the next time they user your app.

Redirect the user-agent to the end_session_endpoint as shown in the OpenID Connect configuration document. The end_session_endpoint supports both HTTP GET and POST requests.

If you fail to perform either operation, the user may remain authenticated and not be prompted to sign-in the next time they user your app. https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/auth-oidc

Other Key Points

  • You can review the contents of JWT using https://jwt.io/
  • Both JWS (JSON Web Signature) and JWE (JSON Web Encryption) have two methods of serialization; “JSON” and “Compact”.
  • JWT (JSON Web Token) is either JWS or JWE. In either case, its serialization is “Compact” because the specification defines so.
  • By definition, ID Token is signed. Therefore, its format is either “JWS” or “JWE including JWS”.
  • ID Token never takes the form of “JWS including JWE”. It’s because when ID Token is encrypted, the order must be “signed then encrypted” as the specification requires so.
  • Access Token is not always a JWT.
  • ID Token is always a JWT by definition.
  • Even if an ID Token is used for access control, ID Token is not called Access Token.

References

Please read my other articles as well and share your feedback. If you like the content shared please like, comment, and subscribe for new articles.

--

--

Technical Solutions Developer (GCP). Writes about significant learnings and experiences at work.