Skip to content

Commit 3e9be59

Browse files
committed
Updated resource provisioning script with creation and assignment of Oauth roles associated with app registrations governing our function app and static website
1 parent 0eab1b7 commit 3e9be59

18 files changed

+315
-32
lines changed

.github/workflows/deploy_to_azure.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ jobs:
8989
- name: Build React app
9090
run: npm run build
9191
working-directory: ./client
92+
env:
93+
REACT_APP_STATIC_WEB_APP_CLIENT_ID: ${{ secrets.STATIC_WEB_APP_CLIENT_ID }}
94+
REACT_APP_AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
9295

9396
- name: Zip build folder
9497
run: zip -r build.zip ./client/build
@@ -117,7 +120,7 @@ jobs:
117120
- name: Login to Azure with OIDC
118121
uses: azure/login@v2
119122
with:
120-
client-id: ${{ secrets.AZURE_CLIENT_ID }}
123+
client-id: ${{ secrets.AZURE_GITHUB_SP_CLIENT_ID }}
121124
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
122125
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
123126

README.md

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Once the CSV has been uploaded to the storage blob, another, blob-triggered Azur
66
The computed statistics are then stored in a new blob container, which is used to serve the results to the user.
77
These two functions are defined in the python script [function_app.py](hvalfangst_function/function_app.py) - which is the main entrypoint of our Azure Function App instance.
88

9+
The SPA is protected with Oauth2.0 authorization code flow with PKCE and OIDC. The user is redirected to the Azure AD login page, where they must authenticate before being redirected back to the SPA.
10+
11+
912
The associated Azure infrastructure is deployed with a script (more on that below).
1013

1114
A branch-triggered pipeline has been set up to deploy our code to the respective Azure resources using a GitHub Actions Workflows [script](.github/workflows/deploy_to_azure.yml).
@@ -24,7 +27,7 @@ Thus, deploying the website is simply a matter of uploading the static files to
2427

2528
## Allocate resources
2629

27-
The shell script [allocate_resources](infra/allocate_resources.sh) creates Azure resources using the Azure CLI and a
30+
The shell script [allocate_resources](infra/allocate_resources.sh) creates Azure resources using the Azure CLI in conjunction with a
2831
[Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep) template [file](infra/main.bicep).
2932

