Skip to content

Commit 062280b

Browse files
committed
add JWT token authentication, working logout too
1 parent e707a04 commit 062280b

File tree

12 files changed

+1432
-1232
lines changed

12 files changed

+1432
-1232
lines changed

ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
<None Remove="ClientApp\components\Error.tsx" />
3535
<None Remove="ClientApp\components\Example.tsx" />
3636
<None Remove="ClientApp\components\users\Logout.tsx" />
37+
<None Remove="ClientApp\css\home.css" />
38+
<None Remove="ClientApp\css\loading.css" />
39+
<None Remove="ClientApp\utils\helpers.tsx" />
3740
</ItemGroup>
3841

3942
<ItemGroup>
@@ -47,6 +50,9 @@
4750
<TypeScriptCompile Include="ClientApp\components\users\Login.tsx" />
4851
<TypeScriptCompile Include="ClientApp\components\users\Logout.tsx" />
4952
<TypeScriptCompile Include="ClientApp\components\users\Register.tsx" />
53+
<TypeScriptCompile Include="ClientApp\css\home.css" />
54+
<TypeScriptCompile Include="ClientApp\css\loading.css" />
55+
<TypeScriptCompile Include="ClientApp\utils\helpers.tsx" />
5056
</ItemGroup>
5157

5258
<ItemGroup>

ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example/ClientApp/components/About.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
export class About extends React.Component<any, any> {
44
public render() {
5-
return <h3>This is our about page.</h3>
6-
};
5+
return (
6+
<div className="about">
7+
<h3>This is our about page.</h3>
8+
<p>Ellipsis loading icon by loading.io</p>
9+
<p>Baby cow image by neko-kuma.deviantart.com</p>
10+
</div>
11+
);
12+
}
713
}

ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example/ClientApp/components/Home.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { Grid, Row } from 'react-bootstrap';
55
export class Home extends React.Component<RouteComponentProps<{}>, {}> {
66
public render() {
77
return (
8-
<Grid>
8+
<Grid className="home">
99
<Row className="text-center">
10-
<h2>Our Products</h2>
11-
<h4>This is an example of setting ASP.NET Core Web API and ReactJS with basic User Authentication. :)</h4>
10+
<h2>Hello!</h2>
11+
<h4>This is an example of setting up an ASP.NET Core Web API and ReactJS with basic User Authentication. :)</h4>
1212
</Row>
1313
</Grid>
1414
);

ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example/ClientApp/components/NavMenu.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
import * as React from 'react';
22
import { Link, NavLink } from 'react-router-dom';
33
import { Navbar, NavItem, Nav } from 'react-bootstrap';
4+
import { isLoggedIn, getUser, removeAccessToken } from '../utils/helpers';
5+
6+
export class NavMenu extends React.Component<{}, {}> {
7+
constructor() {
8+
super();
9+
10+
this.logout = this.logout.bind(this);
11+
}
12+
13+
logout() {
14+
removeAccessToken();
15+
window.location.hash = '/';
16+
}
417

5-
export class NavMenu extends React.Component<{}, {}> {
618
render() {
19+
let content = isLoggedIn()
20+
?
21+
<Nav pullRight>
22+
{/*TODO: getUser can go to user profile page :)*/}
23+
<NavItem eventKey={1}>{`${getUser()}`}</NavItem>
24+
<NavItem eventKey={2} onClick={this.logout}>Logout</NavItem>
25+
</Nav>
26+
: <Nav pullRight>
27+
<NavItem eventKey={1} href="/login">Login</NavItem>
28+
<NavItem eventKey={2} href="/register">Register</NavItem>
29+
</Nav>
730
return (
831
<Navbar inverse collapseOnSelect fixedTop>
932
<Navbar.Header>
@@ -17,10 +40,7 @@ export class NavMenu extends React.Component<{}, {}> {
1740
<NavItem eventKey={1} href="/users">Users</NavItem>
1841
<NavItem eventKey={2} href="/about">About</NavItem>
1942
</Nav>
20-
<Nav pullRight>
21-
<NavItem eventKey={1} href="/login">Login</NavItem>
22-
<NavItem eventKey={2} href="/register">Register</NavItem>
23-
</Nav>;
43+
{ content }
2444
</Navbar.Collapse>
2545
</Navbar>
2646
);

ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example/ClientApp/components/users/Login.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import * as React from "react";
2+
import { Redirect } from "react-router-dom";
23
import 'isomorphic-fetch';
34
import { Form, FormGroup, FormControl, ControlLabel, Button, Col, Grid, Row } from 'react-bootstrap';
5+
import { setAccessToken, setUser, isLoggedIn } from '../../utils/helpers';
46

57
export class Login extends React.Component<any, any> {
68
constructor() {
79
super();
810

911
this.state = {
1012
userName: '',
11-
password: ''
13+
password: '',
14+
loggedIn: isLoggedIn()
1215
};
1316

1417
this.handleOnChange = this.handleOnChange.bind(this);
@@ -50,6 +53,9 @@ export class Login extends React.Component<any, any> {
5053
// since it hasn't clue yet; redirect to pages depending of response's status code
5154
checkStatus(res: any): void {
5255
if (res.status >= 200 && res.status < 300) {
56+
setAccessToken(res.access_token);
57+
setUser(this.state.userName);
58+
this.setState({ loggedIn: true });
5359
this.props.history.push('/example');
5460
} else {
5561
let error = new Error(res.statusTest);
@@ -59,6 +65,10 @@ export class Login extends React.Component<any, any> {
5965
}
6066

6167
render() {
68+
if (this.state.loggedIn) {
69+
return <Redirect to="/" />;
70+
}
71+
6272
return (
6373
<Grid>
6474
<Row className="show-grid">
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
/*Import our styles and reference it to the boot.tsx for cleaner code*/
22
@import 'site.css';
3-
@import 'error.css';
3+
@import 'error.css';
4+
@import 'loading.css';
5+
@import 'home.css';

ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example/Controllers/UsersController.cs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,45 @@ namespace ASPNETCoreReactJS_Example.Controllers
33
using Data.Models;
44
using Microsoft.AspNetCore.Identity;
55
using Microsoft.AspNetCore.Mvc;
6-
using Microsoft.Extensions.Logging;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.IdentityModel.Tokens;
78
using Models;
89
using Services.Interfaces;
10+
using System;
911
using System.Collections.Generic;
12+
using System.IdentityModel.Tokens.Jwt;
13+
using System.Linq;
14+
using System.Security.Claims;
15+
using System.Text;
1016
using System.Threading.Tasks;
1117

12-
[Route("api/[controller]")]
18+
[Route("api/[controller]/[action]")]
1319
public class UsersController : Controller
1420
{
1521
private readonly UserManager<User> userManager;
1622
private readonly SignInManager<User> signInManager;
23+
private readonly IConfiguration configuration;
1724
private readonly IUserService service;
1825

1926
public UsersController(
2027
UserManager<User> userManager,
2128
SignInManager<User> signInManager,
22-
ILogger<UsersController> logger,
29+
IConfiguration configuration,
2330
IUserService service)
2431
{
2532
this.userManager = userManager;
2633
this.signInManager = signInManager;
34+
this.configuration = configuration;
2735
this.service = service;
2836
}
2937

3038
// GET All Users
31-
[HttpGet("[action]")]
39+
[HttpGet]
3240
public IEnumerable<UserViewModel> All()
3341
=> this.service.All();
3442

3543
// POST Register User
36-
[HttpPost("[action]")]
44+
[HttpPost]
3745
public async Task<IActionResult> Register([FromBody]RegisterModel model)
3846
{
3947
if (!ModelState.IsValid)
@@ -58,11 +66,14 @@ public async Task<IActionResult> Register([FromBody]RegisterModel model)
5866

5967
this.service.Add(user);
6068

61-
return this.Ok();
69+
// Automatically sign in user ?
70+
//this.signInManager.SignInAsync(user, false);
71+
72+
return this.Ok(GenerateJwtToken(model.Email, user));
6273
}
6374

6475
// POST Login User
65-
[HttpPost("[action]")]
76+
[HttpPost]
6677
public async Task<IActionResult> Login([FromBody]LoginModel model)
6778
{
6879
if (!ModelState.IsValid)
@@ -78,16 +89,41 @@ public async Task<IActionResult> Login([FromBody]LoginModel model)
7889
return this.BadRequest();
7990
}
8091

81-
return this.Ok();
92+
var appUser = this.userManager.Users.SingleOrDefault(u => u.UserName == model.UserName);
93+
return this.Ok(GenerateJwtToken(model.UserName, appUser));
8294
}
8395

8496
// POST Logout User
85-
[HttpPost("[action]")]
97+
[HttpPost]
8698
public async Task<IActionResult> Logout()
8799
{
88100
await this.signInManager.SignOutAsync();
89101

90102
return this.Ok();
91103
}
104+
105+
private object GenerateJwtToken(string email, IdentityUser user)
106+
{
107+
var claims = new List<Claim>
108+
{
109+
new Claim(JwtRegisteredClaimNames.Sub, email),
110+
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
111+
new Claim(ClaimTypes.NameIdentifier, user.Id)
112+
};
113+
114+
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtKey"]));
115+
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
116+
var expires = DateTime.Now.AddDays(Convert.ToDouble(configuration["JwtExpireDays"]));
117+
118+
var token = new JwtSecurityToken(
119+
configuration["JwtIssuer"],
120+
configuration["JwtIssuer"],
121+
claims,
122+
expires: expires,
123+
signingCredentials: creds
124+
);
125+
126+
return new JwtSecurityTokenHandler().WriteToken(token);
127+
}
92128
}
93129
}

ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example/Startup.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@ namespace ASPNETCoreReactJS_Example
44
using Data;
55
using Data.Models;
66
using Extensions;
7+
using Microsoft.AspNetCore.Authentication.JwtBearer;
78
using Microsoft.AspNetCore.Builder;
89
using Microsoft.AspNetCore.Hosting;
910
using Microsoft.AspNetCore.Identity;
1011
using Microsoft.AspNetCore.SpaServices.Webpack;
1112
using Microsoft.EntityFrameworkCore;
1213
using Microsoft.Extensions.Configuration;
1314
using Microsoft.Extensions.DependencyInjection;
15+
using Microsoft.IdentityModel.Tokens;
1416
using Services.Implementations;
1517
using Services.Interfaces;
18+
using System;
19+
using System.IdentityModel.Tokens.Jwt;
20+
using System.Text;
1621

1722
public class Startup
1823
{
@@ -39,9 +44,32 @@ public void ConfigureServices(IServiceCollection services)
3944
.AddEntityFrameworkStores<ExampleDbContext>()
4045
.AddDefaultTokenProviders();
4146

47+
// Add JWT Authentication
48+
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
49+
4250
// Inject Application Services
4351
services.AddTransient<IUserService, UserService>();
4452

53+
services
54+
.AddAuthentication(options =>
55+
{
56+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
57+
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
58+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
59+
})
60+
.AddJwtBearer(cfg =>
61+
{
62+
cfg.RequireHttpsMetadata = false;
63+
cfg.SaveToken = true;
64+
cfg.TokenValidationParameters = new TokenValidationParameters
65+
{
66+
ValidIssuer = Configuration["JwtIssuer"],
67+
ValidAudience = Configuration["JwtIssuer"],
68+
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
69+
ClockSkew = TimeSpan.Zero // remove delay of token when expire
70+
};
71+
});
72+
4573
// AutoMapper, of course
4674
services.AddAutoMapper();
4775

@@ -72,6 +100,9 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
72100

73101
app.UseStaticFiles();
74102

103+
// Use Authentication
104+
app.UseAuthentication();
105+
75106
app.UseMvc(routes =>
76107
{
77108
routes.MapRoute(

ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example/Views/Home/Index.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@{
22
ViewData["Title"] = "Home Page";
33
}
4-
<div id="react-app">Loading...</div>
4+
<div id="react-app"><img class="loading" src="~/elipsis.svg" /></div>
55

66
@section scripts {
77
<script src="~/dist/main.js" asp-append-version="true"></script>

ASPNETCoreReactJS-Example/ASPNETCoreReactJS-Example/appsettings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@
66
"LogLevel": {
77
"Default": "Warning"
88
}
9-
}
9+
},
10+
"JwtKey": "SOME_RANDOM_KEY_DO_NOT_SHARE",
11+
"JwtIssuer": "https://127.0.0.1",
12+
"JwtExpireDays": 30
1013
}

0 commit comments

Comments
 (0)