>()
22 | .ToTable("UserClaims");
23 |
24 | base.OnModelCreating(builder);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/AuthDemo.Identity.Jwt/Middleware/SecureJwtMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.AspNetCore.Http;
3 |
4 | namespace AuthDemo.Identity.Jwt.Middleware
5 | {
6 | public class SecureJwtMiddleware
7 | {
8 | private readonly RequestDelegate _next;
9 |
10 | public SecureJwtMiddleware(RequestDelegate next) => _next = next;
11 |
12 | public async Task InvokeAsync(HttpContext context)
13 | {
14 | var token = context.Request.Cookies[".AspNetCore.Application.Id"];
15 |
16 | if (!string.IsNullOrEmpty(token))
17 | context.Request.Headers.Add("Authorization", "Bearer " + token);
18 |
19 | context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
20 | context.Response.Headers.Add("X-Xss-Protection", "1");
21 | context.Response.Headers.Add("X-Frame-Options", "DENY");
22 |
23 | await _next(context);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/components/ui/grid/grid.extensions.js:
--------------------------------------------------------------------------------
1 | const buildColumns = (items) => {
2 | let keyCollection = [];
3 | for(let key in items)
4 | keyCollection.push({ key: key, hidden: items[key].hidden ? true : false });
5 |
6 | return keyCollection;
7 | }
8 |
9 | const buildRows = (keys, item, actions) => {
10 | let items = [];
11 | for(let i in keys)
12 | items.push({ key: keys[i].key, value: item[keys[i].key], hidden: keys[i].hidden})
13 |
14 | if(actions.show)
15 | items.push({key:'actions', value: 'actions'});
16 |
17 | return items;
18 | }
19 |
20 | const buildHead = (array) => {
21 | let output = [];
22 | for(let key in array){
23 | output.push({
24 | id: key,
25 | value: array[key]
26 | });
27 | }
28 | return output;
29 | }
30 |
31 | export const gridExtensions = {
32 | buildColumns,
33 | buildRows,
34 | buildHead
35 | }
--------------------------------------------------------------------------------
/AuthDemo.Web/Pages/Error.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ErrorModel
3 | @{
4 | ViewData["Title"] = "Error";
5 | }
6 |
7 | Error.
8 | An error occurred while processing your request.
9 |
10 | @if (Model.ShowRequestId)
11 | {
12 |
13 | Request ID: @Model.RequestId
14 |
15 | }
16 |
17 | Development Mode
18 |
19 | Swapping to the Development environment displays detailed information about the error that occurred.
20 |
21 |
22 | The Development environment shouldn't be enabled for deployed applications.
23 | It can result in displaying sensitive information from exceptions to end users.
24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
25 | and restarting the app.
26 |
27 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Client App
15 |
16 |
17 |
18 |
19 | You need to enable JavaScript to run this app.
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/components/ui/form/form.component.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Field, ErrorMessage, Form as F } from 'formik';
3 | import { Button, types } from '../button';
4 | import { Label, FormField } from './form.elements';
5 | import { extensions as e } from './form.extensions';
6 |
7 | export const Form = (props) => {
8 | const renderFields = () =>
9 | e.buildForm(props.schema).map((item, i) =>
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
17 | const renderButton = () =>
18 |
19 |
20 |
21 |
22 | return (
23 |
24 | {renderFields()}
25 | {renderButton()}
26 | );
27 | }
--------------------------------------------------------------------------------
/AuthDemo.Application/AsyncInitialization/DataInitializerBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using AspNetCore.AsyncInitialization;
4 | using AuthDemo.Infrastructure.Data.Contexts;
5 | using Microsoft.Extensions.DependencyInjection;
6 |
7 | namespace AuthDemo.Application.AsyncInitialization
8 | {
9 | public abstract class DataInitializerBase : IAsyncInitializer
10 | {
11 | private readonly IServiceProvider _serviceProvider;
12 |
13 | protected DataInitializerBase(IServiceProvider serviceProvider)
14 | {
15 | _serviceProvider = serviceProvider;
16 | }
17 |
18 | public async Task InitializeAsync()
19 | {
20 | using (var scope = _serviceProvider.CreateScope())
21 | {
22 | using (var context = scope.ServiceProvider.GetRequiredService())
23 | {
24 | await InitializeAsync(context);
25 | await context.SaveChangesAsync();
26 | }
27 | }
28 | }
29 |
30 | protected abstract Task InitializeAsync(ApplicationContext dbContext);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/resources/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: 'Muli', sans-serif !important;
4 | font-size: 1rem;
5 | font-weight: 400;
6 | line-height: 1.5;
7 | color: #6b6f82;
8 | text-align: left;
9 | background: #f9f9fc;
10 | }
11 |
12 | h1, h2, h3, h4, h5, h6 {
13 | font-family: 'Muli', sans-serif;
14 | font-weight: 400;
15 | line-height: 1.1;
16 | color: #333;
17 | }
18 |
19 | .input-field label {
20 | color: #9e9e9e;
21 | }
22 |
23 | .input-field input:focus + label {
24 | color: #00bcd4!important;
25 | }
26 |
27 |
28 | .input-field input[type=text]:focus {
29 | border-bottom: 1px solid #00bcd4!important;
30 | box-shadow: 0 1px 0 0 #00bcd4!important;
31 | }
32 |
33 | .input-field input.valid {
34 | border-bottom: 1px solid #00bcd4!important;
35 | box-shadow: 0 1px 0 0 #00bcd4!important;
36 | }
37 |
38 | .input-field input.invalid {
39 | border-bottom: 1px solid #F44336!important;
40 | box-shadow: 0 1px 0 0 #F44336!important;
41 | }
42 |
43 | .input-field .prefix.active {
44 | color: #000;
45 | }
46 |
47 | .row{
48 | margin-bottom: 0;
49 | }
50 |
51 | .submit-progress-bar{
52 | width: 140px;
53 | margin: 0px;
54 | }
--------------------------------------------------------------------------------
/AuthDemo.Identity/Extensions/UserAccountExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Security.Claims;
5 | using AuthDemo.Domain.Entities.Identity;
6 | using Microsoft.IdentityModel.JsonWebTokens;
7 |
8 | namespace AuthDemo.Identity.Extensions
9 | {
10 | public static class UserAccountExtensions
11 | {
12 | public static ClaimsIdentity BuildClaims(this T user, IList roles)
13 | where T : User
14 | {
15 | var roleClaims = roles.Select(role => new Claim(ClaimsIdentity.DefaultRoleClaimType, role)).ToList();
16 |
17 | var defaultClaims = new List
18 | {
19 | new Claim(ClaimTypes.Name, user.UserName),
20 | new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
21 | new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
22 | new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.TimeOfDay.Ticks.ToString(),
23 | ClaimValueTypes.Integer64)
24 | };
25 |
26 | var claims = defaultClaims.Concat(roleClaims);
27 |
28 | return new ClaimsIdentity(claims, "token");
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/redux/actions/auth.actions.js:
--------------------------------------------------------------------------------
1 | import { authConstants as c } from '../constants';
2 | import { authService as s } from '../services';
3 | import history from '../helpers/history';
4 |
5 | const login = (username, password) => {
6 | return dispatch => {
7 | dispatch(request({ username }));
8 |
9 | s.login(username, password)
10 | .then(
11 | auth => {
12 | localStorage.setItem('auth', auth);
13 | dispatch(success(auth));
14 | history.push('/');
15 | },
16 | error => {
17 | history.push('/SignIn');
18 | dispatch(failure());
19 | }
20 | );
21 | };
22 | };
23 |
24 | const logout = dispatch => {
25 | localStorage.removeItem('auth');
26 | dispatch(logoutReq());
27 | }
28 |
29 | const request = (auth) => { return { type: c.LOGIN_REQUEST, auth }; }
30 | const success = (auth) => { return { type: c.LOGIN_SUCCESS, auth }; }
31 | const failure = () => { return { type: c.LOGIN_FAILURE }; }
32 | const logoutReq = () => { return { type: c.LOGOUT }; }
33 |
34 | export const authActions = {
35 | login,
36 | logout
37 | };
38 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/redux/api/api.axios.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import messages from '../resources/messages';
3 |
4 | const api = axios.create({ baseURL: process.env.REACT_APP_API_URL });
5 |
6 | api.interceptors.request.use(request => requestInterceptor(request));
7 |
8 | api.interceptors.response.use(
9 | response => successHandler(response),
10 | error => errorHandler(error)
11 | )
12 |
13 | const requestInterceptor = (request) => {
14 | request.withCredentials = true;
15 | return request;
16 | }
17 |
18 | const successHandler = (response) => {
19 | return new Promise((resolve, reject) => {
20 | if (response.status === 200) {
21 | resolve(response.data)
22 | }
23 | });
24 | }
25 |
26 | const errorHandler = (error) => {
27 | return new Promise((resolve, reject) => {
28 | // TODO: Обрабатывайте тут свои ошибки
29 |
30 | console.log(error)
31 | if (error.response.status === 400) {
32 | return reject(messages[error.response.data.subStatus])
33 | }
34 | if (error.response.status === 401 || error.response.status === 403) {
35 | if ([401, 403].indexOf(error.response.status) !== -1) {
36 | //authActions.logout();
37 | }
38 | reject();
39 | }
40 |
41 | return reject();
42 | })
43 | }
44 |
45 | export default api;
--------------------------------------------------------------------------------
/AuthDemo.Application/AsyncInitialization/IdentityInitializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using AuthDemo.Domain.Entities.Identity;
7 | using AuthDemo.Infrastructure.Data.Contexts;
8 |
9 | namespace AuthDemo.Application.AsyncInitialization
10 | {
11 | public class IdentityInitializer : DataInitializerBase
12 | {
13 | public IdentityInitializer(IServiceProvider serviceProvider)
14 | : base(serviceProvider)
15 | {
16 | }
17 |
18 | protected override async Task InitializeAsync(ApplicationContext dbContext)
19 | {
20 | var roleNames = Enum
21 | .GetNames(typeof(Role.Types));
22 |
23 | var existingRoles = dbContext
24 | .Roles
25 | .Select(x => x.Name)
26 | .ToArray();
27 |
28 | if (roleNames.Length != existingRoles.Length)
29 | {
30 | var newRoles = roleNames
31 | .Where(role => existingRoles.All(x => x != role))
32 | .Select(x => new Role(x)
33 | {
34 | NormalizedName = x.ToUpperInvariant()
35 | })
36 | .ToList();
37 |
38 | await dbContext.AddRangeAsync(newRoles);
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/containers/home/home.page.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { postsActions as actions } from '../../redux/actions';
4 | import Grid from '../../components/ui/grid/grid.component';
5 | import schema from './home.schema';
6 |
7 | class HomePage extends Component {
8 | state = { schema, loading: true};
9 |
10 | componentDidMount() {
11 | this.props.getAll();
12 | }
13 |
14 | componentDidUpdate(prevProps) {
15 | const { loading } = this.props;
16 | if (prevProps.loading !== loading)
17 | this.setState({ loading });
18 | }
19 |
20 | render() {
21 | const { schema, loading } = this.state;
22 | const { posts } = this.props;
23 | return (
24 |
25 | {
26 | loading
27 | ?
Loading
28 | :
29 | }
30 |
31 | );
32 | }
33 | }
34 |
35 |
36 | const mapStateToProps = (state) => {
37 | const { posts } = state;
38 | const { loading } = posts;
39 | return { posts, loading };
40 | }
41 |
42 | const mapDispatchToProps = (dispatch) => {
43 | return { getAll: () => { dispatch(actions.getAll()); } };
44 | }
45 |
46 |
47 | export default connect(mapStateToProps, mapDispatchToProps)(HomePage);
48 |
49 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.19.0",
7 | "flow": "^0.2.3",
8 | "formik": "^1.5.8",
9 | "jquery": "^3.5.0",
10 | "react": "^16.2.0",
11 | "react-cookies": "^0.1.1",
12 | "react-dom": "^16.2.1",
13 | "react-materialize": "^3.3.4",
14 | "react-redux": "^5.0.6",
15 | "react-router-bootstrap": "^0.24.4",
16 | "react-router-dom": "^4.2.2",
17 | "react-router-redux": "^5.0.0-alpha.8",
18 | "react-scripts": "^1.1.5",
19 | "redux": "^3.7.2",
20 | "redux-logger": "^3.0.6",
21 | "redux-thunk": "^2.3.0",
22 | "rimraf": "^2.6.2",
23 | "yup": "^0.27.0"
24 | },
25 | "devDependencies": {
26 | "ajv": "^6.0.0",
27 | "babel-eslint": "^7.2.3",
28 | "cross-env": "^5.2.0",
29 | "eslint": "^4.18.2",
30 | "eslint-config-react-app": "^2.1.0",
31 | "eslint-plugin-flowtype": "^2.50.3",
32 | "eslint-plugin-import": "^2.14.0",
33 | "eslint-plugin-jsx-a11y": "^5.1.1",
34 | "eslint-plugin-react": "^7.11.1"
35 | },
36 | "eslintConfig": {
37 | "extends": "react-app"
38 | },
39 | "scripts": {
40 | "build": "react-scripts build",
41 | "test": "cross-env CI=true react-scripts test --env=jsdom",
42 | "eject": "react-scripts eject",
43 | "lint": "eslint ./src/",
44 | "start": "set HTTPS=true&&rimraf ./build&&react-scripts start"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/AuthDemo.Web/Features/Posts/PostsController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using AuthDemo.Web.Controllers.Administrator;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace AuthDemo.Web.Features.Posts
6 | {
7 | public class PostsController : AdministratorApiController
8 | {
9 | [HttpGet]
10 | public IActionResult Get()
11 | {
12 | var posts = new List
13 | {
14 | new Post
15 | {
16 | Id = 1, Title = "Lorem ipsum",
17 | Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod"
18 | },
19 | new Post
20 | {
21 | Id = 2, Title = "Lorem ipsum",
22 | Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod"
23 | },
24 | new Post
25 | {
26 | Id = 3, Title = "Lorem ipsum",
27 | Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod"
28 | },
29 | new Post
30 | {
31 | Id = 4, Title = "Lorem ipsum",
32 | Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod"
33 | }
34 | };
35 |
36 |
37 | return Ok(posts);
38 | }
39 | }
40 |
41 | public class Post
42 | {
43 | public long Id { get; set; }
44 | public string Title { get; set; }
45 | public string Content { get; set; }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/components/ui/grid/styles.css:
--------------------------------------------------------------------------------
1 | .grid-pagination{
2 | box-sizing: border-box;
3 | display: inline-block;
4 | }
5 |
6 | .grid-scale{
7 | box-sizing: border-box;
8 | float: right;
9 | }
10 |
11 | .search-input-field{
12 | margin-bottom: 0px;
13 | padding: 0px;
14 | padding-right: 0px!important;
15 | }
16 |
17 | .table-card {
18 | margin-top: 25px;
19 | margin-bottom: 25px;
20 | }
21 |
22 | .table-card table td:first-child, .table-card table th:first-child {
23 | padding: 15px 5px 15px 25px;
24 | }
25 |
26 | .table-card table td:last-child, .table-card table th:last-child {
27 | padding: 15px 25px 15px 5px;
28 | }
29 |
30 | .table-card table th{
31 | color:#6b6f82;
32 | font-weight: 400;
33 | text-transform: uppercase;
34 | }
35 |
36 | .card-title{
37 | line-height: 32px;
38 | font-size: 18px;
39 | font-weight: 400;
40 | margin: 0;
41 | padding: 24px;
42 | padding-bottom: 1% !important;
43 | }
44 |
45 | .no-search-result-td{
46 | background: white;
47 | }
48 |
49 | .no-search-result-text{
50 | font-size: 2rem;
51 | }
52 |
53 | .fade-enter {
54 | opacity: 0.01;
55 | }
56 |
57 | .fade-enter-active {
58 | opacity: 1;
59 | transition: opacity 500ms ease-in;
60 | }
61 |
62 | .fade-exit {
63 | opacity: 1;
64 | }
65 |
66 | .fade-exit-active {
67 | opacity: 0.01;
68 | transition: opacity 500ms ease-in;
69 | }
70 |
71 | .grid-loader{
72 | margin: 0px;
73 | }
74 |
75 | .grid-loader-wrapper{
76 | height: 4px;
77 | }
78 |
79 | .grid-sort-cursor{
80 | cursor: pointer;
81 | }
82 |
83 | .sort-icon{
84 | display: inline-flex;
85 | vertical-align: top;
86 | }
87 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/components/ui/button/button.component.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { types } from './button.types';
3 | import { Link } from 'react-router-dom';
4 | import './styles.css';
5 |
6 | const Button = (props) => {
7 | switch (props.type) {
8 | case types.link:
9 | return (
10 |
11 | {props.title ? props.title : 'Link'} {props.icon ? props.icon : 'done'}
12 | )
13 | case types.submit:
14 | return (
15 |
16 | {props.loading
17 | ?
18 |
19 | {props.title ? props.title : 'Submit'} {props.icon ? props.icon : 'send'}
20 |
21 |
24 |
25 | :
26 | {props.title ? props.title : 'Submit'} {props.icon ? props.icon : 'send'}
27 |
28 | }
29 |
)
30 | default:
31 | return null;
32 | }
33 | };
34 |
35 | export { Button };
36 |
--------------------------------------------------------------------------------
/AuthDemo.Identity.Jwt/JwtOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using Microsoft.IdentityModel.Tokens;
4 |
5 | namespace AuthDemo.Identity.Jwt
6 | {
7 | public sealed class JwtOptions
8 | {
9 | internal JwtOptions(string issuer,
10 | string audience,
11 | SecurityKey signingKey,
12 | int tokenExpiryInMinutes = 5)
13 | {
14 | if (string.IsNullOrWhiteSpace(audience))
15 | throw new ArgumentNullException(
16 | $"{nameof(Audience)} is required in order to generate a JWT!");
17 |
18 | if (string.IsNullOrWhiteSpace(issuer))
19 | throw new ArgumentNullException(
20 | $"{nameof(Issuer)} is required in order to generate a JWT!");
21 |
22 | Audience = audience;
23 | Issuer = issuer;
24 | SigningKey = signingKey ??
25 | throw new ArgumentNullException(
26 | $"{nameof(SigningKey)} is required in order to generate a JWT!");
27 | TokenExpiryInMinutes = tokenExpiryInMinutes;
28 | }
29 |
30 | public JwtOptions(string issuer,
31 | string audience,
32 | string rawSigningKey,
33 | int tokenExpiryInMinutes = 5)
34 | : this(issuer,
35 | audience,
36 | new SymmetricSecurityKey(
37 | Encoding.ASCII.GetBytes(
38 | rawSigningKey)),
39 | tokenExpiryInMinutes)
40 | {
41 | }
42 |
43 | public SecurityKey SigningKey { get; }
44 |
45 | public string Issuer { get; }
46 |
47 | public string Audience { get; }
48 |
49 | public int TokenExpiryInMinutes { get; }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/AuthDemo.Identity.Jwt/Services/JwtTokenGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IdentityModel.Tokens.Jwt;
4 | using AuthDemo.Domain.Entities.Identity;
5 | using AuthDemo.Identity.Extensions;
6 | using AuthDemo.Identity.Jwt.Interfaces;
7 | using AuthDemo.Identity.Jwt.Models;
8 | using Microsoft.IdentityModel.Tokens;
9 |
10 | namespace AuthDemo.Identity.Jwt.Services
11 | {
12 | public sealed class JwtTokenGenerator : IJwtTokenGenerator
13 | {
14 | private readonly JwtOptions _tokenOptions;
15 | public JwtTokenGenerator(JwtOptions tokenOptions)
16 | {
17 | _tokenOptions = tokenOptions ??
18 | throw new ArgumentNullException(
19 | $"An instance of valid {nameof(JwtOptions)} must be passed in order to generate a JWT!");
20 | }
21 |
22 | public JwtTokenResult Generate(User user, IList roles)
23 | {
24 | var expiration = TimeSpan.FromMinutes(_tokenOptions.TokenExpiryInMinutes);
25 | var claimsIdentity = user.BuildClaims(roles);
26 |
27 | var jwt = new JwtSecurityToken(
28 | _tokenOptions.Issuer,
29 | _tokenOptions.Audience,
30 | claimsIdentity.Claims,
31 | DateTime.UtcNow,
32 | DateTime.UtcNow.Add(expiration),
33 | new SigningCredentials(
34 | _tokenOptions.SigningKey,
35 | SecurityAlgorithms.HmacSha256));
36 |
37 | var accessToken = new JwtSecurityTokenHandler().WriteToken(jwt);
38 |
39 | return new JwtTokenResult
40 | {
41 | AccessToken = accessToken,
42 | Expires = expiration
43 | };
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/AuthDemo.Web/Features/Authorization/AuthorizeController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using AuthDemo.Domain.Entities.Identity;
4 | using AuthDemo.Identity.Jwt.Interfaces;
5 | using AuthDemo.Web.Controllers;
6 | using AuthDemo.Web.Features.Authorization.Models;
7 | using Microsoft.AspNetCore.Authorization;
8 | using Microsoft.AspNetCore.Http;
9 | using Microsoft.AspNetCore.Identity;
10 | using Microsoft.AspNetCore.Mvc;
11 |
12 | namespace AuthDemo.Web.Features.Authorization
13 | {
14 | [Route("[controller]")]
15 | public class AuthorizeController : ApiControllerBase
16 | {
17 | private readonly IJwtTokenGenerator _jwtTokenGenerator;
18 | private readonly SignInManager _signInManager;
19 | private readonly UserManager _userManager;
20 |
21 | public AuthorizeController(IJwtTokenGenerator jwtTokenGenerator, SignInManager signInManager,
22 | UserManager userManager)
23 | {
24 | _jwtTokenGenerator = jwtTokenGenerator;
25 | _signInManager = signInManager;
26 | _userManager = userManager;
27 | }
28 |
29 | [AllowAnonymous]
30 | [HttpPost]
31 | public async Task Post([FromBody] UserCredentials model)
32 | {
33 | var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, true, true);
34 | if (result.IsLockedOut || !result.Succeeded)
35 | return Unauthorized();
36 |
37 | var user = await _userManager.FindByNameAsync(model.Username);
38 | if (user == null)
39 | return Unauthorized();
40 |
41 | var userRoles = await _userManager.GetRolesAsync(user);
42 | var tokenResult = _jwtTokenGenerator.Generate(user, userRoles);
43 |
44 | HttpContext.Response.Cookies.Append(
45 | ".AspNetCore.Application.Id",
46 | tokenResult.AccessToken,
47 | new CookieOptions { MaxAge = TimeSpan.FromMinutes(60) });
48 |
49 | return Ok(tokenResult.Expires);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/AuthDemo.Application/AsyncInitialization/SeedInitializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using AuthDemo.Application.Options;
7 | using AuthDemo.Domain.Entities.Identity;
8 | using AuthDemo.Infrastructure.Data.Contexts;
9 | using Microsoft.AspNetCore.Identity;
10 | using Microsoft.Extensions.Options;
11 |
12 | namespace AuthDemo.Application.AsyncInitialization
13 | {
14 | public class SeedInitializer : DataInitializerBase
15 | {
16 | private readonly IOptions _options;
17 | private readonly UserManager _userManager;
18 | private readonly IPasswordHasher _hasher;
19 |
20 | public SeedInitializer(IServiceProvider serviceProvider, IOptions options,
21 | UserManager userManager, IPasswordHasher hasher)
22 | : base(serviceProvider)
23 | {
24 | _hasher = hasher;
25 | _options = options;
26 | _userManager = userManager;
27 | }
28 |
29 | protected override async Task InitializeAsync(ApplicationContext dbContext)
30 | {
31 | await InitializeUsers();
32 | }
33 |
34 | private async Task InitializeUsers()
35 | {
36 | var users = _options
37 | .Value
38 | .Administrators
39 | .Select(x => new User
40 | {
41 | UserName = x.UserName,
42 | NormalizedUserName = x.UserName.ToUpperInvariant()
43 | })
44 | .ToList();
45 |
46 | var existingUsers = await _userManager.GetUsersInRoleAsync(Role.Types.Administrator.ToString());
47 |
48 | if (users.Any() && users.Count > existingUsers.Count)
49 | {
50 | var newUsers = users
51 | .Where(x => existingUsers.All(u => u.UserName != x.UserName))
52 | .ToList();
53 |
54 | foreach (var user in newUsers)
55 | {
56 | user.PasswordHash = _hasher.HashPassword(user, "Qwe123!");
57 | await _userManager.CreateAsync(user);
58 | await _userManager.AddToRoleAsync(user, Role.Types.Administrator.ToString());
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/containers/login/login.page.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { CardWrapper, CardForm, CardContent, CardTitle, types } from '../../components/ui/cards';
4 | import { Formik } from 'formik';
5 | import { Form } from '../../components/ui/form/form.component';
6 | import { authActions as a } from '../../redux/actions';
7 | import schema from './login.schema';
8 | import rules from '../../validation/yup.validation';
9 | import './styles.css';
10 | import * as Yup from 'yup';
11 |
12 | class LoginPage extends Component {
13 | state = {
14 | schema
15 | };
16 |
17 | renderLoader() {
18 | const { loading } = this.props;
19 | return (
20 | loading
21 | ?
24 | :
25 | );
26 | }
27 |
28 | onSubmit = (username, password) => {
29 | console.log({ username, password })
30 | }
31 |
32 | render() {
33 | const { schema } = this.state;
34 | const { login } = this.props;
35 | return (
36 |
37 |
38 |
39 |
40 | { login(username, password) }}
47 | render={({ errors, touched }) => (
48 |
49 | )}
50 | />
51 |
52 |
53 | {this.renderLoader()}
54 |
55 | );
56 | }
57 | }
58 |
59 | const mapStateToProps = (state) => {
60 | const { loading } = state.auth;
61 | return {
62 | loading
63 | };
64 | }
65 |
66 | const mapDispatchToProps = (dispatch) => {
67 | return {
68 | logout: () => {
69 | dispatch(a.logout());
70 | },
71 | login: (username, password) => {
72 | dispatch(a.login(username, password));
73 | }
74 | };
75 | }
76 |
77 | export default connect(mapStateToProps, mapDispatchToProps)(LoginPage);
--------------------------------------------------------------------------------
/AuthDemo.Web/AuthDemo.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | true
6 | Latest
7 | false
8 | ClientApp\
9 | $(DefaultItemExcludes);$(SpaRoot)node_modules\**
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | %(DistFiles.Identity)
51 | PreserveNewest
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/components/ui/grid/grid.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { gridExtensions as ex } from './grid.extensions';
3 | import { Link } from 'react-router-dom';
4 | import { Table } from 'react-materialize';
5 | import { CardTitle, CardGrid } from '../cards'
6 | import { Icon } from '../icon/icon.component';
7 | import { Button, types as ButtonTypes } from '../button';
8 | import './styles.css';
9 |
10 | class Grid extends Component {
11 | renderTHead = () =>
12 |
13 |
14 | {this.renderHead()}
15 | {this.props.schema.actions.show &&
16 |
17 | {this.props.schema.actions.name}
18 |
19 | }
20 |
21 |
22 |
23 | renderHead = () =>
24 | ex.buildHead(this.props.schema.columns).map((item) =>
25 |
26 | {item.value.name}
27 |
28 | );
29 |
30 | renderTBody = () =>
31 |
32 | {this.props.data.map((item, i) =>
33 |
34 | {this.renderItem(item)}
35 | )}
36 |
37 |
38 | renderItem(item) {
39 | const columns = ex.buildColumns(this.props.schema.columns);
40 | const rows = ex.buildRows(columns, item, this.props.schema.actions);
41 | const id = item.id;
42 |
43 | return rows.map((item, i) => {
44 | switch (item.value) {
45 | case 'actions': return this.renderActions(this.props.schema.actions, id);
46 | default: return this.renderDefault(item);
47 | }
48 | });
49 | }
50 |
51 | renderDefault = (item) =>
52 |
53 | {item.value}
54 |
55 |
56 | renderActions = (actions, id) =>
57 |
58 |
59 |
60 |
61 |
62 |
63 | renderTable = () =>
64 | !this.props.loading &&
65 |
66 | {this.renderTHead()}
67 | {this.renderTBody()}
68 |
69 |
70 | renderTitle = () =>
71 |
72 |
73 |
74 |
75 | {this.props.schema.search && this.renderSearch()}
76 |
77 |
78 | renderButton = () =>
79 | this.props.schema.create.show &&
80 |
81 |
87 |
88 |
89 | render = () =>
90 |
91 | {this.renderTitle()}
92 | {this.renderTable()}
93 | {this.renderButton()}
94 |
95 | }
96 |
97 | export default Grid;
--------------------------------------------------------------------------------
/AuthDemo.Identity.Jwt/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AuthDemo.Domain.Entities.Identity;
3 | using AuthDemo.Identity.Jwt.Interfaces;
4 | using AuthDemo.Identity.Jwt.Services;
5 | using AuthDemo.Infrastructure.Data.Contexts;
6 | using Microsoft.AspNetCore.Authentication.JwtBearer;
7 | using Microsoft.AspNetCore.Hosting;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.IdentityModel.Tokens;
11 | using static AuthDemo.Identity.Contracts;
12 |
13 | namespace AuthDemo.Identity.Jwt.Extensions
14 | {
15 | public static class ServiceCollectionExtensions
16 | {
17 | public static IServiceCollection AddApiJwtAuthentication(
18 | this IServiceCollection services,
19 | JwtOptions tokenOptions,
20 | IHostingEnvironment environment)
21 | {
22 | if (tokenOptions == null)
23 | throw new ArgumentNullException(
24 | $"{nameof(tokenOptions)} is a required parameter. " +
25 | "Please make sure you've provided a valid instance with the appropriate values configured.");
26 |
27 | services.AddScoped(serviceProvider =>
28 | new JwtTokenGenerator(tokenOptions));
29 |
30 | services.AddIdentity(opt =>
31 | {
32 | opt.Password.RequiredLength = 10;
33 | opt.Password.RequireDigit = true;
34 | opt.Password.RequireLowercase = true;
35 | opt.Password.RequireUppercase = true;
36 | opt.Password.RequireNonAlphanumeric = true;
37 | opt.Lockout.AllowedForNewUsers = true;
38 | opt.Lockout.MaxFailedAccessAttempts = 5;
39 | opt.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(environment.IsDevelopment() ? 0 : 15);
40 | })
41 | .AddEntityFrameworkStores()
42 | .AddDefaultTokenProviders();
43 |
44 | services.AddAuthentication(options =>
45 | {
46 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
47 | options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
48 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
49 | })
50 | .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
51 | {
52 | options.RequireHttpsMetadata = true;
53 | options.SaveToken = true;
54 | options.TokenValidationParameters = new TokenValidationParameters
55 | {
56 | ClockSkew = TimeSpan.Zero,
57 | ValidateAudience = false,
58 | ValidAudience = tokenOptions.Audience,
59 | ValidateIssuer = false,
60 | ValidIssuer = tokenOptions.Issuer,
61 | IssuerSigningKey = tokenOptions.SigningKey,
62 | ValidateIssuerSigningKey = true,
63 | RequireExpirationTime = true,
64 | ValidateLifetime = true
65 | };
66 | });
67 |
68 | services.AddAuthorization(options =>
69 | {
70 | options.AddPolicy(UserPolicy, policy => policy.RequireRole(nameof(Role.Types.User)));
71 | options.AddPolicy(AdministratorPolicy, policy => policy.RequireRole(nameof(Role.Types.Administrator)));
72 | });
73 |
74 | return services;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/AuthDemo.Web/Startup.cs:
--------------------------------------------------------------------------------
1 | using AuthDemo.Application.AsyncInitialization;
2 | using AuthDemo.Application.Options;
3 | using AuthDemo.Identity.Jwt;
4 | using AuthDemo.Identity.Jwt.Extensions;
5 | using AuthDemo.Infrastructure.Data.Contexts;
6 | using Microsoft.AspNetCore.Builder;
7 | using Microsoft.AspNetCore.CookiePolicy;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.AspNetCore.Http;
10 | using Microsoft.AspNetCore.Mvc;
11 | using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
12 | using Microsoft.EntityFrameworkCore;
13 | using Microsoft.EntityFrameworkCore.Diagnostics;
14 | using Microsoft.Extensions.Configuration;
15 | using Microsoft.Extensions.DependencyInjection;
16 |
17 | namespace AuthDemo.Web
18 | {
19 | public class Startup
20 | {
21 | public Startup(IConfiguration configuration, IHostingEnvironment environment)
22 | {
23 | Configuration = configuration;
24 | Environment = environment;
25 | }
26 |
27 | public IHostingEnvironment Environment { get; }
28 | public IConfiguration Configuration { get; }
29 |
30 | public void ConfigureServices(IServiceCollection services)
31 | {
32 | services.AddCors();
33 |
34 | services.AddDbContext(opt =>
35 | opt.UseSqlServer(Configuration.GetConnectionString("Connection"))
36 | .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)));
37 |
38 | services.AddMvc()
39 | .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
40 |
41 | var section = Configuration.GetSection("AuthOptions");
42 | var options = section.Get();
43 | var jwtOptions = new JwtOptions(options.Audience, options.Issuer, options.Secret, options.Lifetime);
44 | services.AddApiJwtAuthentication(jwtOptions, Environment);
45 |
46 | if (Configuration.GetSection("React").Get().UseSpa)
47 | services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/build"; });
48 |
49 | services.Configure(Configuration.GetSection("Seed"));
50 | services.Configure(section);
51 | services.AddAsyncInitializer();
52 | services.AddAsyncInitializer();
53 | services.AddAsyncInitializer();
54 | }
55 |
56 | public void Configure(IApplicationBuilder app, IHostingEnvironment env)
57 | {
58 | if (Environment.IsDevelopment())
59 | app.UseDeveloperExceptionPage();
60 | else
61 | app.UseHsts();
62 |
63 | app.UseHttpsRedirection();
64 | app.UseStaticFiles();
65 |
66 | app.UseCookiePolicy(new CookiePolicyOptions
67 | {
68 | MinimumSameSitePolicy = SameSiteMode.Strict,
69 | HttpOnly = HttpOnlyPolicy.Always,
70 | Secure = CookieSecurePolicy.Always
71 | });
72 |
73 | if (Environment.IsDevelopment())
74 | app.UseCors(x => x
75 | .WithOrigins("https://localhost:3000")
76 | .AllowCredentials()
77 | .AllowAnyMethod()
78 | .AllowAnyHeader());
79 |
80 | app.UseSecureJwt();
81 | app.UseAuthentication();
82 | app.UseMvc();
83 |
84 | if (Configuration.GetSection("React").Get().UseSpa)
85 | {
86 | app.UseSpaStaticFiles();
87 | app.UseSpa(spa =>
88 | {
89 | spa.Options.SourcePath = "ClientApp";
90 |
91 | if (env.IsDevelopment())
92 | spa.UseReactDevelopmentServer("start");
93 | });
94 | }
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/AuthDemo.Web/ClientApp/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 | } else {
39 | // Is not local host. Just register service worker
40 | registerValidSW(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | bin/
23 | Bin/
24 | obj/
25 | Obj/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | /wwwroot/dist/
30 |
31 | # MSTest test Results
32 | [Tt]est[Rr]esult*/
33 | [Bb]uild[Ll]og.*
34 |
35 | # NUNIT
36 | *.VisualState.xml
37 | TestResult.xml
38 |
39 | # Build Results of an ATL Project
40 | [Dd]ebugPS/
41 | [Rr]eleasePS/
42 | dlldata.c
43 |
44 | *_i.c
45 | *_p.c
46 | *_i.h
47 | *.ilk
48 | *.meta
49 | *.obj
50 | *.pch
51 | *.pdb
52 | *.pgc
53 | *.pgd
54 | *.rsp
55 | *.sbr
56 | *.tlb
57 | *.tli
58 | *.tlh
59 | *.tmp
60 | *.tmp_proj
61 | *.log
62 | *.vspscc
63 | *.vssscc
64 | .builds
65 | *.pidb
66 | *.svclog
67 | *.scc
68 |
69 | # Chutzpah Test files
70 | _Chutzpah*
71 |
72 | # Visual C++ cache files
73 | ipch/
74 | *.aps
75 | *.ncb
76 | *.opendb
77 | *.opensdf
78 | *.sdf
79 | *.cachefile
80 |
81 | # Visual Studio profiler
82 | *.psess
83 | *.vsp
84 | *.vspx
85 | *.sap
86 |
87 | # TFS 2012 Local Workspace
88 | $tf/
89 |
90 | # Guidance Automation Toolkit
91 | *.gpState
92 |
93 | # ReSharper is a .NET coding add-in
94 | _ReSharper*/
95 | *.[Rr]e[Ss]harper
96 | *.DotSettings.user
97 |
98 | # JustCode is a .NET coding add-in
99 | .JustCode
100 |
101 | # TeamCity is a build add-in
102 | _TeamCity*
103 |
104 | # DotCover is a Code Coverage Tool
105 | *.dotCover
106 |
107 | # NCrunch
108 | _NCrunch_*
109 | .*crunch*.local.xml
110 | nCrunchTemp_*
111 |
112 | # MightyMoose
113 | *.mm.*
114 | AutoTest.Net/
115 |
116 | # Web workbench (sass)
117 | .sass-cache/
118 |
119 | # Installshield output folder
120 | [Ee]xpress/
121 |
122 | # DocProject is a documentation generator add-in
123 | DocProject/buildhelp/
124 | DocProject/Help/*.HxT
125 | DocProject/Help/*.HxC
126 | DocProject/Help/*.hhc
127 | DocProject/Help/*.hhk
128 | DocProject/Help/*.hhp
129 | DocProject/Help/Html2
130 | DocProject/Help/html
131 |
132 | # Click-Once directory
133 | publish/
134 |
135 | # Publish Web Output
136 | *.[Pp]ublish.xml
137 | *.azurePubxml
138 | # TODO: Comment the next line if you want to checkin your web deploy settings
139 | # but database connection strings (with potential passwords) will be unencrypted
140 | *.pubxml
141 | *.publishproj
142 |
143 | # NuGet Packages
144 | *.nupkg
145 | # The packages folder can be ignored because of Package Restore
146 | **/packages/*
147 | # except build/, which is used as an MSBuild target.
148 | !**/packages/build/
149 | # Uncomment if necessary however generally it will be regenerated when needed
150 | #!**/packages/repositories.config
151 |
152 | # Microsoft Azure Build Output
153 | csx/
154 | *.build.csdef
155 |
156 | # Microsoft Azure Emulator
157 | ecf/
158 | rcf/
159 |
160 | # Microsoft Azure ApplicationInsights config file
161 | ApplicationInsights.config
162 |
163 | # Windows Store app package directory
164 | AppPackages/
165 | BundleArtifacts/
166 |
167 | # Visual Studio cache files
168 | # files ending in .cache can be ignored
169 | *.[Cc]ache
170 | # but keep track of directories ending in .cache
171 | !*.[Cc]ache/
172 |
173 | # Others
174 | ClientBin/
175 | ~$*
176 | *~
177 | *.dbmdl
178 | *.dbproj.schemaview
179 | *.pfx
180 | *.publishsettings
181 | orleans.codegen.cs
182 |
183 | /node_modules
184 |
185 | # RIA/Silverlight projects
186 | Generated_Code/
187 |
188 | # Backup & report files from converting an old project file
189 | # to a newer Visual Studio version. Backup files are not needed,
190 | # because we have git ;-)
191 | _UpgradeReport_Files/
192 | Backup*/
193 | UpgradeLog*.XML
194 | UpgradeLog*.htm
195 |
196 | # SQL Server files
197 | *.mdf
198 | *.ldf
199 |
200 | # Business Intelligence projects
201 | *.rdl.data
202 | *.bim.layout
203 | *.bim_*.settings
204 |
205 | # Microsoft Fakes
206 | FakesAssemblies/
207 |
208 | # GhostDoc plugin setting file
209 | *.GhostDoc.xml
210 |
211 | # Node.js Tools for Visual Studio
212 | .ntvs_analysis.dat
213 |
214 | # Visual Studio 6 build log
215 | *.plg
216 |
217 | # Visual Studio 6 workspace options file
218 | *.opt
219 |
220 | # Visual Studio LightSwitch build output
221 | **/*.HTMLClient/GeneratedArtifacts
222 | **/*.DesktopClient/GeneratedArtifacts
223 | **/*.DesktopClient/ModelManifest.xml
224 | **/*.Server/GeneratedArtifacts
225 | **/*.Server/ModelManifest.xml
226 | _Pvt_Extensions
227 |
228 | # Paket dependency manager
229 | .paket/paket.exe
230 |
231 | # FAKE - F# Make
232 | .fake/
233 |
--------------------------------------------------------------------------------
/AuthDemo.Infrastructure.Data/Migrations/ApplicationContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using AuthDemo.Infrastructure.Data.Contexts;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace AuthDemo.Infrastructure.Data.Migrations
10 | {
11 | [DbContext(typeof(ApplicationContext))]
12 | partial class ApplicationContextModelSnapshot : ModelSnapshot
13 | {
14 | protected override void BuildModel(ModelBuilder modelBuilder)
15 | {
16 | #pragma warning disable 612, 618
17 | modelBuilder
18 | .HasAnnotation("ProductVersion", "2.2.1-servicing-10028")
19 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
21 |
22 | modelBuilder.Entity("AuthDemo.Domain.Entities.Identity.Role", b =>
23 | {
24 | b.Property("Id")
25 | .ValueGeneratedOnAdd()
26 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
27 |
28 | b.Property("ConcurrencyStamp");
29 |
30 | b.Property("Name");
31 |
32 | b.Property("NormalizedName");
33 |
34 | b.HasKey("Id");
35 |
36 | b.ToTable("Roles");
37 | });
38 |
39 | modelBuilder.Entity("AuthDemo.Domain.Entities.Identity.User", b =>
40 | {
41 | b.Property("Id")
42 | .ValueGeneratedOnAdd()
43 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
44 |
45 | b.Property("AccessFailedCount");
46 |
47 | b.Property("ConcurrencyStamp");
48 |
49 | b.Property("Email");
50 |
51 | b.Property("EmailConfirmed");
52 |
53 | b.Property("LockoutEnabled");
54 |
55 | b.Property("LockoutEnd");
56 |
57 | b.Property("NormalizedEmail");
58 |
59 | b.Property("NormalizedUserName");
60 |
61 | b.Property("PasswordHash");
62 |
63 | b.Property("PhoneNumber");
64 |
65 | b.Property("PhoneNumberConfirmed");
66 |
67 | b.Property("SecurityStamp");
68 |
69 | b.Property("TwoFactorEnabled");
70 |
71 | b.Property("UserName");
72 |
73 | b.HasKey("Id");
74 |
75 | b.ToTable("Users");
76 | });
77 |
78 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
79 | {
80 | b.Property("Id")
81 | .ValueGeneratedOnAdd()
82 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
83 |
84 | b.Property("ClaimType");
85 |
86 | b.Property("ClaimValue");
87 |
88 | b.Property("RoleId");
89 |
90 | b.HasKey("Id");
91 |
92 | b.ToTable("IdentityRoleClaims");
93 | });
94 |
95 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
96 | {
97 | b.Property("Id")
98 | .ValueGeneratedOnAdd()
99 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
100 |
101 | b.Property("ClaimType");
102 |
103 | b.Property("ClaimValue");
104 |
105 | b.Property("UserId");
106 |
107 | b.HasKey("Id");
108 |
109 | b.ToTable("UserClaims");
110 | });
111 |
112 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
113 | {
114 | b.Property("UserId");
115 |
116 | b.Property("RoleId");
117 |
118 | b.HasKey("UserId", "RoleId");
119 |
120 | b.ToTable("UserRoles");
121 | });
122 | #pragma warning restore 612, 618
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/AuthDemo.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28307.271
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthDemo.Web", "AuthDemo.Web\AuthDemo.Web.csproj", "{978AA805-0CC1-45BB-AE5D-A8B24566032E}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthDemo.Application", "AuthDemo.Application\AuthDemo.Application.csproj", "{B2079979-9FAF-489B-BC58-9FFA0BB96216}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{0508417D-AC64-4CFF-97B3-C17E0C3D3CD1}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{DB848C64-90F7-48AE-8D9C-CFB8390B1A8E}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthDemo.Infrastructure.Data", "AuthDemo.Infrastructure.Data\AuthDemo.Infrastructure.Data.csproj", "{F76D8947-28D8-4169-A869-2D53760E71EF}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{198D1874-623C-4747-B172-5A23499AC5F0}"
17 | EndProject
18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthDemo.Domain", "AuthDemo.Domain\AuthDemo.Domain.csproj", "{5976EC1D-00F1-4CC2-B586-A4B9DEA3F204}"
19 | EndProject
20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{D3199D5E-4098-4FA6-BCA9-F5B252C1F562}"
21 | EndProject
22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthDemo.Identity", "AuthDemo.Identity\AuthDemo.Identity.csproj", "{DAC42674-7A29-439C-ADCD-913E7DC6C2DB}"
23 | EndProject
24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthDemo.Identity.Jwt", "AuthDemo.Identity.Jwt\AuthDemo.Identity.Jwt.csproj", "{0ADD0968-8B49-49F3-83E5-2B77B930D221}"
25 | EndProject
26 | Global
27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
28 | Debug|Any CPU = Debug|Any CPU
29 | Release|Any CPU = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
32 | {978AA805-0CC1-45BB-AE5D-A8B24566032E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {978AA805-0CC1-45BB-AE5D-A8B24566032E}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {978AA805-0CC1-45BB-AE5D-A8B24566032E}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {978AA805-0CC1-45BB-AE5D-A8B24566032E}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {B2079979-9FAF-489B-BC58-9FFA0BB96216}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {B2079979-9FAF-489B-BC58-9FFA0BB96216}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {B2079979-9FAF-489B-BC58-9FFA0BB96216}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {B2079979-9FAF-489B-BC58-9FFA0BB96216}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {F76D8947-28D8-4169-A869-2D53760E71EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {F76D8947-28D8-4169-A869-2D53760E71EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {F76D8947-28D8-4169-A869-2D53760E71EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {F76D8947-28D8-4169-A869-2D53760E71EF}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {5976EC1D-00F1-4CC2-B586-A4B9DEA3F204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {5976EC1D-00F1-4CC2-B586-A4B9DEA3F204}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {5976EC1D-00F1-4CC2-B586-A4B9DEA3F204}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {5976EC1D-00F1-4CC2-B586-A4B9DEA3F204}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {DAC42674-7A29-439C-ADCD-913E7DC6C2DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {DAC42674-7A29-439C-ADCD-913E7DC6C2DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {DAC42674-7A29-439C-ADCD-913E7DC6C2DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {DAC42674-7A29-439C-ADCD-913E7DC6C2DB}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {0ADD0968-8B49-49F3-83E5-2B77B930D221}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {0ADD0968-8B49-49F3-83E5-2B77B930D221}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {0ADD0968-8B49-49F3-83E5-2B77B930D221}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {0ADD0968-8B49-49F3-83E5-2B77B930D221}.Release|Any CPU.Build.0 = Release|Any CPU
56 | EndGlobalSection
57 | GlobalSection(SolutionProperties) = preSolution
58 | HideSolutionNode = FALSE
59 | EndGlobalSection
60 | GlobalSection(NestedProjects) = preSolution
61 | {B2079979-9FAF-489B-BC58-9FFA0BB96216} = {0508417D-AC64-4CFF-97B3-C17E0C3D3CD1}
62 | {F76D8947-28D8-4169-A869-2D53760E71EF} = {DB848C64-90F7-48AE-8D9C-CFB8390B1A8E}
63 | {5976EC1D-00F1-4CC2-B586-A4B9DEA3F204} = {198D1874-623C-4747-B172-5A23499AC5F0}
64 | {D3199D5E-4098-4FA6-BCA9-F5B252C1F562} = {0508417D-AC64-4CFF-97B3-C17E0C3D3CD1}
65 | {DAC42674-7A29-439C-ADCD-913E7DC6C2DB} = {D3199D5E-4098-4FA6-BCA9-F5B252C1F562}
66 | {0ADD0968-8B49-49F3-83E5-2B77B930D221} = {D3199D5E-4098-4FA6-BCA9-F5B252C1F562}
67 | EndGlobalSection
68 | GlobalSection(ExtensibilityGlobals) = postSolution
69 | SolutionGuid = {660919AF-05C9-467D-86B0-95CB2BDE79E3}
70 | EndGlobalSection
71 | EndGlobal
72 |
--------------------------------------------------------------------------------
/AuthDemo.Infrastructure.Data/Migrations/20190911193047_Initial.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using AuthDemo.Infrastructure.Data.Contexts;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace AuthDemo.Infrastructure.Data.Migrations
11 | {
12 | [DbContext(typeof(ApplicationContext))]
13 | [Migration("20190911193047_Initial")]
14 | partial class Initial
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "2.2.1-servicing-10028")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("AuthDemo.Domain.Entities.Identity.Role", b =>
25 | {
26 | b.Property("Id")
27 | .ValueGeneratedOnAdd()
28 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
29 |
30 | b.Property("ConcurrencyStamp");
31 |
32 | b.Property("Name");
33 |
34 | b.Property("NormalizedName");
35 |
36 | b.HasKey("Id");
37 |
38 | b.ToTable("Roles");
39 | });
40 |
41 | modelBuilder.Entity("AuthDemo.Domain.Entities.Identity.User", b =>
42 | {
43 | b.Property("Id")
44 | .ValueGeneratedOnAdd()
45 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
46 |
47 | b.Property("AccessFailedCount");
48 |
49 | b.Property("ConcurrencyStamp");
50 |
51 | b.Property("Email");
52 |
53 | b.Property("EmailConfirmed");
54 |
55 | b.Property("LockoutEnabled");
56 |
57 | b.Property("LockoutEnd");
58 |
59 | b.Property("NormalizedEmail");
60 |
61 | b.Property("NormalizedUserName");
62 |
63 | b.Property("PasswordHash");
64 |
65 | b.Property("PhoneNumber");
66 |
67 | b.Property("PhoneNumberConfirmed");
68 |
69 | b.Property("SecurityStamp");
70 |
71 | b.Property("TwoFactorEnabled");
72 |
73 | b.Property("UserName");
74 |
75 | b.HasKey("Id");
76 |
77 | b.ToTable("Users");
78 | });
79 |
80 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
81 | {
82 | b.Property("Id")
83 | .ValueGeneratedOnAdd()
84 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
85 |
86 | b.Property("ClaimType");
87 |
88 | b.Property("ClaimValue");
89 |
90 | b.Property("RoleId");
91 |
92 | b.HasKey("Id");
93 |
94 | b.ToTable("IdentityRoleClaims");
95 | });
96 |
97 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
98 | {
99 | b.Property("Id")
100 | .ValueGeneratedOnAdd()
101 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
102 |
103 | b.Property("ClaimType");
104 |
105 | b.Property("ClaimValue");
106 |
107 | b.Property("UserId");
108 |
109 | b.HasKey("Id");
110 |
111 | b.ToTable("UserClaims");
112 | });
113 |
114 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
115 | {
116 | b.Property("UserId");
117 |
118 | b.Property("RoleId");
119 |
120 | b.HasKey("UserId", "RoleId");
121 |
122 | b.ToTable("UserRoles");
123 | });
124 | #pragma warning restore 612, 618
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/AuthDemo.Infrastructure.Data/Migrations/20190911193047_Initial.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Metadata;
3 | using Microsoft.EntityFrameworkCore.Migrations;
4 |
5 | namespace AuthDemo.Infrastructure.Data.Migrations
6 | {
7 | public partial class Initial : Migration
8 | {
9 | protected override void Up(MigrationBuilder migrationBuilder)
10 | {
11 | migrationBuilder.CreateTable(
12 | name: "IdentityRoleClaims",
13 | columns: table => new
14 | {
15 | Id = table.Column(nullable: false)
16 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
17 | RoleId = table.Column(nullable: false),
18 | ClaimType = table.Column(nullable: true),
19 | ClaimValue = table.Column(nullable: true)
20 | },
21 | constraints: table =>
22 | {
23 | table.PrimaryKey("PK_IdentityRoleClaims", x => x.Id);
24 | });
25 |
26 | migrationBuilder.CreateTable(
27 | name: "Roles",
28 | columns: table => new
29 | {
30 | Id = table.Column(nullable: false)
31 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
32 | Name = table.Column(nullable: true),
33 | NormalizedName = table.Column(nullable: true),
34 | ConcurrencyStamp = table.Column(nullable: true)
35 | },
36 | constraints: table =>
37 | {
38 | table.PrimaryKey("PK_Roles", x => x.Id);
39 | });
40 |
41 | migrationBuilder.CreateTable(
42 | name: "UserClaims",
43 | columns: table => new
44 | {
45 | Id = table.Column(nullable: false)
46 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
47 | UserId = table.Column(nullable: false),
48 | ClaimType = table.Column(nullable: true),
49 | ClaimValue = table.Column(nullable: true)
50 | },
51 | constraints: table =>
52 | {
53 | table.PrimaryKey("PK_UserClaims", x => x.Id);
54 | });
55 |
56 | migrationBuilder.CreateTable(
57 | name: "UserRoles",
58 | columns: table => new
59 | {
60 | UserId = table.Column(nullable: false),
61 | RoleId = table.Column(nullable: false)
62 | },
63 | constraints: table =>
64 | {
65 | table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId });
66 | });
67 |
68 | migrationBuilder.CreateTable(
69 | name: "Users",
70 | columns: table => new
71 | {
72 | Id = table.Column(nullable: false)
73 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
74 | UserName = table.Column(nullable: true),
75 | NormalizedUserName = table.Column(nullable: true),
76 | Email = table.Column(nullable: true),
77 | NormalizedEmail = table.Column(nullable: true),
78 | EmailConfirmed = table.Column(nullable: false),
79 | PasswordHash = table.Column(nullable: true),
80 | SecurityStamp = table.Column(nullable: true),
81 | ConcurrencyStamp = table.Column(nullable: true),
82 | PhoneNumber = table.Column(nullable: true),
83 | PhoneNumberConfirmed = table.Column(nullable: false),
84 | TwoFactorEnabled = table.Column(nullable: false),
85 | LockoutEnd = table.Column(nullable: true),
86 | LockoutEnabled = table.Column(nullable: false),
87 | AccessFailedCount = table.Column(nullable: false)
88 | },
89 | constraints: table =>
90 | {
91 | table.PrimaryKey("PK_Users", x => x.Id);
92 | });
93 | }
94 |
95 | protected override void Down(MigrationBuilder migrationBuilder)
96 | {
97 | migrationBuilder.DropTable(
98 | name: "IdentityRoleClaims");
99 |
100 | migrationBuilder.DropTable(
101 | name: "Roles");
102 |
103 | migrationBuilder.DropTable(
104 | name: "UserClaims");
105 |
106 | migrationBuilder.DropTable(
107 | name: "UserRoles");
108 |
109 | migrationBuilder.DropTable(
110 | name: "Users");
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------