├── AuthDemo.Web ├── ClientApp │ ├── src │ │ ├── components │ │ │ ├── ui │ │ │ │ ├── button │ │ │ │ │ ├── styles.css │ │ │ │ │ ├── index.js │ │ │ │ │ ├── button.types.js │ │ │ │ │ └── button.component.js │ │ │ │ ├── cards │ │ │ │ │ ├── card.types.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── styles.css │ │ │ │ │ └── card.component.js │ │ │ │ ├── grid │ │ │ │ │ ├── grid.types.js │ │ │ │ │ ├── grid.extensions.js │ │ │ │ │ ├── styles.css │ │ │ │ │ └── grid.component.js │ │ │ │ ├── icon │ │ │ │ │ └── icon.component.js │ │ │ │ ├── layout │ │ │ │ │ ├── styles.css │ │ │ │ │ └── layout.component.js │ │ │ │ ├── navigation │ │ │ │ │ ├── styles.css │ │ │ │ │ └── navigation.component.js │ │ │ │ ├── form │ │ │ │ │ ├── form.extensions.js │ │ │ │ │ ├── form.elements.js │ │ │ │ │ └── form.component.js │ │ │ │ └── footer │ │ │ │ │ ├── footer.component.js │ │ │ │ │ └── styles.css │ │ │ └── crosscutting │ │ │ │ └── guard │ │ │ │ └── guard.component.js │ │ ├── containers │ │ │ ├── login │ │ │ │ ├── styles.css │ │ │ │ ├── login.schema.js │ │ │ │ └── login.page.js │ │ │ └── home │ │ │ │ ├── home.schema.js │ │ │ │ └── home.page.js │ │ ├── redux │ │ │ ├── actions │ │ │ │ ├── index.js │ │ │ │ ├── posts.actions.js │ │ │ │ └── auth.actions.js │ │ │ ├── resources │ │ │ │ └── messages.js │ │ │ ├── services │ │ │ │ ├── index.js │ │ │ │ ├── posts.service.js │ │ │ │ ├── auth.service.js │ │ │ │ └── crud.service.js │ │ │ ├── constants │ │ │ │ ├── index.js │ │ │ │ ├── auth.constants.js │ │ │ │ └── posts.constants.js │ │ │ ├── helpers │ │ │ │ └── history.js │ │ │ ├── reducers │ │ │ │ ├── root.reducer.js │ │ │ │ ├── posts.reducer.js │ │ │ │ └── auth.reducer.js │ │ │ ├── store │ │ │ │ └── redux.store.js │ │ │ └── api │ │ │ │ └── api.axios.js │ │ ├── validation │ │ │ └── yup.validation.js │ │ ├── index.js │ │ ├── app │ │ │ └── app.component.js │ │ ├── resources │ │ │ └── styles.css │ │ └── registerServiceWorker.js │ ├── README.md │ ├── .env │ ├── .env.development │ ├── public │ │ ├── favicon.ico │ │ ├── manifest.json │ │ └── index.html │ ├── .gitignore │ └── package.json ├── Pages │ ├── _ViewImports.cshtml │ ├── Error.cshtml.cs │ └── Error.cshtml ├── appsettings.json ├── Controllers │ ├── ApiControllerBase.cs │ └── Administrator │ │ └── AdministratorApiController.cs ├── Features │ ├── Authorization │ │ ├── Models │ │ │ └── UserCredentials.cs │ │ └── AuthorizeController.cs │ └── Posts │ │ └── PostsController.cs ├── appsettings.Development.json ├── Program.cs ├── Properties │ └── launchSettings.json ├── AuthDemo.Web.csproj └── Startup.cs ├── AuthDemo.Domain ├── Interfaces │ └── IEntity.cs ├── Entities │ └── Identity │ │ ├── User.cs │ │ └── Role.cs └── AuthDemo.Domain.csproj ├── AuthDemo.Application ├── Options │ ├── ReactOptions.cs │ ├── AdministratorOptions.cs │ ├── SeedOptions.cs │ └── AuthOptions.cs ├── AuthDemo.Application.csproj └── AsyncInitialization │ ├── MigrationsInitializer.cs │ ├── DataInitializerBase.cs │ ├── IdentityInitializer.cs │ └── SeedInitializer.cs ├── AuthDemo.Identity ├── Contracts.cs ├── AuthDemo.Identity.csproj └── Extensions │ └── UserAccountExtensions.cs ├── AuthDemo.Identity.Jwt ├── Models │ └── JwtTokenResult.cs ├── Interfaces │ └── IJwtTokenGenerator.cs ├── Extensions │ ├── SecureJwtMiddlewareExtensions.cs │ ├── TokenValidationParametersExtensions.cs │ └── ServiceCollectionExtensions.cs ├── AuthDemo.Identity.Jwt.csproj ├── Middleware │ └── SecureJwtMiddleware.cs ├── JwtOptions.cs └── Services │ └── JwtTokenGenerator.cs ├── README.md ├── AuthDemo.Infrastructure.Data ├── AuthDemo.Infrastructure.Data.csproj ├── Contexts │ ├── ApplicationContext.Identity.cs │ └── ApplicationContext.cs └── Migrations │ ├── ApplicationContextModelSnapshot.cs │ ├── 20190911193047_Initial.Designer.cs │ └── 20190911193047_Initial.cs ├── .gitignore └── AuthDemo.sln /AuthDemo.Web/ClientApp/src/components/ui/button/styles.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/containers/login/styles.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | start ssl: set HTTPS=true&&npm start -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL= 2 | REACT_APP_GENERATE_SOURCEMAP=false -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/cards/card.types.js: -------------------------------------------------------------------------------- 1 | export const types = { 2 | form: 'form', 3 | } -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/actions/index.js: -------------------------------------------------------------------------------- 1 | export * from './posts.actions'; 2 | export * from './auth.actions'; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/resources/messages.js: -------------------------------------------------------------------------------- 1 | const messages = { 2 | 3 | } 4 | 5 | export default messages; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/services/index.js: -------------------------------------------------------------------------------- 1 | export * from './posts.service'; 2 | export * from './auth.service'; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL=https://localhost:44308/ 2 | REACT_APP_GENERATE_SOURCEMAP=false -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/cards/index.js: -------------------------------------------------------------------------------- 1 | export * from './card.component'; 2 | export * from './card.types'; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/constants/index.js: -------------------------------------------------------------------------------- 1 | export * from './posts.constants'; 2 | export * from './auth.constants'; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/button/index.js: -------------------------------------------------------------------------------- 1 | export * from './button.component'; 2 | export * from './button.types'; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neonbones/Boilerplate.AuthDemo/HEAD/AuthDemo.Web/ClientApp/public/favicon.ico -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/button/button.types.js: -------------------------------------------------------------------------------- 1 | export const types = { 2 | link: 'link', 3 | submit: 'submit' 4 | }; 5 | -------------------------------------------------------------------------------- /AuthDemo.Web/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using AuthDemo.Web 2 | @namespace AuthDemo.Web.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /AuthDemo.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /AuthDemo.Domain/Interfaces/IEntity.cs: -------------------------------------------------------------------------------- 1 | namespace AuthDemo.Domain.Interfaces 2 | { 3 | public interface IEntity 4 | { 5 | long Id { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/helpers/history.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history'; 2 | const history = createBrowserHistory(); 3 | export default history; -------------------------------------------------------------------------------- /AuthDemo.Application/Options/ReactOptions.cs: -------------------------------------------------------------------------------- 1 | namespace AuthDemo.Application.Options 2 | { 3 | public class ReactOptions 4 | { 5 | public bool UseSpa { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /AuthDemo.Application/Options/AdministratorOptions.cs: -------------------------------------------------------------------------------- 1 | namespace AuthDemo.Application.Options 2 | { 3 | public class AdministratorOptions 4 | { 5 | public string UserName { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/grid/grid.types.js: -------------------------------------------------------------------------------- 1 | export const types = { 2 | striped: "striped", 3 | highlight: "highlight", 4 | centered: "centered", 5 | responsive: "responsive-table" 6 | } -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/services/posts.service.js: -------------------------------------------------------------------------------- 1 | import { get } from './crud.service'; 2 | 3 | const getAll = async () => await get('Administrator/Posts'); 4 | 5 | export const postsService = { 6 | getAll 7 | } 8 | 9 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/constants/auth.constants.js: -------------------------------------------------------------------------------- 1 | export const authConstants = { 2 | LOGIN_REQUEST: 'LOGIN_REQUEST', 3 | LOGIN_SUCCESS: 'LOGIN_SUCCESS', 4 | LOGIN_FAILURE: 'LOGIN_FAILURE', 5 | LOGOUT: 'LOGOUT' 6 | }; 7 | -------------------------------------------------------------------------------- /AuthDemo.Web/Controllers/ApiControllerBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace AuthDemo.Web.Controllers 4 | { 5 | [ApiController] 6 | public class ApiControllerBase : ControllerBase 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/icon/icon.component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon as I } from 'react-materialize'; 3 | 4 | export const Icon = (props) => 5 | {props.icon} 6 | 7 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/services/auth.service.js: -------------------------------------------------------------------------------- 1 | import { post } from './crud.service'; 2 | 3 | const login = async (username, password) => await post('authorize', { username, password }); 4 | 5 | export const authService = { 6 | login 7 | }; 8 | -------------------------------------------------------------------------------- /AuthDemo.Domain/Entities/Identity/User.cs: -------------------------------------------------------------------------------- 1 | using AuthDemo.Domain.Interfaces; 2 | using Microsoft.AspNetCore.Identity; 3 | 4 | namespace AuthDemo.Domain.Entities.Identity 5 | { 6 | public class User : IdentityUser, IEntity 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/layout/styles.css: -------------------------------------------------------------------------------- 1 | .site { 2 | display: flex; 3 | flex-direction: column; 4 | min-height: 100vh; 5 | } 6 | .content-wrapper { 7 | flex: 1 0 auto; 8 | width: 100%; 9 | padding: 20px; 10 | } 11 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/validation/yup.validation.js: -------------------------------------------------------------------------------- 1 | import * as Yup from 'yup'; 2 | 3 | const rules = { 4 | username: Yup.string().required('Username обязателен'), 5 | password: Yup.string().required('Password обязателен') 6 | } 7 | 8 | export default rules; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/services/crud.service.js: -------------------------------------------------------------------------------- 1 | import api from '../api/api.axios'; 2 | 3 | export const get = async (url, config = {}) => await api.get(url, config) 4 | 5 | export const post = async (url, payload, config = {}) => await api.post(url, payload, config) -------------------------------------------------------------------------------- /AuthDemo.Application/Options/SeedOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace AuthDemo.Application.Options 4 | { 5 | public class SeedOptions 6 | { 7 | public List Administrators { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/navigation/styles.css: -------------------------------------------------------------------------------- 1 | nav{ 2 | background: linear-gradient(45deg, #8e24aa, #ff6e40) !important; 3 | box-shadow: 0 6px 20px 0 rgba(255, 110, 64, .5) !important; 4 | } 5 | 6 | .nav-link { 7 | color: rgba(255, 255, 255, .8); 8 | } -------------------------------------------------------------------------------- /AuthDemo.Identity/Contracts.cs: -------------------------------------------------------------------------------- 1 | namespace AuthDemo.Identity 2 | { 3 | public static class Contracts 4 | { 5 | public const string AdministratorPolicy = nameof(AdministratorPolicy); 6 | 7 | public const string UserPolicy = nameof(UserPolicy); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AuthDemo.Web/Features/Authorization/Models/UserCredentials.cs: -------------------------------------------------------------------------------- 1 | namespace AuthDemo.Web.Features.Authorization.Models 2 | { 3 | public class UserCredentials 4 | { 5 | public string Username { get; set; } 6 | 7 | public string Password { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/reducers/root.reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import posts from './posts.reducer'; 3 | import auth from './auth.reducer'; 4 | 5 | const rootReducer = combineReducers({ 6 | posts, 7 | auth, 8 | }); 9 | 10 | export default rootReducer; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/form/form.extensions.js: -------------------------------------------------------------------------------- 1 | const buildForm = (schema) => { 2 | const formArray = []; 3 | for(let id in schema) 4 | formArray.push({id,value: schema[id]}); 5 | return formArray; 6 | } 7 | 8 | export const extensions = { 9 | buildForm 10 | } -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/constants/posts.constants.js: -------------------------------------------------------------------------------- 1 | export const postsConstants = { 2 | POSTS_GETALL_REQUEST: 'POSTS_GETALL_REQUEST', 3 | POSTS_BYID_REQUEST: 'POSTS_BYID_REQUEST', 4 | 5 | POSTS_GETALL_SUCCESS: 'POSTS_GETALL_SUCCESS', 6 | POSTS_BYID_SUCCESS: 'POSTS_BYID_SUCCESS', 7 | }; -------------------------------------------------------------------------------- /AuthDemo.Identity.Jwt/Models/JwtTokenResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AuthDemo.Identity.Jwt.Models 4 | { 5 | public sealed class JwtTokenResult 6 | { 7 | public string AccessToken { get; internal set; } 8 | 9 | public TimeSpan Expires { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AuthDemo.Domain/AuthDemo.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/footer/footer.component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles.css'; 3 | 4 | const Footer = () => 5 |
6 |
7 | Designed and Developed by neonbones_sp 8 |
9 |
; 10 | 11 | export default Footer; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/containers/login/login.schema.js: -------------------------------------------------------------------------------- 1 | const schema ={ 2 | username:{ 3 | config:{ 4 | type:'text' 5 | }, 6 | label:'Username' 7 | }, 8 | password:{ 9 | config:{ 10 | type:'password' 11 | }, 12 | label:'Password' 13 | } 14 | } 15 | 16 | export default schema; -------------------------------------------------------------------------------- /AuthDemo.Identity.Jwt/Interfaces/IJwtTokenGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AuthDemo.Domain.Entities.Identity; 3 | using AuthDemo.Identity.Jwt.Models; 4 | 5 | namespace AuthDemo.Identity.Jwt.Interfaces 6 | { 7 | public interface IJwtTokenGenerator 8 | { 9 | JwtTokenResult Generate(User user, IList roles); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/form/form.elements.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Label = (props) => 4 | 5 | 6 | export const FormField = (props) => 7 |
8 |
9 | {props.children} 10 |
11 |
-------------------------------------------------------------------------------- /AuthDemo.Web/Controllers/Administrator/AdministratorApiController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace AuthDemo.Web.Controllers.Administrator 5 | { 6 | [Authorize] 7 | [Route("Administrator/[controller]")] 8 | public class AdministratorApiController : ApiControllerBase 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Client App", 3 | "name": "Client App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/footer/styles.css: -------------------------------------------------------------------------------- 1 | .page-footer{ 2 | border-top: 1px solid #e0e0e0; 3 | background: linear-gradient(45deg, #8e24aa, #ff6e40) !important; 4 | box-shadow: 0 6px 35px 0 rgba(255, 110, 64, 1) !important; 5 | height: 64px; 6 | padding: 22px; 7 | } 8 | .footer-content{ 9 | float: right; 10 | color: rgba(255, 255, 255, .8); 11 | font-size: 16px; 12 | } -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './app/app.component'; 3 | import store from './redux/store/redux.store'; 4 | import { render } from 'react-dom'; 5 | import { Provider } from 'react-redux'; 6 | import './resources/styles.css'; 7 | 8 | render( 9 | 10 | 11 | , 12 | document.getElementById('app') 13 | ); -------------------------------------------------------------------------------- /AuthDemo.Identity.Jwt/Extensions/SecureJwtMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | using AuthDemo.Identity.Jwt.Middleware; 2 | using Microsoft.AspNetCore.Builder; 3 | 4 | namespace AuthDemo.Identity.Jwt.Extensions 5 | { 6 | public static class SecureJwtMiddlewareExtensions 7 | { 8 | public static IApplicationBuilder UseSecureJwt(this IApplicationBuilder builder) => builder.UseMiddleware(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/reducers/posts.reducer.js: -------------------------------------------------------------------------------- 1 | import { postsConstants as c } from '../constants/posts.constants'; 2 | 3 | const posts = (state = {}, action) => { 4 | switch(action.type){ 5 | case c.POSTS_GETALL_REQUEST: return { loading: true }; 6 | case c.POSTS_GETALL_SUCCESS: return { items: action.posts, loading: false }; 7 | default: return state; 8 | } 9 | } 10 | 11 | export default posts; -------------------------------------------------------------------------------- /AuthDemo.Application/Options/AuthOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AuthDemo.Application.Options 6 | { 7 | public class AuthOptions 8 | { 9 | public string Issuer { get; set; } 10 | 11 | public string Audience { get; set; } 12 | 13 | public int Lifetime { get; set; } 14 | 15 | public string Secret { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/cards/styles.css: -------------------------------------------------------------------------------- 1 | .card-form { 2 | overflow: hidden; 3 | margin: 1rem 0 1rem 0; 4 | } 5 | 6 | .form-card-content { 7 | border-radius: 0 0 2px 2px; 8 | padding: 24px 24px 0px 24px; 9 | } 10 | 11 | .card-form .form-card-title { 12 | font-size: 18px; 13 | font-weight: 400; 14 | margin: 0; 15 | padding: 0px; 16 | line-height: 32px; 17 | display: block; 18 | margin-bottom: 8px; 19 | } -------------------------------------------------------------------------------- /AuthDemo.Identity/AuthDemo.Identity.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/crosscutting/guard/guard.component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | 4 | const Guard = ({ component: Component, roles, ...rest }) => ( 5 | { 6 | var auth = localStorage.getItem('auth') 7 | 8 | if(!auth) 9 | return 10 | 11 | return }} 12 | /> 13 | ) 14 | 15 | export default Guard; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/layout/layout.component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Footer from '../footer/footer.component'; 3 | import Navigation from '../navigation/navigation.component'; 4 | import './styles.css'; 5 | 6 | const Layout = (props) => ( 7 |
8 | 9 |
10 | {props.children} 11 |
12 |
13 |
14 | ) 15 | 16 | export default Layout; 17 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/store/redux.store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { createLogger } from 'redux-logger'; 4 | import rootReducer from '../reducers/root.reducer'; 5 | 6 | const logger = createLogger(); 7 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 8 | 9 | const store = createStore( 10 | rootReducer, 11 | composeEnhancer(applyMiddleware(thunk, logger)) 12 | ); 13 | 14 | export default store; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jwt + httpOnly secure cookies authentication boilerplate. 2 | - Frontend path: AuthDemo.Web -> ClientApp 3 | - Authentication configure: AuthDemo.Web -> Startup.cs 4 | - HTTPS connection is already configured. 5 | # Instructions for running: 6 | 1. Change "ConnectionStrings" in appsettings.Development.json to yours. 7 | 2. Just run it! Migrations will apply automatically. 8 | 3. Default admin username Username: neonbones_sp, Password: Qwe123! 9 | 4. https://localhost:3000/ - Protected path, https://localhost:3000/SignIn - Login path 10 | -------------------------------------------------------------------------------- /AuthDemo.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "Connection": "Server=.;Database=AuthDemo;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "AuthOptions": { 6 | "Issuer": "AuthDemo", 7 | "Audience": "AuthDemo", 8 | "Lifetime": "60", 9 | "Secret": "c09cb2f7-fb47-4e7d-8c08-da788e35e057" 10 | }, 11 | "Seed": { 12 | "Administrators": [ 13 | { 14 | "UserName": "neonbones_sp" 15 | } 16 | ] 17 | }, 18 | "React": { 19 | "UseSpa": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AuthDemo.Domain/Entities/Identity/Role.cs: -------------------------------------------------------------------------------- 1 | using AuthDemo.Domain.Interfaces; 2 | using Microsoft.AspNetCore.Identity; 3 | 4 | namespace AuthDemo.Domain.Entities.Identity 5 | { 6 | public class Role : IdentityRole, IEntity 7 | { 8 | public enum Types 9 | { 10 | Administrator, 11 | User 12 | } 13 | 14 | protected Role() 15 | { 16 | } 17 | 18 | public Role(string roleName) : base(roleName) 19 | { 20 | NormalizedName = roleName.ToUpperInvariant(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AuthDemo.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore; 3 | using Microsoft.AspNetCore.Hosting; 4 | 5 | namespace AuthDemo.Web 6 | { 7 | public class Program 8 | { 9 | public static async Task Main(string[] args) 10 | { 11 | var buider = CreateWebHostBuilder(args).Build(); 12 | await buider.InitAsync(); 13 | await buider.RunAsync(); 14 | } 15 | 16 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 17 | WebHost.CreateDefaultBuilder(args) 18 | .UseStartup(); 19 | } 20 | } -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/actions/posts.actions.js: -------------------------------------------------------------------------------- 1 | import { postsService as s } from '../services'; 2 | import { postsConstants as c } from '../constants'; 3 | 4 | const getAll = () => dispatch => { 5 | dispatch(request()); 6 | 7 | s.getAll() 8 | .then( 9 | data => { 10 | dispatch(success(data)) 11 | }, 12 | error => { 13 | } 14 | ) 15 | } 16 | 17 | const request = () => { return { type: c.POSTS_GETALL_REQUEST } }; 18 | const success = (posts) => { return { type: c.POSTS_GETALL_SUCCESS, posts } }; 19 | 20 | export const postsActions = { 21 | getAll 22 | } 23 | -------------------------------------------------------------------------------- /AuthDemo.Infrastructure.Data/AuthDemo.Infrastructure.Data.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /AuthDemo.Infrastructure.Data/Contexts/ApplicationContext.Identity.cs: -------------------------------------------------------------------------------- 1 | using AuthDemo.Domain.Entities.Identity; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace AuthDemo.Infrastructure.Data.Contexts 6 | { 7 | public partial class ApplicationContext 8 | { 9 | public DbSet Users { get; set; } 10 | 11 | public DbSet Roles { get; set; } 12 | 13 | public DbSet> UserRoles { get; set; } 14 | 15 | public DbSet> IdentityUserClaims { get; set; } 16 | 17 | public DbSet> IdentityRoleClaims { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AuthDemo.Application/AuthDemo.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/navigation/navigation.component.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Navbar } from 'react-materialize' 3 | import { Link } from 'react-router-dom'; 4 | import './styles.css'; 5 | 6 | class Navigation extends Component { 7 | 8 | render() { 9 | return ( 10 | Client App} centerLogo alignLinks="right"> 11 |
    12 |
  • 13 | Sign In 14 |
  • 15 |
16 |
17 | ); 18 | } 19 | } 20 | 21 | export default Navigation; 22 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/containers/home/home.schema.js: -------------------------------------------------------------------------------- 1 | import { types } from '../../components/ui/grid/grid.types'; 2 | 3 | const schema = { 4 | title: 'Posts', 5 | type: types.highlight, 6 | columns: { 7 | title: { name: 'Title', width: 35 }, 8 | content: { name: 'Content', width: 40 } 9 | }, 10 | create: { 11 | show: true, 12 | title: 'Create', 13 | link: 'Posts/Create', 14 | }, 15 | actions: { 16 | show: true, 17 | width: 20, 18 | name: 'Actions', 19 | preview: 'Posts/Preview', 20 | delete: 'Posts/Delete', 21 | edit: 'Posts/Edit' 22 | }, 23 | search: false 24 | } 25 | 26 | export default schema; -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/redux/reducers/auth.reducer.js: -------------------------------------------------------------------------------- 1 | import { authConstants as c } from '../constants'; 2 | 3 | let authUser = localStorage.getItem('auth'); 4 | 5 | const initialState = authUser 6 | ? { loggedIn: true, authUser } 7 | : {}; 8 | 9 | const auth = (state = initialState, action) => { 10 | switch (action.type) { 11 | case c.LOGIN_REQUEST: return { loggedIn: false, loading: true, user: action.user }; 12 | case c.LOGIN_SUCCESS: return { loggedIn: true, loading: false, user: action.user }; 13 | case c.LOGIN_FAILURE: return { loggedIn: false, loading: false }; 14 | case c.LOGOUT: return {}; 15 | default: return state; 16 | } 17 | } 18 | 19 | export default auth; -------------------------------------------------------------------------------- /AuthDemo.Web/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | 9 | namespace AuthDemo.Web.Pages 10 | { 11 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 12 | public class ErrorModel : PageModel 13 | { 14 | public string RequestId { get; set; } 15 | 16 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 17 | 18 | public void OnGet() 19 | { 20 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AuthDemo.Application/AsyncInitialization/MigrationsInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AuthDemo.Infrastructure.Data.Contexts; 6 | using Microsoft.EntityFrameworkCore; 7 | 8 | namespace AuthDemo.Application.AsyncInitialization 9 | { 10 | public class MigrationsInitializer : DataInitializerBase 11 | { 12 | public MigrationsInitializer(IServiceProvider serviceProvider) 13 | : base(serviceProvider) 14 | { 15 | } 16 | 17 | protected override async Task InitializeAsync(ApplicationContext dbContext) 18 | { 19 | await dbContext.Database.MigrateAsync(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AuthDemo.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52625", 7 | "sslPort": 44308 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "AuthDemo.Web": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/app/app.component.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Router, Route } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import history from '../redux/helpers/history' 5 | import Layout from '../components/ui/layout/layout.component'; 6 | import Guard from '../components/crosscutting/guard/guard.component'; 7 | import HomePage from '../containers/home/home.page'; 8 | import LoginPage from '../containers/login/login.page'; 9 | 10 | class App extends Component { 11 | render() { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | } 22 | 23 | 24 | 25 | export default connect()(App); -------------------------------------------------------------------------------- /AuthDemo.Identity.Jwt/AuthDemo.Identity.Jwt.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /AuthDemo.Web/ClientApp/src/components/ui/cards/card.component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles.css' 3 | 4 | export const CardForm = (props) => 5 |
6 | {props.children} 7 |
8 | 9 | export const CardGrid = (props) => 10 |
11 | {props.children} 12 |
13 | 14 | export const CardContent = (props) => 15 |
16 | {props.children} 17 |
18 | 19 | export const CardTitle = (props) => 20 |

21 | {props.title} 22 |

23 | 24 | export const CardWrapper = (props) => 25 |
26 | {props.children} 27 |
-------------------------------------------------------------------------------- /AuthDemo.Identity.Jwt/Extensions/TokenValidationParametersExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.IdentityModel.Tokens; 3 | 4 | namespace AuthDemo.Identity.Jwt.Extensions 5 | { 6 | public static class TokenValidationParametersExtensions 7 | { 8 | internal static TokenValidationParameters ToTokenValidationParams( 9 | this JwtOptions tokenOptions) => 10 | new TokenValidationParameters 11 | { 12 | ClockSkew = TimeSpan.Zero, 13 | ValidateAudience = false, 14 | ValidAudience = tokenOptions.Audience, 15 | ValidateIssuer = false, 16 | ValidIssuer = tokenOptions.Issuer, 17 | IssuerSigningKey = tokenOptions.SigningKey, 18 | ValidateIssuerSigningKey = true, 19 | RequireExpirationTime = true, 20 | ValidateLifetime = true 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AuthDemo.Infrastructure.Data/Contexts/ApplicationContext.cs: -------------------------------------------------------------------------------- 1 | using AuthDemo.Domain.Entities; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace AuthDemo.Infrastructure.Data.Contexts 6 | { 7 | public partial class ApplicationContext : DbContext 8 | { 9 | public ApplicationContext(DbContextOptions options) : base(options) 10 | { 11 | } 12 | 13 | protected override void OnModelCreating(ModelBuilder builder) 14 | { 15 | builder.Entity>() 16 | .HasKey(r => new { r.UserId, r.RoleId }); 17 | 18 | builder.Entity>() 19 | .ToTable("UserRoles"); 20 | 21 | builder.Entity>() 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 | 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 | 15 | ); 16 | 17 | const renderButton = () => 18 | 19 | 21 |
22 |
23 |
24 |
25 | : 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 | ?
22 |
23 |
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 |
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 | --------------------------------------------------------------------------------