Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Identity] Samples aligned with other languages (v1 compatible) #15031

Merged
25 commits merged into from
Jun 24, 2021
Merged
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a4bb2c2
WIP
sadasant Apr 24, 2021
2a465ac
wip
sadasant Apr 26, 2021
ba60cf2
good for a review
sadasant Apr 27, 2021
e091f44
removing en-us
sadasant Apr 27, 2021
42692d1
Merge remote-tracking branch 'Azure/master' into identity/fix14435-sa…
sadasant May 7, 2021
611ff8a
feedback by Maor
sadasant May 7, 2021
d54e2db
Removing the last en-us
sadasant May 7, 2021
4031fa5
addressing the core-auth install instructions
sadasant Jun 10, 2021
9b56a1b
removed unnecessary async
sadasant Jun 10, 2021
00dd868
feedback, but I want to re-read tomorrow
sadasant Jun 11, 2021
6eb23d2
small change
sadasant Jun 11, 2021
bb71774
Merge remote-tracking branch 'Azure/master' into identity/fix14435-sa…
sadasant Jun 11, 2021
b3ae522
Update sdk/identity/identity/samples/AzureIdentityExamples.md
sadasant Jun 11, 2021
eab4538
small changes
sadasant Jun 11, 2021
545995e
some improvements
sadasant Jun 11, 2021
4572bbb
some improvements
sadasant Jun 11, 2021
81601e5
many small tweaks
sadasant Jun 11, 2021
c5b21fe
Apply suggestions from code review
sadasant Jun 16, 2021
371a5da
Apply suggestions from code review
sadasant Jun 18, 2021
69c56ab
prerequisites sections
sadasant Jun 18, 2021
0f2543a
Apply suggestions from code review
sadasant Jun 21, 2021
69db4a2
Update sdk/identity/identity/samples/AzureIdentityExamples.md
sadasant Jun 23, 2021
2f44003
Merge branch 'main' into identity/fix14435-samples
sadasant Jun 24, 2021
99ba628
master -> main
sadasant Jun 24, 2021
0927bd8
MSAL doesnt have main yet
sadasant Jun 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 275 additions & 4 deletions sdk/identity/identity/samples/AzureIdentityExamples.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Table of contents

