Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7a353b9

Browse files
committed
added authentication support
1 parent f9c4d71 commit 7a353b9

10 files changed

+495
-36
lines changed

‎README.md‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ To run this sample in your subscription, make sure to fork the repository into y
5252
This repo has three branches that shows the development at different stages
5353

5454
- 1.0: First version, no database support
55-
- 2.0: [This branch]Database support added
56-
- 3.0: Authentication and Authorization
55+
- 2.0: Database support added
56+
- 3.0: [This branch]Authentication and Authorization
5757

58-
### V2.0 Notes
58+
### V3.0 Notes
5959

60-
In this branch the backend REST API service is modified so that the to-do list can be saved an manged using an Azure SQL database. Communication with the database is done using JSON too, as Azure SQL support [JSON natively](https://docs.microsoft.com/en-us/sql/relational-databases/json/json-data-sql-server?view=sql-server-ver15).
60+
In this branch the backend REST API service and the database are modified so that a user can be authenticated and they will see and manage only the to-do items they have created. Anonymous access is also allowed, and all to-do items created while not authenticated will be visible and manageable by anyone. Authentication is done via the Azure Static Web Apps reverse proxy, that [takes care of all the complexities](https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-authorization) of OAuth2 for you. The Vue web client has been also updated to provide login and logoff capabilities.
6161

6262
## Folder Structure
6363

@@ -93,7 +93,7 @@ az sql server create -n <server-name> -l <location> --admin-user <admin-user> --
9393
Create a new Azure SQL database:
9494
9595
```sh
96-
az sql db create -g <resource-group> -s <server-name> -n todo_v2 --service-objective GP_Gen5_2
96+
az sql db create -g <resource-group> -s <server-name> -n todo_v3 --service-objective GP_Gen5_2
9797
```
9898
9999
Another option is to run the `azure-create-sql-db.sh` script in the `./databases` folder. The script uses the ARM template available in the same folder to create a server and a `todo_vw` database.
@@ -111,7 +111,7 @@ you can get your public IP from here, for example: https://ifconfig.me/
111111
Database is deployed using [DbUp](http://dbup.github.io/). Switch to the `./database/deploy` folder and create new `.env` file containing the connection string to the created Azure SQL database. You can use the provide `.env.template` as a guide. The connection string look like:
112112
113113
```
114-
SERVER=<my-server>.database.windows.net;DATABASE=todo_v2;UID=<my_user_id>;PWD=<my_user_password>;
114+
SERVER=<my-server>.database.windows.net;DATABASE=todo_v3;UID=<my_user_id>;PWD=<my_user_password>;
115115
```
116116
117117
replace the placeholder with the correct value for your database, username and password and you're good to go. Make sure the database user specified in the connection string has enough permission to create objects (for example, make sure is a server administrator or in the db_owner database role).
@@ -137,7 +137,7 @@ dotnet run
137137
you will see something like:
138138

139139
```
140-
Deploying database: todo_v2
140+
Deploying database: todo_v3
141141
Testing connection...
142142
Starting deployment...
143143
Beginning database upgrade
@@ -147,6 +147,10 @@ Executing Database Server script '01-create-objects.sql'
147147
Checking whether journal table exists..
148148
Creating the [dbo].[$__dbup_journal] table
149149
The [dbo].[$__dbup_journal] table has been created
150+
Executing Database Server script '02-update-todo-table.sql'
151+
Executing Database Server script '03-update-stored-procs.sql'
152+
Executing Database Server script '04-move-to-long-user-id.sql'
153+
Executing Database Server script '05-update-stored-procs-support-long-user-id.sql'
150154
Upgrade successful
151155
Success!
152156
```
@@ -155,7 +159,7 @@ Database has been deployed successfully!
155159

156160
## Test solution locally
157161

158-
Before starting the solution locally, you have to configure the Azure Function that is used to provide the backed API. In the `./api` folder create a `local.settings.json` file starting from the provided template. All you have to do is update the connection string with the value correct for you solution. If have created the Azure SQL database as described above you'll have a database named `todo_v2`. Just make sure you add the correct server name in the `local.settings.json`. The database name, user login and password are already set in the template file to match those used in this repository and in the `./database/sql/01-create-objects.sql` file.
162+
Before starting the solution locally, you have to configure the Azure Function that is used to provide the backed API. In the `./api` folder create a `local.settings.json` file starting from the provided template. All you have to do is update the connection string with the value correct for you solution. If have created the Azure SQL database as described above you'll have a database named `todo_v3`. Just make sure you add the correct server name in the `local.settings.json`. The database name, user login and password are already set in the template file to match those used in this repository and in the `./database/sql/01-create-objects.sql` file.
159163

160164
To run Azure Functions locally, you also need a local Azure Storage emulator. You can use [Azurite](https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio) that also has a VS Code extension.
161165

@@ -175,6 +179,8 @@ Azure Static Web Apps emulator started at http://localhost:4280. Press CTRL+C to
175179
176180
everything will be up and running. Go the the indicated URL and you'll see the ToDo App. Go an play with it, it will work perfectly, having the Vue.js frontend calling the REST API provided by the Azure Function and storing the to-do list in a List object.
177181

182+
You can also try to login and be an authenticated user. Static Web Apps will provide a mock of the real authentication process (done using the GitHub authentication provider, in this sample), so you can have a full experience also when debugging locally.
183+
178184
## Deploy the solution on Azure
179185

180186
Now that you know everything works fine, you can deploy the solution to Azure. You can take advantage of the script `./azure-deploy.sh` that will deploy the Azure Static Web app for you.

‎api/ToDoHandler.cs‎

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,53 @@
1313
using System.Data;
1414
using Microsoft.Data.SqlClient;
1515
using Dapper;
16+
using System.Security.Claims;
17+
using System.Text;
1618

1719
namespace api
1820
{
1921
public static class ToDoHandler
20-
{
21-
private staticasyncTask<JToken>ExecuteProcedure(stringverb,JTokenpayload)
22+
{
23+
private classClientPrincipal
2224
{
23-
JToken result = null;
25+
public string IdentityProvider { get; set; }
26+
public string UserId { get; set; }
27+
public string UserDetails { get; set; }
28+
public IEnumerable<string> UserRoles { get; set; }
29+
}
30+
31+
private static ClientPrincipal ParsePrincipal(this HttpRequest req)
32+
{
33+
var principal = new ClientPrincipal();
34+
35+
if (req.Headers.TryGetValue("x-ms-client-principal", out var header))
36+
{
37+
var data = header[0];
38+
var decoded = Convert.FromBase64String(data);
39+
var json = Encoding.UTF8.GetString(decoded);
40+
principal = JsonConvert.DeserializeObject<ClientPrincipal>(json);
41+
}
42+
43+
principal.UserRoles = principal.UserRoles?.Except(new string[] { "anonymous" }, StringComparer.CurrentCultureIgnoreCase);
44+
45+
return principal;
46+
}
47+
48+
private static async Task<JToken> ExecuteProcedure(string verb, JToken payload, JToken context)
49+
{
50+
JToken result = null;
2451

2552
using (var conn = new SqlConnection(Environment.GetEnvironmentVariable("AzureSQL")))
2653
{
2754
DynamicParameters parameters = new DynamicParameters();
28-
if (payload != null) parameters.Add("payload", payload.ToString());
55+
56+
if (payload != null)
57+
parameters.Add("payload", payload.ToString());
58+
59+
if (context != null)
60+
parameters.Add("context", context.ToString());
61+
else
62+
parameters.Add("context", "{}");
2963

3064
string stringResult = await conn.ExecuteScalarAsync<string>(
3165
sql: $"web.{verb}_todo",
@@ -36,75 +70,83 @@ private static async Task<JToken> ExecuteProcedure(string verb, JToken payload)
3670
if (!string.IsNullOrEmpty(stringResult)) result = JToken.Parse(stringResult);
3771
}
3872

39-
return result;
73+
return result;
4074
}
4175

4276
[FunctionName("Get")]
4377
public static async Task<IActionResult> Get(
44-
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "todo/{id:int?}")] HttpRequest req,
45-
ILogger log,
46-
int? id)
78+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "todo/{id:int?}")] HttpRequest req,
79+
ILogger log,
80+
int? id)
4781
{
82+
var context = JObject.FromObject(req.ParsePrincipal());
83+
4884
var payload = id.HasValue ? new JObject { ["id"] = id.Value } : null;
49-
50-
var result = await ExecuteProcedure("get", payload);
5185

52-
if (result == null)
86+
var result = await ExecuteProcedure("get", payload, context);
87+
88+
if (result == null)
5389
return new NotFoundResult();
5490

55-
return new OkObjectResult(result);
91+
return new OkObjectResult(result);
5692
}
5793

5894
[FunctionName("Post")]
5995
public static async Task<IActionResult> Post(
60-
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "todo")] HttpRequest req,
96+
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "todo")] HttpRequest req,
6197
ILogger log)
6298
{
99+
var context = JObject.FromObject(req.ParsePrincipal());
100+
63101
string body = await new StreamReader(req.Body).ReadToEndAsync();
64-
102+
65103
var payload = JObject.Parse(body);
66104

67-
var result = await ExecuteProcedure("post", payload);
68-
105+
var result = await ExecuteProcedure("post", payload,context);
106+
69107
return new OkObjectResult(result);
70108
}
71109

