Skip to content

Commit 37548c8

Browse files
pbr1111Pol Bonastre
authored andcommitted
feat(storage-azure): add support for user-managed identities
1 parent 95dbaed commit 37548c8

5 files changed

Lines changed: 192 additions & 82 deletions

File tree

packages/storage-azure/README.md

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,47 @@ export default buildConfig({
4040
})
4141
```
4242

43+
### Authentication methods
44+
45+
Azure Blob Storage supports different authentication methods. Choose the one that best fits your deployment environment.
46+
47+
#### Connection string
48+
49+
Use Azure Storage connection string for straightforward authentication:
50+
51+
```ts
52+
azureStorage({
53+
baseURL: process.env.AZURE_STORAGE_ACCOUNT_BASEURL,
54+
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
55+
containerName: process.env.AZURE_STORAGE_CONTAINER_NAME,
56+
})
57+
```
58+
59+
#### Azure credentials
60+
61+
Use Azure Identity credentials for enhanced security with managed identities:
62+
63+
```ts
64+
import { DefaultAzureCredential } from '@azure/identity'
65+
66+
azureStorage({
67+
baseURL: process.env.AZURE_STORAGE_ACCOUNT_BASEURL,
68+
credentials: new DefaultAzureCredential(),
69+
containerName: process.env.AZURE_STORAGE_CONTAINER_NAME,
70+
})
71+
```
72+
73+
**Note:** When using User Managed Identity, set the `AZURE_CLIENT_ID` environment variable with your managed identity's client ID.
74+
4375
### Configuration Options
4476

45-
| Option | Description | Default |
46-
| ---------------------- | ------------------------------------------------------------------------ | ------- |
47-
| `enabled` | Whether or not to enable the plugin | `true` |
48-
| `collections` | Collections to apply the Azure Blob adapter to | |
49-
| `allowContainerCreate` | Whether or not to allow the container to be created if it does not exist | `false` |
50-
| `baseURL` | Base URL for the Azure Blob storage account | |
51-
| `connectionString` | Azure Blob storage connection string | |
52-
| `containerName` | Azure Blob storage container name | |
53-
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
77+
| Option | Description | Default |
78+
| ---------------------- | -------------------------------------------------------------------------------------------------------- | ------- |
79+
| `enabled` | Whether or not to enable the plugin | `true` |
80+
| `collections` | Collections to apply the Azure Blob adapter to | |
81+
| `allowContainerCreate` | Whether or not to allow the container to be created if it does not exist | `false` |
82+
| `baseURL` | Base URL for the Azure Blob storage account (required when using credentials) | |
83+
| `connectionString` | Azure Blob storage connection string (alternative to credentials + baseURL) | |
84+
| `credentials` | Azure TokenCredential for authentication (e.g., DefaultAzureCredential). Alternative to connectionString | |
85+
| `containerName` | Azure Blob storage container name | |
86+
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |

packages/storage-azure/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
},
4949
"dependencies": {
5050
"@azure/abort-controller": "^1.1.0",
51+
"@azure/identity": "^4.11.1",
5152
"@azure/storage-blob": "^12.11.0",
5253
"@payloadcms/plugin-cloud-storage": "workspace:*",
5354
"range-parser": "^1.2.1"

packages/storage-azure/src/index.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { TokenCredential } from '@azure/identity'
12
import type { ContainerClient } from '@azure/storage-blob'
23
import type {
34
Adapter,
@@ -42,15 +43,21 @@ export type AzureStorageOptions = {
4243
collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>
4344

4445
/**
45-
* Azure Blob storage connection string
46+
* Azure Blob storage connection string (when using connection string authentication)
4647
*/
47-
connectionString: string
48+
connectionString?: string
4849

4950
/**
5051
* Azure Blob storage container name
5152
*/
5253
containerName: string
5354

55+
/**
56+
* Azure Blob storage credentials (alternative to connectionString)
57+
* Use this for UMI (User Managed Identity) or other Azure identity-based authentication
58+
*/
59+
credentials?: TokenCredential
60+
5461
/**
5562
* Whether or not to enable the plugin
5663
*
@@ -66,8 +73,10 @@ export const azureStorage: AzureStoragePlugin =
6673
(incomingConfig: Config): Config => {
6774
const getStorageClient = () =>
6875
getStorageClientFunc({
76+
baseURL: azureStorageOptions.baseURL,
6977
connectionString: azureStorageOptions.connectionString,
7078
containerName: azureStorageOptions.containerName,
79+
credentials: azureStorageOptions.credentials,
7180
})
7281

7382
const isPluginDisabled = azureStorageOptions.enabled === false
@@ -140,10 +149,16 @@ function azureStorageInternal(
140149
clientUploads,
141150
connectionString,
142151
containerName,
152+
credentials,
143153
}: AzureStorageOptions,
144154
): Adapter {
145155
const createContainerIfNotExists = () => {
146-
void getStorageClientFunc({ connectionString, containerName }).createIfNotExists({
156+
void getStorageClientFunc({
157+
baseURL,
158+
connectionString,
159+
containerName,
160+
credentials,
161+
}).createIfNotExists({
147162
access: 'blob',
148163
})
149164
}

packages/storage-azure/src/utils/getStorageClient.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,29 @@ import type { AzureStorageOptions } from '../index.js'
77
let storageClient: ContainerClient | null = null
88

99
export function getStorageClient(
10-
options: Pick<AzureStorageOptions, 'connectionString' | 'containerName'>,
10+
options: Pick<
11+
AzureStorageOptions,
12+
'baseURL' | 'connectionString' | 'containerName' | 'credentials'
13+
>,
1114
): ContainerClient {
1215
if (storageClient) {
1316
return storageClient
1417
}
1518

16-
const { connectionString, containerName } = options
19+
const { baseURL, connectionString, containerName, credentials } = options
20+
21+
let blobServiceClient: BlobServiceClient
22+
23+
if (credentials) {
24+
blobServiceClient = new BlobServiceClient(baseURL, credentials)
25+
} else if (connectionString) {
26+
blobServiceClient = BlobServiceClient.fromConnectionString(connectionString)
27+
} else {
28+
throw new Error(
29+
'Azure Storage: Either provide a connectionString or credentials for authentication',
30+
)
31+
}
1732

18-
const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString)
1933
storageClient = blobServiceClient.getContainerClient(containerName)
2034
return storageClient
2135
}

0 commit comments

Comments
 (0)