├── appsettings.Development.json
├── appsettings.json
├── JwtAuthDemo.csproj
├── Properties
└── launchSettings.json
├── JwtHelpers.cs
├── Program.cs
└── .gitignore
/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "JwtSettings": {
3 | "Issuer": "JwtAuthDemo",
4 | "SignKey": "01234567890123456789012345678912"
5 | },
6 | "Logging": {
7 | "LogLevel": {
8 | "Default": "Information",
9 | "Microsoft.AspNetCore": "Warning"
10 | }
11 | },
12 | "AllowedHosts": "*"
13 | }
14 |
--------------------------------------------------------------------------------
/JwtAuthDemo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:36917",
8 | "sslPort": 44336
9 | }
10 | },
11 | "profiles": {
12 | "JwtAuthDemo": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "launchUrl": "swagger",
17 | "applicationUrl": "https://localhost:7255;http://localhost:5227",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "IIS Express": {
23 | "commandName": "IISExpress",
24 | "launchBrowser": true,
25 | "launchUrl": "swagger",
26 | "environmentVariables": {
27 | "ASPNETCORE_ENVIRONMENT": "Development"
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/JwtHelpers.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Claims;
2 | using System.Text;
3 | using Microsoft.IdentityModel.JsonWebTokens;
4 | using Microsoft.IdentityModel.Tokens;
5 |
6 | public class JwtHelpers
7 | {
8 | private readonly IConfiguration Configuration;
9 |
10 | public JwtHelpers(IConfiguration configuration)
11 | {
12 | this.Configuration = configuration;
13 | }
14 |
15 | public string GenerateToken(string userName, int expireMinutes = 30)
16 | {
17 | var issuer = Configuration.GetValue("JwtSettings:Issuer");
18 | var signKey = Configuration.GetValue("JwtSettings:SignKey");
19 |
20 | // Configuring "Claims" to your JWT Token
21 | var claims = new List();
22 |
23 | // In RFC 7519 (Section#4), there are defined 7 built-in Claims, but we mostly use 2 of them.
24 | //claims.Add(new Claim(JwtRegisteredClaimNames.Iss, issuer));
25 | claims.Add(new Claim(JwtRegisteredClaimNames.Sub, userName)); // User.Identity.Name
26 | //claims.Add(new Claim(JwtRegisteredClaimNames.Aud, "The Audience"));
27 | //claims.Add(new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds().ToString()));
28 | //claims.Add(new Claim(JwtRegisteredClaimNames.Nbf, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())); // 必須為數字
29 | //claims.Add(new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())); // 必須為數字
30 | claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); // JWT ID
31 |
32 | // The "NameId" claim is usually unnecessary.
33 | //claims.Add(new Claim(JwtRegisteredClaimNames.NameId, userName));
34 |
35 | // This Claim can be replaced by JwtRegisteredClaimNames.Sub, so it's redundant.
36 | //claims.Add(new Claim(ClaimTypes.Name, userName));
37 |
38 | // TODO: You can define your "roles" to your Claims.
39 | claims.Add(new Claim(ClaimTypes.Role, "Admin"));
40 | claims.Add(new Claim(ClaimTypes.Role, "Users"));
41 |
42 | var userClaimsIdentity = new ClaimsIdentity(claims);
43 |
44 | // Create a SymmetricSecurityKey for JWT Token signatures
45 | var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey));
46 |
47 | // HmacSha256 MUST be larger than 128 bits, so the key can't be too short. At least 16 and more characters.
48 | // https://stackoverflow.com/questions/47279947/idx10603-the-algorithm-hs256-requires-the-securitykey-keysize-to-be-greater
49 | var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
50 |
51 | // Create SecurityTokenDescriptor
52 | var tokenDescriptor = new SecurityTokenDescriptor
53 | {
54 | Issuer = issuer,
55 | //Audience = issuer, // Sometimes you don't have to define Audience.
56 | //NotBefore = DateTime.Now, // Default is DateTime.Now
57 | //IssuedAt = DateTime.Now, // Default is DateTime.Now
58 | Subject = userClaimsIdentity,
59 | Expires = DateTime.Now.AddMinutes(expireMinutes),
60 | SigningCredentials = signingCredentials
61 | };
62 |
63 | // Generate a JWT, than get the serialized Token result (string)
64 | var tokenHandler = new JsonWebTokenHandler();
65 | var serializeToken = tokenHandler.CreateToken(tokenDescriptor);
66 |
67 | return serializeToken;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Claims;
2 | using System.Text;
3 | using Microsoft.AspNetCore.Authentication.JwtBearer;
4 | using Microsoft.IdentityModel.Tokens;
5 |
6 | var builder = WebApplication.CreateBuilder(args);
7 |
8 | // Add services to the container.
9 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
10 | builder.Services.AddEndpointsApiExplorer();
11 | builder.Services.AddSwaggerGen();
12 |
13 | builder.Services.AddSingleton();
14 |
15 | builder.Services
16 | .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
17 | .AddJwtBearer(options =>
18 | {
19 | // 當驗證失敗時,回應標頭會包含 WWW-Authenticate 標頭,這裡會顯示失敗的詳細錯誤原因
20 | options.IncludeErrorDetails = true; // 預設值為 true,有時會特別關閉
21 |
22 | options.TokenValidationParameters = new TokenValidationParameters
23 | {
24 | // 透過這項宣告,就可以從 "sub" 取值並設定給 User.Identity.Name
25 | NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
26 | // 透過這項宣告,就可以從 "roles" 取值,並可讓 [Authorize] 判斷角色
27 | RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
28 |
29 | // 一般我們都會驗證 Issuer
30 | ValidateIssuer = true,
31 | ValidIssuer = builder.Configuration.GetValue("JwtSettings:Issuer"),
32 |
33 | // 通常不太需要驗證 Audience
34 | ValidateAudience = false,
35 | //ValidAudience = "JwtAuthDemo", // 不驗證就不需要填寫
36 |
37 | // 一般我們都會驗證 Token 的有效期間
38 | ValidateLifetime = true,
39 |
40 | // 如果 Token 中包含 key 才需要驗證,一般都只有簽章而已
41 | ValidateIssuerSigningKey = false,
42 |
43 | // "1234567890123456" 應該從 IConfiguration 取得
44 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetValue("JwtSettings:SignKey")))
45 | };
46 | });
47 |
48 | builder.Services.AddAuthorization();
49 |
50 | var app = builder.Build();
51 |
52 | // Configure the HTTP request pipeline.
53 | if (app.Environment.IsDevelopment())
54 | {
55 | app.UseSwagger();
56 | app.UseSwaggerUI();
57 | }
58 |
59 | app.UseHttpsRedirection();
60 |
61 | app.UseAuthentication();
62 | app.UseAuthorization();
63 |
64 | var summaries = new[]
65 | {
66 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
67 | };
68 |
69 | app.MapGet("/weatherforecast", () =>
70 | {
71 | var forecast = Enumerable.Range(1, 5).Select(index =>
72 | new WeatherForecast
73 | (
74 | DateTime.Now.AddDays(index),
75 | Random.Shared.Next(-20, 55),
76 | summaries[Random.Shared.Next(summaries.Length)]
77 | ))
78 | .ToArray();
79 | return forecast;
80 | })
81 | .WithName("GetWeatherForecast");
82 |
83 | app.MapPost("/signin", (LoginViewModel login, JwtHelpers jwt) =>
84 | {
85 | if (ValidateUser(login))
86 | {
87 | var token = jwt.GenerateToken(login.Username);
88 | return Results.Ok(new { token });
89 | }
90 | else
91 | {
92 | return Results.BadRequest();
93 | }
94 | })
95 | .WithName("SignIn")
96 | .AllowAnonymous();
97 |
98 | app.MapGet("/claims", (ClaimsPrincipal user) =>
99 | {
100 | return Results.Ok(user.Claims.Select(p => new { p.Type, p.Value }));
101 | })
102 | .WithName("Claims")
103 | .RequireAuthorization();
104 |
105 | app.MapGet("/username", (ClaimsPrincipal user) =>
106 | {
107 | return Results.Ok(user.Identity?.Name);
108 | })
109 | .WithName("Username")
110 | .RequireAuthorization();
111 |
112 | app.MapGet("/jwtid", (ClaimsPrincipal user) =>
113 | {
114 | return Results.Ok(user.Claims.FirstOrDefault(p => p.Type == "jti")?.Value);
115 | })
116 | .WithName("JwtId")
117 | .RequireAuthorization();
118 |
119 | bool ValidateUser(LoginViewModel login)
120 | {
121 | return true;
122 | }
123 |
124 | app.Run();
125 |
126 | record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
127 | {
128 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
129 | }
130 |
131 | record LoginViewModel(string Username, string Password);
132 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudio
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio
4 |
5 | ### VisualStudio ###
6 | ## Ignore Visual Studio temporary files, build results, and
7 | ## files generated by popular Visual Studio add-ons.
8 | ##
9 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
10 |
11 | # User-specific files
12 | *.rsuser
13 | *.suo
14 | *.user
15 | *.userosscache
16 | *.sln.docstates
17 |
18 | # User-specific files (MonoDevelop/Xamarin Studio)
19 | *.userprefs
20 |
21 | # Mono auto generated files
22 | mono_crash.*
23 |
24 | # Build results
25 | [Dd]ebug/
26 | [Dd]ebugPublic/
27 | [Rr]elease/
28 | [Rr]eleases/
29 | x64/
30 | x86/
31 | [Ww][Ii][Nn]32/
32 | [Aa][Rr][Mm]/
33 | [Aa][Rr][Mm]64/
34 | bld/
35 | [Bb]in/
36 | [Oo]bj/
37 | [Ll]og/
38 | [Ll]ogs/
39 |
40 | # Visual Studio 2015/2017 cache/options directory
41 | .vs/
42 | # Uncomment if you have tasks that create the project's static files in wwwroot
43 | #wwwroot/
44 |
45 | # Visual Studio 2017 auto generated files
46 | Generated\ Files/
47 |
48 | # MSTest test Results
49 | [Tt]est[Rr]esult*/
50 | [Bb]uild[Ll]og.*
51 |
52 | # NUnit
53 | *.VisualState.xml
54 | TestResult.xml
55 | nunit-*.xml
56 |
57 | # Build Results of an ATL Project
58 | [Dd]ebugPS/
59 | [Rr]eleasePS/
60 | dlldata.c
61 |
62 | # Benchmark Results
63 | BenchmarkDotNet.Artifacts/
64 |
65 | # .NET Core
66 | project.lock.json
67 | project.fragment.lock.json
68 | artifacts/
69 |
70 | # ASP.NET Scaffolding
71 | ScaffoldingReadMe.txt
72 |
73 | # StyleCop
74 | StyleCopReport.xml
75 |
76 | # Files built by Visual Studio
77 | *_i.c
78 | *_p.c
79 | *_h.h
80 | *.ilk
81 | *.meta
82 | *.obj
83 | *.iobj
84 | *.pch
85 | *.pdb
86 | *.ipdb
87 | *.pgc
88 | *.pgd
89 | *.rsp
90 | *.sbr
91 | *.tlb
92 | *.tli
93 | *.tlh
94 | *.tmp
95 | *.tmp_proj
96 | *_wpftmp.csproj
97 | *.log
98 | *.tlog
99 | *.vspscc
100 | *.vssscc
101 | .builds
102 | *.pidb
103 | *.svclog
104 | *.scc
105 |
106 | # Chutzpah Test files
107 | _Chutzpah*
108 |
109 | # Visual C++ cache files
110 | ipch/
111 | *.aps
112 | *.ncb
113 | *.opendb
114 | *.opensdf
115 | *.sdf
116 | *.cachefile
117 | *.VC.db
118 | *.VC.VC.opendb
119 |
120 | # Visual Studio profiler
121 | *.psess
122 | *.vsp
123 | *.vspx
124 | *.sap
125 |
126 | # Visual Studio Trace Files
127 | *.e2e
128 |
129 | # TFS 2012 Local Workspace
130 | $tf/
131 |
132 | # Guidance Automation Toolkit
133 | *.gpState
134 |
135 | # ReSharper is a .NET coding add-in
136 | _ReSharper*/
137 | *.[Rr]e[Ss]harper
138 | *.DotSettings.user
139 |
140 | # TeamCity is a build add-in
141 | _TeamCity*
142 |
143 | # DotCover is a Code Coverage Tool
144 | *.dotCover
145 |
146 | # AxoCover is a Code Coverage Tool
147 | .axoCover/*
148 | !.axoCover/settings.json
149 |
150 | # Coverlet is a free, cross platform Code Coverage Tool
151 | coverage*.json
152 | coverage*.xml
153 | coverage*.info
154 |
155 | # Visual Studio code coverage results
156 | *.coverage
157 | *.coveragexml
158 |
159 | # NCrunch
160 | _NCrunch_*
161 | .*crunch*.local.xml
162 | nCrunchTemp_*
163 |
164 | # MightyMoose
165 | *.mm.*
166 | AutoTest.Net/
167 |
168 | # Web workbench (sass)
169 | .sass-cache/
170 |
171 | # Installshield output folder
172 | [Ee]xpress/
173 |
174 | # DocProject is a documentation generator add-in
175 | DocProject/buildhelp/
176 | DocProject/Help/*.HxT
177 | DocProject/Help/*.HxC
178 | DocProject/Help/*.hhc
179 | DocProject/Help/*.hhk
180 | DocProject/Help/*.hhp
181 | DocProject/Help/Html2
182 | DocProject/Help/html
183 |
184 | # Click-Once directory
185 | publish/
186 |
187 | # Publish Web Output
188 | *.[Pp]ublish.xml
189 | *.azurePubxml
190 | # Note: Comment the next line if you want to checkin your web deploy settings,
191 | # but database connection strings (with potential passwords) will be unencrypted
192 | *.pubxml
193 | *.publishproj
194 |
195 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
196 | # checkin your Azure Web App publish settings, but sensitive information contained
197 | # in these scripts will be unencrypted
198 | PublishScripts/
199 |
200 | # NuGet Packages
201 | *.nupkg
202 | # NuGet Symbol Packages
203 | *.snupkg
204 | # The packages folder can be ignored because of Package Restore
205 | **/[Pp]ackages/*
206 | # except build/, which is used as an MSBuild target.
207 | !**/[Pp]ackages/build/
208 | # Uncomment if necessary however generally it will be regenerated when needed
209 | #!**/[Pp]ackages/repositories.config
210 | # NuGet v3's project.json files produces more ignorable files
211 | *.nuget.props
212 | *.nuget.targets
213 |
214 | # Microsoft Azure Build Output
215 | csx/
216 | *.build.csdef
217 |
218 | # Microsoft Azure Emulator
219 | ecf/
220 | rcf/
221 |
222 | # Windows Store app package directories and files
223 | AppPackages/
224 | BundleArtifacts/
225 | Package.StoreAssociation.xml
226 | _pkginfo.txt
227 | *.appx
228 | *.appxbundle
229 | *.appxupload
230 |
231 | # Visual Studio cache files
232 | # files ending in .cache can be ignored
233 | *.[Cc]ache
234 | # but keep track of directories ending in .cache
235 | !?*.[Cc]ache/
236 |
237 | # Others
238 | ClientBin/
239 | ~$*
240 | *~
241 | *.dbmdl
242 | *.dbproj.schemaview
243 | *.jfm
244 | *.pfx
245 | *.publishsettings
246 | orleans.codegen.cs
247 |
248 | # Including strong name files can present a security risk
249 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
250 | #*.snk
251 |
252 | # Since there are multiple workflows, uncomment next line to ignore bower_components
253 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
254 | #bower_components/
255 |
256 | # RIA/Silverlight projects
257 | Generated_Code/
258 |
259 | # Backup & report files from converting an old project file
260 | # to a newer Visual Studio version. Backup files are not needed,
261 | # because we have git ;-)
262 | _UpgradeReport_Files/
263 | Backup*/
264 | UpgradeLog*.XML
265 | UpgradeLog*.htm
266 | ServiceFabricBackup/
267 | *.rptproj.bak
268 |
269 | # SQL Server files
270 | *.mdf
271 | *.ldf
272 | *.ndf
273 |
274 | # Business Intelligence projects
275 | *.rdl.data
276 | *.bim.layout
277 | *.bim_*.settings
278 | *.rptproj.rsuser
279 | *- [Bb]ackup.rdl
280 | *- [Bb]ackup ([0-9]).rdl
281 | *- [Bb]ackup ([0-9][0-9]).rdl
282 |
283 | # Microsoft Fakes
284 | FakesAssemblies/
285 |
286 | # GhostDoc plugin setting file
287 | *.GhostDoc.xml
288 |
289 | # Node.js Tools for Visual Studio
290 | .ntvs_analysis.dat
291 | node_modules/
292 |
293 | # Visual Studio 6 build log
294 | *.plg
295 |
296 | # Visual Studio 6 workspace options file
297 | *.opt
298 |
299 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
300 | *.vbw
301 |
302 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
303 | *.vbp
304 |
305 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
306 | *.dsw
307 | *.dsp
308 |
309 | # Visual Studio 6 technical files
310 |
311 | # Visual Studio LightSwitch build output
312 | **/*.HTMLClient/GeneratedArtifacts
313 | **/*.DesktopClient/GeneratedArtifacts
314 | **/*.DesktopClient/ModelManifest.xml
315 | **/*.Server/GeneratedArtifacts
316 | **/*.Server/ModelManifest.xml
317 | _Pvt_Extensions
318 |
319 | # Paket dependency manager
320 | .paket/paket.exe
321 | paket-files/
322 |
323 | # FAKE - F# Make
324 | .fake/
325 |
326 | # CodeRush personal settings
327 | .cr/personal
328 |
329 | # Python Tools for Visual Studio (PTVS)
330 | __pycache__/
331 | *.pyc
332 |
333 | # Cake - Uncomment if you are using it
334 | # tools/**
335 | # !tools/packages.config
336 |
337 | # Tabs Studio
338 | *.tss
339 |
340 | # Telerik's JustMock configuration file
341 | *.jmconfig
342 |
343 | # BizTalk build output
344 | *.btp.cs
345 | *.btm.cs
346 | *.odx.cs
347 | *.xsd.cs
348 |
349 | # OpenCover UI analysis results
350 | OpenCover/
351 |
352 | # Azure Stream Analytics local run output
353 | ASALocalRun/
354 |
355 | # MSBuild Binary and Structured Log
356 | *.binlog
357 |
358 | # NVidia Nsight GPU debugger configuration file
359 | *.nvuser
360 |
361 | # MFractors (Xamarin productivity tool) working folder
362 | .mfractor/
363 |
364 | # Local History for Visual Studio
365 | .localhistory/
366 |
367 | # Visual Studio History (VSHistory) files
368 | .vshistory/
369 |
370 | # BeatPulse healthcheck temp database
371 | healthchecksdb
372 |
373 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
374 | MigrationBackup/
375 |
376 | # Ionide (cross platform F# VS Code tools) working folder
377 | .ionide/
378 |
379 | # Fody - auto-generated XML schema
380 | FodyWeavers.xsd
381 |
382 | # VS Code files for those working on multiple tools
383 | .vscode/*
384 | !.vscode/settings.json
385 | !.vscode/tasks.json
386 | !.vscode/launch.json
387 | !.vscode/extensions.json
388 | *.code-workspace
389 |
390 | # Local History for Visual Studio Code
391 | .history/
392 |
393 | # Windows Installer files from build outputs
394 | *.cab
395 | *.msi
396 | *.msix
397 | *.msm
398 | *.msp
399 |
400 | # JetBrains Rider
401 | *.sln.iml
402 |
403 | ### VisualStudio Patch ###
404 | # Additional files built by Visual Studio
405 |
406 | # End of https://www.toptal.com/developers/gitignore/api/visualstudio
407 |
--------------------------------------------------------------------------------