3033
It will create the following hierarchy of resources:
@@ -47,13 +50,83 @@ graph TD
4750
B -->|Contains| F
4851
```
4952

53+
## Registrations
54+
In addition to the resources listed above, the script will also create a **service principal** and two Microsoft Entra ID **app registrations.**
55+
56+
### Service Principal for GitHub Actions
57+
The service principal has been assigned contributor role to our resource group, which is sufficient in order to deploy the static web app to the storage blob.
58+
It has been assigned a federated credential configured to work with this repository as it is utilized in our CI/CD [GitHub Actions Workflow script](.github/workflows/deploy_to_azure.yml).
59+
60+
### App Registration for Azure Function App
61+
62+
Exposes the scopes **Csv.Writer** and **Csv.Reader** under URI **api://hvalfangst-function-app**
63+
64+
![img.png](images/expose_api.png)
65+
66+
### App Registration for SPA
67+
68+
Has a redirect URI configured to the static web app's URL and the permissions **Csv.Writer** and the OIDC ones.
69+
70+
![img_1.png](images/api_permissions.png)
71+
72+
5073
## GitHub secrets
5174
Four secrets are required in order for the GitHub Actions Workflow script to deploy the code to the Azure resources.
5275
As may be observed in the [script](.github/workflows/deploy_to_azure.yml), these are:
5376

54-
- **AZURE_CLIENT_ID**: Used to authenticate the service principal in order to deploy the static web app
77+
- **AZURE_GITHUB_SP_CLIENT_ID**: Used to authenticate the service principal in order to deploy the static web app
5578
- **AZURE_SUBSCRIPTION_ID**: Used to authenticate the service principal in order to deploy the static web app
56-
- **AZURE_TENANT_ID**: Used to authenticate the service principal in order to deploy the static web app
79+
- **AZURE_TENANT_ID**: Used to authenticate the service principal and for the OIDC flow in the React SPA
5780
- **PUBLISH_PROFILE**: Used to deploy our two functions to the Azure Function App
81+
- **STATIC_WEB_APP_CLIENT_ID**: Used in the React SPA for OIDC authentication
82+
83+
### Subscription and Tenant ID
84+
The **subscription ID** and **tenant ID** is found by running the following Azure CLI command:
85+
86+
```bash
87+
az account show --query id
88+
az account show --query tenantId
89+
```
90+
91+
### Publish Profile
92+
The publish profile may be obtained by navigating to the Azure Portal, selecting the Azure Function App, and clicking on **Get publish profile**.
93+
94+
### Azure GitHub Service Principal
95+
The service principal used for GitHub Actions is created as part of our resource provisioning script and
96+
as thus should be displayed in the terminal output as such:
97+
98+
![sp_terminal.png](images/sp_terminal.png)
99+
100+
### Static Web App Client ID
101+
Similarly, the **client ID** for the static web app is created as part of our resource provisioning script and outputted to the terminal as such:
102+
103+
![spa_terminal.png](images/spa_terminal.png)
104+
105+
## Usage
106+
After provisioning resources, setting up secrets, and pushing the code to the repository, one
107+
may access the static web app by navigating to the following URL:
108+
109+
110+
https://hvalfangststorageaccount.z6.web.core.windows.net, which results in the following.
111+
112+
![csv_uploader.png](images/csv_uploader.png)
113+
114+
Click on **Sign In** to initiate the OIDC flow - which redirects to the Azure AD permission consent screen.
115+
116+
![oidc.png](images/oidc_sign_in.png)
117+
118+
Clik on **Accept** and check off the **Consent on behalf of your organization** box to be redirected back to the SPA, where you will be greeted with the following.
119+
120+
![authenticated_user.png](images/authenticated_user.png)
121+
122+
Proceed to click on **Upload** to choose a file to upload. Pick the CSV file named [input](input.csv) which has been provided for this purpose.
123+
124+
![choose_file.png](images/choose_file.png)
125+
126+
![file_chosen.png](images/file_chosen.png)
127+
128+
The file name will be displayed in the input field. Click on **Upload** to attempt to upload the file to the storage blob.
129+
130+
If the upload was successful, the following message will be displayed.
58131

59-
![img_1.png](images/img_1.png)
132+
![upload_successful.png](images/upload_successful.png)

client/src/App.js

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from 'react';
1+
import React, { useState, useEffect } from 'react';
22
import { PublicClientApplication } from '@azure/msal-browser';
33
import { Buffer } from 'buffer';
44

@@ -7,19 +7,20 @@ window.Buffer = Buffer;
77
// MSAL Configuration
88
const msalConfig = {
99
auth: {
10-
clientId: 'x',
11-
authority: 'https://login.microsoftonline.com/x',
10+
clientId: process.env.REACT_APP_STATIC_WEB_APP_CLIENT_ID,
11+
authority: `https://login.microsoftonline.com/${process.env.REACT_APP_AZURE_TENANT_ID}`,
1212
redirectUri: window.location.origin,
1313
},
1414
};
1515

1616
const msalInstance = new PublicClientApplication(msalConfig);
1717

18-
1918
const App = () => {
2019
const [file, setFile] = useState(null);
2120
const [error, setError] = useState('');
2221
const [modalVisible, setModalVisible] = useState(false);
22+
const [user, setUser] = useState(null);
23+
const [isInitialized, setIsInitialized] = useState(false);
2324

2425
const handleFileChange = (e) => {
2526
setError('');
@@ -42,13 +43,15 @@ const App = () => {
4243
}
4344

4445
const tokenResponse = await msalInstance.acquireTokenSilent({
45-
scopes: ["api://61b4a548-3979-48df-b2df-37dc4e5e0e02/.default"],
46+
scopes: ['api://hvalfangst-function-app/Csv.Writer'],
4647
account
4748
});
4849

50+
console.log('Token response:', tokenResponse);
51+
4952
const token = tokenResponse.accessToken;
5053

51-
const endpoint = 'x';
54+
const endpoint = 'https://hvalfangstlinuxfunctionapp.azurewebsites.net/api/upload_csv';
5255

5356
// Read the file as a text string
5457
const fileContent = await file.text();
@@ -67,6 +70,8 @@ const App = () => {
6770
throw new Error(`Failed to upload: ${errorMessage}`);
6871
}
6972

73+
console.log('Response:', response)
74+
7075
console.log('File uploaded successfully');
7176
setModalVisible(true); // Show success modal
7277
} catch (error) {
@@ -90,25 +95,52 @@ const App = () => {
9095
const handleSignIn = async () => {
9196
try {
9297
await msalInstance.initialize();
93-
await msalInstance.loginPopup({
94-
scopes: ['x'],
98+
setIsInitialized(true);
99+
const response = await msalInstance.loginPopup({
100+
scopes: ['openid'],
95101
});
102+
console.log('Sign in response:', response);
96103
console.log('User signed in successfully');
104+
setUser(response.account);
97105
} catch (error) {
98106
console.error('Error signing in:', error);
99107
setError(`Error signing in: ${error.message}`);
100108
}
101109
};
102110

103-
const handleSignOut = () => {
104-
msalInstance.logoutPopup();
111+
const handleSignOut = async () => {
112+
if (!isInitialized) {
113+
console.error('MSAL instance is not initialized.');
114+
return;
115+
}
116+
await msalInstance.logoutPopup();
117+
setUser(null);
105118
};
106119

120+
useEffect(() => {
121+
const initializeMsal = async () => {
122+
await msalInstance.initialize();
123+
setIsInitialized(true);
124+
const accounts = msalInstance.getAllAccounts();
125+
if (accounts.length > 0) {
126+
setUser(accounts[0]);
127+
}
128+
};
129+
130+
initializeMsal();
131+
}, []);
132+
107133
return (
108134
<div className="csv-uploader">
109135
<h1>CSV Uploader</h1>
110-
<button onClick={handleSignIn}>Sign In</button>
111-
<button onClick={handleSignOut}>Sign Out</button>
136+
{user ? (
137+
<div>
138+
<p>Welcome, {user.name}</p>
139+
<button onClick={handleSignOut}>Sign Out</button>
140+
</div>
141+
) : (
142+
<button onClick={handleSignIn}>Sign In</button>
143+
)}
112144

113145
<input type="file" accept=".csv" onChange={handleFileChange} />
114146
<button onClick={handleFileUpload}>Upload</button>
@@ -127,4 +159,4 @@ const App = () => {
127159
);
128160
};
129161

130-
export default App;
162+
export default App;

hvalfangst_function/function_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def upload_csv(req: func.HttpRequest, outbound: func.Out[str]) -> HttpResponse:
9696

9797
token = auth_header.split(" ")[1] # Extract Bearer token
9898
audience = os.environ.get("FUNCTION_APP_CLIENT_ID")
99-
required_scopes = ["Csv.Write"]
99+
required_scopes = ["Csv.Writer"]
100100

101101
if not validate_jwt(token, audience, required_scopes):
102102
return HttpResponse("Unauthorized", status_code=401)

images/api_permissions.png

85.5 KB
Loading

images/authenticated_user.png

11.3 KB
Loading

images/choose_file.png

8.19 KB
Loading

images/csv_uploader.png

9.11 KB
Loading

images/expose_api.png

93.6 KB
Loading

images/file_chosen.png

8.57 KB
Loading

images/oidc.png

26.8 KB
Loading

images/oidc_sign_in.png

32.9 KB
Loading
File renamed without changes.

images/sp_terminal.png

14.5 KB
Loading

images/spa_terminal.png

19.9 KB
Loading

images/upload_successful.png

25.9 KB
Loading

0 commit comments

Comments
 (0)