72110
[FunctionName("Patch")]
73111
public static async Task<IActionResult> Patch(
74-
[HttpTrigger(AuthorizationLevel.Anonymous, "patch", Route = "todo/{id}")] HttpRequest req,
112+
[HttpTrigger(AuthorizationLevel.Anonymous, "patch", Route = "todo/{id}")] HttpRequest req,
75113
ILogger log,
76114
int id)
77115
{
116+
var context = JObject.FromObject(req.ParsePrincipal());
117+
78118
string body = await new StreamReader(req.Body).ReadToEndAsync();
79-
119+
80120
var payload = new JObject
81121
{
82122
["id"] = id,
83123
["todo"] = JObject.Parse(body)
84124
};
85125

86-
JToken result = await ExecuteProcedure("patch", payload);
126+
JToken result = await ExecuteProcedure("patch", payload,context);
87127

88-
if (result == null)
128+
if (result == null)
89129
return new NotFoundResult();
90130

91131
return new OkObjectResult(result);
92132
}
93133

94134
[FunctionName("Delete")]
95135
public static async Task<IActionResult> Delete(
96-
[HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "todo/{id}")] HttpRequest req,
136+
[HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "todo/{id}")] HttpRequest req,
97137
ILogger log,
98138
int id)
99139
{
100-
var payload = newJObject{["id"]=id};
140+
var context = JObject.FromObject(req.ParsePrincipal());
101141

102-
var result = await ExecuteProcedure("delete", payload);
142+
var payload = new JObject { ["id"] = id };
143+
144+
var result = await ExecuteProcedure("delete", payload, context);
103145

104-
if (result == null)
146+
if (result == null)
105147
return new NotFoundResult();
106148

107-
return new OkObjectResult(result);
149+
return new OkObjectResult(result);
108150
}
109151
}
110152
}

