Skip to content

Commit

Permalink
[msal-core] Fix response type configuration by basing it mainly on sc…
Browse files Browse the repository at this point in the history
…opes (#2022)

* Add initial respnse type tests to UserAgentApplication

* Enable empty scopes array for login API calls

* Enable getting an id token from acquireToken APIs if only OIDC scopes are configured or clientId is the only scope

* Add new getTokenType logic for response type configuration for acquireToken APIs as well as remove clientId as single scope restriction from msal-core.

* Finish passing tests for response_type behavior for acquireToken APIs

* Refactor getTokenType for code readability and correct class responsibility

* Refactor clientId translation and OIDC scope addition into ScopeSet.normalize

* Fix bug where clientId was being spliced even when it wasn't the only scope

* Update documentation on scopes behavior

* Remove unnecessary imports from UrlUtils

* Add response-types.md documentation and update the links on scopes.md

* Add missing OIDC spec links to response types doc

* Response types doc update to address feedback

* Refactor clientId translation to translate in request validation, then replace all instances of adding clientId as single scope when scopes are null or empty to add OIDC scopes instead of clientId

* Add warning that document only applies to msal-core to response types doc

* Change default request signature to use OIDC scopes rather than clientId to avoid confusion in the future

* Refactor onlyContainsOidcScopes method to skip unnecessary subtraction and prevent it from returning true when the scopes array is empty

* Simplify ternary operator to a logical comparison in ScopeSet.onlyContainsClientId

* Add TLDRs to new docs on scopes and response types

* Fix typos in response-types doc

* Add custom scope example to scopes doc

* Fix typos and style issues in new docs

* Add closing bold asterisks in response-types doc

* Change files

* Update beachball change file with PR in description
  • Loading branch information
Héctor Morales committed Aug 17, 2020
1 parent 5f10555 commit 603c101
Show file tree
Hide file tree
Showing 15 changed files with 937 additions and 187 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "minor",
"comment": "Enables idToken acquisition in acquireToken API calls through the use of OIDC scopes by redefining the way response_type is determined. (PR #2022)",
"packageName": "msal",
"email": "hemoral@microsoft.com",
"dependentChangeType": "patch",
"date": "2020-08-10T18:48:42.205Z"
}
85 changes: 85 additions & 0 deletions lib/msal-core/docs/response-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Response Types

> :warning: This document only applies to `msal@1.x` which implements the Implicit Flow Grant type. For the Authorization Code Flow Grant type, please use the [msal-browser](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser) library.
## Quick Reference

> This section provides a summary of the main points this document addresses, without getting into any details. If you need more clarity or information about the functionality and behavior of Response Types in `msal@1.x`, please read the rest of this document.
The key takeaways of the way `msal@1.x` determines and handles `Response Types` are:

1. `loginRedirect`, `loginPopup` and `ssoSilent` will always return ID tokens and have a `response_type` of `id_token`.
2. `acquireToken` requests will always return an ID token if `openid` or `profile` are included in the request scopes.
3. `acquireToken` requests will always return an access token if a `resource scope` is requested.

If you're interested in learning more about the reasoning and implications around the way response types are determined and what they are used for, please read the rest of this document.

## Definition and Types
The `msal@1.x` library, in compliance of both the OAuth 2.0 protocol specification as well as the OpenID Connect specification, defines and supports three different `response types`:

* token
* id_token
* id_token token

The **`msal@1.x` library does not support the `code` response type because it does not implement the Authorization Code grant. If you are looking to implement the Authorization Code grant type, consider the [msal-browser](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser) library.**

The listed response types are possible values for the `response_type` parameter in OAuth 2.0 HTTP requests. Assuming a valid request, this parameter determines what kind of token is sent back by the Secure Token Service (STS) that `msal@1.x` requests access and ID tokens from.

| Response Type | Specification that defines it | Expected token type from successful request | Action |
| ------------- | ----------------------------- | ------------------------------------------- | ------ |
| `token` |[OAuth 2.0](https://tools.ietf.org/html/rfc6749#section-3.1.1) | Access Token | Authorization |
| `id_token`| [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html#Authentication) | ID Token | Authentication |
|`id_token token`| [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html#Authentication) | Access Token and ID token | Authorization & Authentication |

**Note: Given that `msal@1.x` uses the OAuth 2.0 Implicit Flow exclusively, which leverages URL fragments for token reception, it is important to be mindful of URL length limitations. Browsers like IE impose restrictions on the length of URLs, so getting both an access token and ID token in the same URL may cause unexpected or incorrect behavior.**

## Response Type configuration and behavior

The `response_type` attribute presented above cannot be configured directly. However, it is important to understand the way `msal@1.x` determines which response type is set and, therefore, what kind of token the developer can expect for each scenario. The factors that come into consideration when setting the request's `response_type` parameter are the following:

1. The `msal@1.x` API called
2. Whether the account passed into the request configuration matches the account in the MSAL cache
3. The contents of the `scopes` array in the Authorization Request Configuration object. For more information on `scopes` configuration, please consult the [Scopes](/docs/scopes.md) document.

**Important note: Login APIs will always set `response_type=id_token`, given that they are designed to perform user login (authentication).**

Login APIs include:

* loginRedirect
* loginPopup
* ssoSilent

In other words, whenever you call `loginRedirect` or `loginPopup` to sign a user in, you should expect to receive an ID token if the request is successful.

The following section contains quick reference tables for both `login` and `acquireToken` APIs that accurately map the request configuration to the resulting response type.

## Quick reference tables

### Login APIs

Applies to: `loginRedirect`, `loginPopup`, `ssoSilent`

| Input scopes | Account passed in | Response Type Result |
| ----------------- | ------------ | -------------------- |
| Any case | Any case | `id_token`|

### Acquire Token APIs

Applies to: `acquireTokenRedirect`, `acquireTokenPopup`, `acquireTokenSilent`

* *OIDC scopes: any combination of `openid` and/or `profile`*
* *OIDC scopes only: Same as OIDC scopes but with no other scopes in the array*

| Input scopes | Account passed in | Response Type Result |
| ----------------- | ------------ | -------------------- |
| ClientId as only scope | Any case | `id_token`|
| OIDC scopes only | Any case | `id_token`|
| ClientId with OIDC scopes | Any case | `id_token token` |
| Resource scope(s) with OIDC scopes (technically the same as above) | Any case | `id_token token` |
| Resource scope(s) only | Matches cached account object | `token` |
| Resource scope(s) and ClientId | Matches cached account object | `token` |
| Resource scope(s) only | Doesn't match cached account object | `id_token token` |

**Note: As seen in the table above, when ClientId is not the only scope, it is assumed to be a resource scope with no special behavior.**


105 changes: 105 additions & 0 deletions lib/msal-core/docs/scopes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Scope Configuration and Behavior

## Contents
* [Quick Reference](#quick-reference)
* [Scopes](#scopes)
* [Scope Functions](#scope-functions)
* [Scope Types](#scope-types)
* [Resource scopes for Authorization](#resource-scopes-for-authorization)
* [OpenID Connect Scopes for Authentication](#openid-connect-scopes-for-authentication)
* [Scopes Behavior](#scopes-behavior)
* [Default Scopes in Authorization Requests](#default-scopes-on-authorization-requests)
* [Special OIDC Scopes behavior cases](#special-oidc-scopes-behavior-cases)

## Quick Reference

> This section provides a summary of the main points this document addresses, without getting into any details. If you need more clarity or information about the functionality and behavior of Scopes in `msal@1.x`, please read the rest of this document.
The key takeaways of the way `msal@1.x` handles and uses scopes are:

1. The `msal@1.x` library will always append `openid` and `profile` as scopes in every outgoing request.
2. Setting the value of the application's ClientId as the only scope will result in it being replaced by `openid` and `profile` and an ID Token being returned

If you're interested in learning more about the reasoning and implications around these two specific behaviors, please read on.



## Scopes

Microsoft identity platform access tokens, which `msal@1.x` acquires in compliance with the OAuth 2.0 protocol specification, are issued to applications as proof of authorization on behalf of a user for a certain resource. The issuing of these tokens is not only specific to an `audience`, or application, but also specific to a set of `scopes` or permissions.

### Scope Functions

#### Function of scopes in OAuth 2.0

The main function of the `scopes` configuration, per the [OAuth 2.0 Access Token Scope Reference](https://tools.ietf.org/html/rfc6749#section-3.3), is to determine the permissions for which an application requests `authorization` on behalf of the user. Said function is both supported and covered by `msal@1.x` and the Microsoft identity platform in general. For more information on the regular function of authorization scopes, please consult the official [Microsoft identity platform documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent).

#### Special use of scopes in msal@1.x

In addition to the global concept and use of `scopes`, it is important to understand that `msal@1.x` gives scopes a special use that adds to the importance of their configuration. In short, `msal@1.x` allows developers to leverage certain scopes in order to determine the `response_type` for the final request. For more information on the way the scopes configuration determines the `response_type` parameter, please refer to the [Response Types Document](/docs/response-types.md).



## Scope Types

As far as `msal@1.x` is concerned, there are two main types of `scopes` that can be configured in a token request.

### Resource scopes for Authorization

`Resource scopes` are the main type of access token `scopes` that `msal@1.x` deals with. These are the `scopes` that represent permissions for specific actions against a particular resource. In other words, these `scopes` determine what actions and resources the requesting application is `authorized` to access on behalf of the user. The following are some examples of the `resource scopes` that the [Microsoft Graph](https://docs.microsoft.com/en-us/graph/overview) service can authorize an application for given the user's consent:

* `User.Read`: Authorizes the application to read a user's account details.
* `Mail.Read`: Authorizes the application to read a user's e-mails.

Including resource scopes in the configuration for a token request doesn't always mean that the response will include an **access token** for said scopes. In the specific case of `msal@1.x`'s `login` APIs (`loginRedirect`, `loginPopup`), adding resource scopes may allow the user to **constent** to said scopes ahead of time, but successful `login` API calls always result in an **ID Token, not an access token**, being returned.

### OpenID Connect Scopes for Authentication

`OpenID Connect (OIDC) scopes` are a specific set of scopes that can be added to requests when `authenticating` a user. In most cases, `OIDC scopes` are added to configure the claims included in an ID Token ([OIDC Reference](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) / [Microsoft Docs](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes)). Some services, such as the Secure Token Service that `msal@1.x` acquires tokens from, also use OIDC scopes in their internal logic. For this reason, it is important to understand and pay attention to the special behavior `msal@1.x` has around OIDC scopes (described in the [next section](#default-scopes-on-authorization-requests)).

The OIDC scopes that `msal@1.x` pays particular attention to are outlined in the table below.

| OIDC Scope | Required by OIDC Specification | Function | OIDC Reference | Microsoft Docs |
| ---------- | ------------------------------ | -------- | -------------- | -------------- |
| `openid`| Mandatory | Main `OIDC scope` that indicates a request for `authentication` [per the OIDC specification](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). In AAD requests, this is the scope that prompts the "Sign in" permission that a user can consent to. | [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)| [Permissions and consent in the Microsoft identity platform endpoint](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#send-the-sign-in-request)|
|`profile`| Optional | Used for ID Token `claims` configuration. Adds the end-user's default profile information as a claim to the ID token returned | [Requesting Claims using Scopes Values](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) | [OpenID Permissions](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent)|


## Scopes Behavior

### Default Scopes on Authorization Requests

Understanding how `OIDC scopes` configure the claims included in an authentication response's ID Token is important when using `msal@1.x` to acquire said ID Tokens. However, there is an important note to be made on how the `openid` and `profile` scopes are added by `msal@1.x` to all server requests by default that does not directly relate to the OpenID Connect specification.

Like previously mentioned, the Secure Token Service that `msal@1.x` requests access and ID tokens from also makes use of the `openid` and `profile` scopes. Specifically, the STS expects these two scopes in order to configure and provide the `client_info` parameter in authorization and authentication responses. The `msal@1.x` library depends on the contents of `client_info` in order to successfully cache tokens and, therefore, provide silent token acquisition as a feature.

**For this reason, whether or not the developer adds the `openid` or `profile` scopes to their request configuration, `msal@1.x` will make sure they are included before sending the request to the STS.**

### Special OIDC Scopes behavior cases

The following is a list of practical implications and examples of the default scope behavior described in the previous section.

- If the scopes array does not include either `openid` or `profile`, whichever is missing (could be both) will be added to the scopes array by default before the request is sent out.

Examples:

```js
{ scopes: ['User.Read'] } // becomes { scopes: ['User.Read', 'openid', 'profile'] } before the request is sent

{ scopes: ['User.Read', 'openid'] } // becomes { scopes ['User.Read', 'openid', 'profile']} before the request is sent

{ scopes: ['User.Read', 'profile'] } // becomes { scopes ['User.Read', 'profile', 'openid']} before the request is sent

{ scopes: ['http://contoso.com/scope'] } // becomes { scopes ['http://contoso.com/scope', 'openid', 'profile'] }
```
- ClientId is removed from the scopes array when it is the only scope in the configuration. If it is not the only scope, it is treated as a resource scope and will be sent in the final server request.

Examples:

```js
{ scopes: ['YOUR_CLIENT_ID'] } // becomes { scopes: ['openid', 'profile'] } before the request is sent (ClientId is spliced out)
{ scopes: ['YOUR_CLIENT_ID', 'User.Read'] } // becomes { scopes ['YOUR_CLIENT_ID', 'User.Read', 'openid', 'profile']} before the request is sent (ClientId is treated as resource scope and therefore not spliced out)
{ scopes: ['YOUR_CLIENT_ID', 'openid'] } // becomes { scopes ['YOUR_CLIENT_ID', 'openid', 'profile']} before the request is sent
```
Loading

0 comments on commit 603c101

Please sign in to comment.