- [Introduction](#introduction)
- [Requirements](#requirements)
- [Authenticating client side browser applications](#authenticating-client-side-browser-applications)
- [Authenticating server side applications](#authenticating-server-side-applications)
- [Authenticating User Accounts](#authenticating-user-accounts)
Expand All @@ -9,11 +10,32 @@
- [Authenticating Azure Hosted Applications](#authenticating-azure-hosted-applications)
- [Chaining credentials](#chaining-credentials)
- [Authenticating With Azure Stack using Azure Identity](#authenticating-with-azure-stack-using-azure-identity)
- [Authenticating With Azure Stack using Azure Identity](#authenticating-with-azure-stack-using-azure-identity)
- [Advanced Examples](#advanced-examples)
- [Custom Credentials](#custom-credentials)
- [Authenticating with a pre-fetched access token](#authenticating-with-a-pre-fetched-access-token).
- [Authenticating with MSAL directly](#authenticating-with-msal-directly).
- [Authenticating with the Confidential Client](#authenticating-with-the-confidential-client).
- [Authenticating with the On Behalf Flow](#authenticating-with-the-on-behalf-flow).
- [Authenticating with Key Vault Certificates](#authenticating-with-key-vault-certificates)
- [Rolling Certificates](#rolling-certificates)

## Introduction

Authenticating your application, users, and principals is an integral part of working with the Azure Client Libraries. The Azure Identity library provides multiple ways to authenticate, each with a flexible configuration that covers most scenarios. In this document we will go over some of these scenarios and provide small examples that can be used as a starting point for your needs.

## Requirements

Many of the code samples in this document will require you to have installed the following packages:

- [@azure/identity](https://www.npmjs.com/package/@azure/identity).
- [@azure/core-http](https://www.npmjs.com/package/@azure/core-http).
- [@azure/keyvault-secrets](https://www.npmjs.com/package/@azure/keyvault-secrets).

You can install them with: `npm install @azure/identity @azure/core-http @azure/keyvault-secrets`.

In the cases in which other packages are necessary, we will mention which packages and how to install them.

## Authenticating client side browser applications

For client side applications running in the browser, the `InteractiveBrowserCredential` provides the simplest user authentication experience and is the only credential type that we support in the browser. To get started, you will want to configure an AAD application for interactive browser authentication. Please refer to the [Single-page application: App registration guide](https://docs.microsoft.com/azure/active-directory/develop/scenario-spa-app-registration) for additional information on how to configure your app registration for the browser.
Expand Down Expand Up @@ -239,7 +261,7 @@ function withDeviceCodeCredential() {

This example demonstrates authenticating the `SecretClient` from the [@azure/keyvault-secrets][secrets_client_library] client library using the `UsernamePasswordCredential`. The user must **not** have Multi-factor auth turned on.

Apart from user name and password, this credential requires you to know the tenant Id and client Id. To get the client Id, first [register your application][quickstart-register-app]
Apart from user name and password, this credential requires you to know the tenant Id and client Id. To get the client Id, first [register your application][quickstart-register-app].

```ts
/**
Expand All @@ -266,7 +288,7 @@ Next, prompt the user to login at the URL documented at [Microsoft identity plat

Then create an API at the redirect URL with the following code to access the Key Vault service.

For a complete example using the authorization code flow in Electron please refer to [our electron sample](https://github.com/Azure/azure-sdk-for-js/blob/master/samples/frameworks/electron/ts/src/authProvider.ts)
For a complete example using the authorization code flow in Electron please refer to [our electron sample](https://github.com/Azure/azure-sdk-for-js/blob/master/samples/frameworks/electron/ts/src/authProvider.ts).

```ts
/**
Expand Down Expand Up @@ -373,7 +395,7 @@ function withUserManagedIdentityCredential() {

## Chaining credentials

The `ChainedTokenCredential` class provides the ability to link together multiple credential instances to be tried sequentially when authenticating. The following example demonstrates creating a credential which will attempt to authenticate a `SecretClient` from the [@azure/keyvault-secrerts][secrets_client_library] using managed identity, and fall back to certificate authentication if a managed identity is unavailable in the current environment.
The `ChainedTokenCredential` class provides the ability to link together multiple credential instances to be tried sequentially when authenticating. The following example demonstrates creating a credential which will attempt to authenticate a `SecretClient` from the [@azure/keyvault-secrets][secrets_client_library] using managed identity, and fall back to certificate authentication if a managed identity is unavailable in the current environment.

```ts
function withChainedTokenCredential() {
Expand Down Expand Up @@ -417,7 +439,7 @@ else, if the Identity provider of your Azure Stack is Active Directory Federatio
The following example demonstrates authenticating a `SecretClient` from the [@azure/keyvault-secrets][secrets_client_library] against an Azure Key Vault hosted in Azure Stack.

```ts
function main() {
async function main() {
const credential = new ClientSecretCredential(
"<YOUR_TENANT_ID>",
"<YOUR_CLIENT_ID>",
Expand All @@ -431,6 +453,255 @@ function main() {
}
```

## Advanced Examples

With all the information given so far, we can explore some advanced usages of the `@azure/identity` credentials.

### Custom Credentials

The `@azure/identity` library covers a broad range of Azure Active Directory authentication scenarios. However, it's possible the credential implementations provided might not meet the specific needs your application, or an application might want to avoid taking a dependency on the `@azure/identity` library.

In this section we'll examine some example cases in which it might make sense to write a credential on your own.

### Authenticating with a pre-fetched access token

Our package `@azure/core-http` exports a `TokenCredential` interface which is used by the `@azure/identity` package to define a common public API for all of the Identity credentials that we offer.

The `@azure/identity` library does not contain a `TokenCredential` implementation which can be constructed directly with an `AccessToken`. This is intentionally omitted as a main line scenario as access tokens expire frequently and have constrained usage. However, we understand there may be some scenarios where authenticating a service client with a pre-fetched token is necessary.

In this example, `StaticTokenCredential` implements the `TokenCredential` abstraction. It takes a pre-fetched access token in its constructor as an `AccessToken` (defined on `@azure/core-http`), and simply returns that from its implementation of `getToken()`.

```ts
import { TokenCredential, AccessToken } from '@azure/core-http';

class StaticTokenCredential implements TokenCredential {
constructor(private accessToken: AccessToken) {
}
async getToken(): Promise<AccessToken> {
return this.accessToken;
}
}
```

Once the application has defined this credential, it can be used to authenticate Azure SDK clients. The following example shows how an application already using some other mechanism for acquiring tokens (in this case the hypothetical method `getTokenForScope()`) could use the `StaticTokenCredential` to authenticate a `SecretClient` from `@azure/keyvault-secrets`.

```ts
import { SecretClient } from "@azure/keyvault-secrets";

async function main() {
const token = getTokenForScope("https://vault.azure.net/.default");

const credential = new StaticTokenCredential(token);

const client = new SecretClient("https://myvault.vault.azure.net/", credential);
}
```

It should be noted when using this custom credential type, it is the responsibility of the caller to ensure that the token is valid, and contains the correct claims needed to authenticate calls from the particular service client. For instance in the above case the token must have the scope "https://vault.azure.net/.default" to authorize calls to Azure Blob Storage.

### Authenticating with MSAL Directly

Some applications already use the MSAL library's `ConfidentialClientApplication` or `PublicClientApplication` to authenticate portions of their application. In these cases, the application might want to use the same to authenticate Azure SDK clients, to take advantage of the token caching the MSAL client application is doing, and to prevent unnecessary authentication calls.

#### Authenticating with the MSAL Confidential Client

In this example the `ConfidentialClientApplicationCredential` is constructed with an instance of `ConfidentialClientApplication` it then implements `getToken()` using the `acquireTokenByClientCredential()` method to acquire a token.

Make sure to install `msal-node` with: `npm install @azure/msal-node`. Then you'll be able to write code similar to the following:

```ts
import { TokenCredential, AccessToken } from "@azure/core-http";
import * as msalNode from "@azure/msal-node";

class ConfidentialClientCredential implements TokenCredential {
constructor(private confidentialApp: msalNode.ConfidentialClientApplication) {
}
async getToken(scopes: string | string[]): Promise<AccessToken> {
const result = await this.confidentialApp.acquireTokenByClientCredential({ scopes: Array.isArray(scopes) ? scopes : [scopes] });
return {
token: result.accessToken,
expiresOnTimestamp: result.expiresOn.getTime()
}
}
}
```

Users could then use the `ConfidentialClientApplicationCredential` to authenticate a `SecretClient` from `@azure/keyvault-secrets` with an MSAL `ConfidentialClientApplication`:

```ts
import { SecretClient } from "@azure/keyvault-secrets";
import * as msalNode from "@azure/msal-node";

async function main() {
const confidentialClient = new msalNode.ConfidentialClientApplication({
// MSAL Configuration
});

const client = new SecretClient("https://myvault.vault.azure.net/", new ConfidentialClientCredential(confidentialClient));
}
```

#### Authenticating with the On Behalf Of Flow

Currently the `@azure/identity` library doesn't provide a credential type for clients which need to authenticate via the On Behalf Of flow. While future support for this is planned, users currently requiring this will have to implement their own `TokenCredential` class.

In this example the `OnBehalfOfCredential` accepts a client Id, client secret, and a user's access token. It then creates an instance of `ConfidentialClientApplication` from MSAL to obtain an OBO token which can be used to authenticate client requests.

```ts
import { TokenCredential, AccessToken } from "@azure/core-http";
import * as msalNode from "@azure/msal-node";

class OnBehalfOfCredential implements TokenCredential {
private confidentialApp: msalNode.ConfidentialClientApplication;

constructor(private clientId: string, private clientSecret: string, private userAccessToken: string) {
this.confidentialApp = new msalNode.ConfidentialClientApplication({
auth: {
clientId,
clientSecret
}
});
}
async getToken(scopes: string | string[]): Promise<AccessToken> {
const result = await this.confidentialApp.acquireTokenOnBehalfOf({
scopes: Array.isArray(scopes) ? scopes : [scopes],
oboAssertion: this.userAccessToken
});
return {
token: result.accessToken,
expiresOnTimestamp: result.expiresOn.getTime()
};
}
}
```

The following example shows an how the `OnBehalfOfCredential` could be used to authenticate a `SecretClient`.

```ts
import { SecretClient } from "@azure/keyvault-secrets";

async function main() {
const oboCredential = new OnBehalfOfCredential(clientId, clientSecret, userAccessToken);

const client = new SecretClient("https://myvault.vault.azure.net/", oboCredential);
}
```
### Authenticating with Key Vault Certificates

Azure Key Vault allows users to create certificates that can be used to authenticate Azure SDK clients.

Certificates can be created through different means. You may follow any of these approaches:

- [Quickstart: Set and retrieve a certificate from Azure Key Vault using the Azure portal](https://docs.microsoft.com/azure/key-vault/certificates/quick-create-portal).
- [Quickstart: Azure Key Vault certificate client library for JavaScript (version 4)](https://docs.microsoft.com/azure/key-vault/certificates/quick-create-node).

Once you have a certificate, you may export the certificate with the Azure CLI following the steps at: [Export certificates from Azure Key Vault](https://docs.microsoft.com/azure/key-vault/certificates/how-to-export-certificate?tabs=azure-cli).

You can also export your certificate through the portal by going to your Key Vault, going to a specific certificate, then downloading the certificate in PFX/PEM format.

Once you have a Key Vault certificate downloaded, go to Azure Active Directory, find the Enterprise app you want to authenticate with, go to `Certificates & secrets` and then upload the certificate.

After that, you'll be able to authenticate by pointing the `@azure/identity`'s `ClientCertificateCredential` to the path where your PEM certificate is, as follows:

```ts
const credential = new ClientCertificateCredential(
"<your-tenant-id>",
"<your-client-id>",
"<the-path-to-your-certificate-in-PEM-format>"
);
```

### Rolling Certificates

Long running applications may have the need to roll certificates during process execution. Certificate rotation is not currently supported by the `ClientCertificateCredential` which treats the certificate used to construct the credential as immutable. This means that any clients constructed with an `ClientCertificateCredential` using a particular cert would fail to authenticate requests after that cert has been rolled and the original is no longer valid.

However, if an application wants to roll this certificate without creating new service clients, it can accomplish this by creating its own `TokenCredential` implementation which wraps the `ClientCertificateCredential`. The implementation of this custom credential `TokenCredential` would somewhat depend on how the application handles certificate rotation.

### Explicit rotation

If the application gets notified of certificate rotations and it can directly respond, it might choose to wrap the `ClientCertificateCredential` in a custom credential which provides a means for rotating the certificate.

```ts
import { TokenCredential, GetTokenOptions, AccessToken } from '@azure/core-http';
import { ClientCertificateCredential } from "@azure/identity";

class RotatableCertificateCredential implements TokenCredential {
private readonly tenantId: string;
private readonly clientId: string;
private credential: ClientCertificateCredential;

constructor(tenantId: string, clientId: string, PEMCertificatePath: string) {
this.tenantId = tenantId;
this.clientId = clientId;
this.credential = new ClientCertificateCredential(tenantId, clientId, PEMCertificatePath);
}

async getToken(scopes: string | string[], options?: GetTokenOptions): Promise<AccessToken> {
return this.credential.getToken(scopes, options);
}

rotateCertificate(PEMCertificatePath: string) {
this.credential = new ClientCertificateCredential(this.tenantId, this.clientId, PEMCertificatePath);
}
}
```

The above example shows a custom credential type `RotatableCertificateCredential` which provides a `rotateCertificate`. The implementation internally relies on an instance of `ClientCertificateCredential`, and `rotateCertificate` simply replaces this instance with a new one using the new certificate path.

### Implicit rotation

Some applications might want to respond to certificate rotations which are external to the application, for instance a separate process rotates the certificate by updating it on disk. Here the application create a custom credential which checks for certificate updates when tokens are requested.

```ts
import { TokenCredential, GetTokenOptions, AccessToken } from '@azure/core-http';
import { ClientCertificateCredential } from "@azure/identity";
import * as fs from "fs";

class RotatingCertificateCredential implements TokenCredential {
private readonly tenantId: string;
private readonly clientId: string;
private readonly certificatePath: string;
private promise: Promise<void> | null = null;
private credential: ClientCertificateCredential;
private lastModified: number = 0;

constructor(tenantId: string, clientId: string, certificatePath: string) {
this.tenantId = tenantId;
this.clientId = clientId;
this.certificatePath = certificatePath;

this.refreshCertificate();
}

async getToken(scopes: string | string[], options?: GetTokenOptions): Promise<AccessToken> {
await this.refreshCertificate();

return this.credential.getToken(scopes, options);
}

refreshCertificate(): Promise<void> {
if (this.promise) {
return this.promise;
}
return new Promise((resolve, reject) => {
fs.stat(this.certificatePath, (err, stats) => {
if (err) {
reject(err);
} else {
if (this.lastModified < stats.mtime.getTime()) {
this.lastModified = stats.mtime.getTime();
this.credential = new ClientCertificateCredential(this.tenantId, this.clientId, this.certificatePath);
this.promise = null;
}
}
})
});
}
}
```

In this example the custom credential type `RotatingCertificateCredential` again uses a `ClientCertificateCredential` instance to retrieve tokens. However, in this case it will attempt to refresh the certificate prior to obtaining the token. The method `RefreshCertificate` will query to see if the certificate has changed, and if so it will replace `this.credential` with a new instance of the certificate credential using the same certificate path.

<!-- LINKS -->

[azure_cli]: https://docs.microsoft.com/cli/azure
Expand Down