‎api/local.settings.json.template‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"Values": {
44
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
55
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
6-
"AzureSQL": "Server=tcp:.database.windows.net,1433;Initial Catalog=todo_v2;Persist Security Info=False;User ID=webapp;Password=Super_Str0ng*P4ZZword!;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
6+
"AzureSQL": "Server=tcp:.database.windows.net,1433;Initial Catalog=todo_v3;Persist Security Info=False;User ID=webapp;Password=Super_Str0ng*P4ZZword!;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
77
}
88
}

‎azure-deploy.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ appName=""
1313
location=""
1414
1515
# Connection string
16-
azureSQL='Server=tcp:.database.windows.net,1433;Initial Catalog=todo_v2;Persist Security Info=False;User ID=webapp;Password=Super_Str0ng*P4ZZword!;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'
16+
azureSQL='Server=tcp:.database.windows.net,1433;Initial Catalog=todo_v3;Persist Security Info=False;User ID=webapp;Password=Super_Str0ng*P4ZZword!;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'
1717
1818
gitSource="https://github.com/Azure-Samples/azure-sql-db-fullstack-serverless-kickstart"
1919
gitToken=""

‎client/index.html‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ <h1>todos</h1>
5656
</footer>
5757
</section>
5858
<footer class="info">
59+
<label id="login">[<a href=".auth/login/github">login</a>]</label>
60+
<label id="logoff">[<a href=".auth/logout">logoff</a>]</label>
5961
<p>Double-click to edit a todo</p>
6062
<p>Original <a href="https://github.com/vuejs/vuejs.org/tree/master/src/v2/examples/vue-20-todomvc">Vue.JS Sample</a> by <a href="http://evanyou.me">Evan You</a></p>
6163
<p>Azure Function + Azure SQL Backend Sample by <a href="http://davidemauri.it">Davide Mauri</a></p>
@@ -230,6 +232,21 @@ <h1>todos</h1>
230232

231233
// mount
232234
app.$mount(".todoapp");
235+
236+
// check if user has logged in or not
237+
async function getUserInfo() {
238+
const response = await fetch('/.auth/me');
239+
const payload = await response.json();
240+
const { clientPrincipal } = payload;
241+
if (clientPrincipal?.userId != null )
242+
{
243+
document.getElementById("login").innerHTML = "Welcome " + clientPrincipal.userDetails;
244+
} else {
245+
document.getElementById("logoff").style.display = "none"
246+
}
247+
}
248+
249+
getUserInfo();
233250
</script>
234251
</body>
235252

‎database/azure-create-sql-db.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ az group create \
1818
-l $location
1919

2020
echo "Deploying Azure SQL Database...";
21-
azureSQLDB="todo_v2"
21+
azureSQLDB="todo_v3"
2222
azureSQLServer=$(az deployment group create \
2323
--name "sql-db-deploy-2.0" \
2424
--resource-group $resourceGroup \
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
alter table dbo.todos
2+
add [owner_id] int
3+
go
4+
5+
update dbo.todos set [owner_id] = 0 where [owner_id] is null;
6+
go
7+
8+
alter table dbo.todos
9+
alter column [owner_id] int not null
10+
go

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /