├── .gitignore
├── AuthenticationServer.API
├── AuthenticationServer.API.csproj
├── AuthenticationServer.API.csproj.user
├── Controllers
│ └── AuthenticationController.cs
├── Migrations
│ ├── 20201119182451_initial.Designer.cs
│ ├── 20201119182451_initial.cs
│ ├── 20201211165943_Identity.Designer.cs
│ ├── 20201211165943_Identity.cs
│ └── AuthenticationDbContextModelSnapshot.cs
├── Models
│ ├── AccessToken.cs
│ ├── AuthenticationConfiguration.cs
│ ├── AuthenticationDbContext.cs
│ ├── RefreshToken.cs
│ └── User.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Services
│ ├── Authenticators
│ │ └── Authenticator.cs
│ ├── RefreshTokenRepositories
│ │ ├── DatabaseRefreshTokenRepository.cs
│ │ ├── IRefreshTokenRepository.cs
│ │ └── InMemoryRefreshTokenRepository.cs
│ ├── TokenGenerators
│ │ ├── AccessTokenGenerator.cs
│ │ ├── RefreshTokenGenerator.cs
│ │ └── TokenGenerator.cs
│ └── TokenValidators
│ │ └── RefreshTokenValidator.cs
├── Startup.cs
├── appsettings.Development.json
└── appsettings.json
├── AuthenticationServer.sln
├── ClientApplication
├── ClientApplication.csproj
├── Http
│ └── AutoRefreshHttpMessageHandler.cs
├── Program.cs
├── Services
│ ├── IDataService.cs
│ ├── ILoginService.cs
│ ├── IRefreshService.cs
│ └── IRegisterService.cs
└── Stores
│ └── TokenStore.cs
├── Core
├── CoreLib.csproj
├── Requests
│ ├── LoginRequest.cs
│ ├── RefreshRequest.cs
│ └── RegisterRequest.cs
└── Responses
│ ├── AuthenticatedUserResponse.cs
│ ├── DataResponse.cs
│ └── ErrorResponse.cs
└── DataServer.API
├── Controllers
└── HomeController.cs
├── DataServer.API.csproj
├── DataServer.API.csproj.user
├── Program.cs
├── Properties
└── launchSettings.json
├── Startup.cs
├── appsettings.Development.json
└── appsettings.json
/.gitignore:
--------------------------------------------------------------------------------
1 | obj
2 | bin
3 | .vs
4 | *.db
--------------------------------------------------------------------------------
/AuthenticationServer.API/AuthenticationServer.API.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | all
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/AuthenticationServer.API.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ProjectDebugger
5 |
6 |
7 | AuthenticationServer.API
8 |
9 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Controllers/AuthenticationController.cs:
--------------------------------------------------------------------------------
1 | using AuthenticationServer.API.Models;
2 | using AuthenticationServer.API.Services.Authenticators;
3 | using AuthenticationServer.API.Services.RefreshTokenRepositories;
4 | using AuthenticationServer.API.Services.TokenValidators;
5 | using CoreLib.Requests;
6 | using CoreLib.Responses;
7 | using Microsoft.AspNetCore.Authorization;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.AspNetCore.Mvc;
10 | using System;
11 | using System.Collections.Generic;
12 | using System.Linq;
13 | using System.Security.Claims;
14 | using System.Threading.Tasks;
15 |
16 | namespace AuthenticationServer.API.Controllers
17 | {
18 | public class AuthenticationController : Controller
19 | {
20 | private readonly UserManager _userRepository;
21 | private readonly Authenticator _authenticator;
22 | private readonly RefreshTokenValidator _refreshTokenValidator;
23 | private readonly IRefreshTokenRepository _refreshTokenRepository;
24 |
25 | public AuthenticationController(UserManager userRepository,
26 | Authenticator authenticator,
27 | RefreshTokenValidator refreshTokenValidator,
28 | IRefreshTokenRepository refreshTokenRepository)
29 | {
30 | _userRepository = userRepository;
31 | _authenticator = authenticator;
32 | _refreshTokenValidator = refreshTokenValidator;
33 | _refreshTokenRepository = refreshTokenRepository;
34 | }
35 |
36 | [HttpPost("register")]
37 | public async Task Register([FromBody] RegisterRequest registerRequest)
38 | {
39 | if(!ModelState.IsValid)
40 | {
41 | return BadRequestModelState();
42 | }
43 |
44 | if (registerRequest.Password != registerRequest.ConfirmPassword)
45 | {
46 | return BadRequest(new ErrorResponse("Password does not match confirm password."));
47 | }
48 |
49 | User registrationUser = new User()
50 | {
51 | Email = registerRequest.Email,
52 | UserName = registerRequest.Username
53 | };
54 |
55 | IdentityResult result = await _userRepository.CreateAsync(registrationUser, registerRequest.Password);
56 | if(!result.Succeeded)
57 | {
58 | IdentityErrorDescriber errorDescriber = new IdentityErrorDescriber();
59 | IdentityError primaryError = result.Errors.FirstOrDefault();
60 |
61 | if(primaryError.Code == nameof(errorDescriber.DuplicateEmail))
62 | {
63 | return Conflict(new ErrorResponse("Email already exists."));
64 | }
65 | else if (primaryError.Code == nameof(errorDescriber.DuplicateUserName))
66 | {
67 | return Conflict(new ErrorResponse("Username already exists."));
68 | }
69 | }
70 |
71 | return Ok();
72 | }
73 |
74 | [HttpPost("login")]
75 | public async Task Login([FromBody] LoginRequest loginRequest)
76 | {
77 | if(!ModelState.IsValid)
78 | {
79 | return BadRequestModelState();
80 | }
81 |
82 | User user = await _userRepository.FindByNameAsync(loginRequest.Username);
83 | if(user == null)
84 | {
85 | return Unauthorized();
86 | }
87 |
88 | bool isCorrectPassword = await _userRepository.CheckPasswordAsync(user, loginRequest.Password);
89 | if(!isCorrectPassword)
90 | {
91 | return Unauthorized();
92 | }
93 |
94 | AuthenticatedUserResponse response = await _authenticator.Authenticate(user);
95 |
96 | return Ok(response);
97 | }
98 |
99 | [HttpPost("refresh")]
100 | public async Task Refresh([FromBody] RefreshRequest refreshRequest)
101 | {
102 | if (!ModelState.IsValid)
103 | {
104 | return BadRequestModelState();
105 | }
106 |
107 | bool isValidRefreshToken = _refreshTokenValidator.Validate(refreshRequest.RefreshToken);
108 | if(!isValidRefreshToken)
109 | {
110 | return BadRequest(new ErrorResponse("Invalid refresh token."));
111 | }
112 |
113 | RefreshToken refreshTokenDTO = await _refreshTokenRepository.GetByToken(refreshRequest.RefreshToken);
114 | if(refreshTokenDTO == null)
115 | {
116 | return NotFound(new ErrorResponse("Invalid refresh token."));
117 | }
118 |
119 | await _refreshTokenRepository.Delete(refreshTokenDTO.Id);
120 |
121 | User user = await _userRepository.FindByIdAsync(refreshTokenDTO.UserId.ToString());
122 | if(user == null)
123 | {
124 | return NotFound(new ErrorResponse("User not found."));
125 | }
126 |
127 | AuthenticatedUserResponse response = await _authenticator.Authenticate(user);
128 |
129 | return Ok(response);
130 | }
131 |
132 | [Authorize]
133 | [HttpDelete("logout")]
134 | public async Task Logout()
135 | {
136 | string rawUserId = HttpContext.User.FindFirstValue("id");
137 |
138 | if(!Guid.TryParse(rawUserId, out Guid userId))
139 | {
140 | return Unauthorized();
141 | }
142 |
143 | await _refreshTokenRepository.DeleteAll(userId);
144 |
145 | return NoContent();
146 | }
147 |
148 | private IActionResult BadRequestModelState()
149 | {
150 | IEnumerable errorMessages = ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage));
151 |
152 | return BadRequest(new ErrorResponse(errorMessages));
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Migrations/20201119182451_initial.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using AuthenticationServer.API.Models;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace AuthenticationServer.API.Migrations
10 | {
11 | [DbContext(typeof(AuthenticationDbContext))]
12 | [Migration("20201119182451_initial")]
13 | partial class initial
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .HasAnnotation("ProductVersion", "5.0.0");
20 |
21 | modelBuilder.Entity("AuthenticationServer.API.Models.RefreshToken", b =>
22 | {
23 | b.Property("Id")
24 | .ValueGeneratedOnAdd()
25 | .HasColumnType("TEXT");
26 |
27 | b.Property("Token")
28 | .HasColumnType("TEXT");
29 |
30 | b.Property("UserId")
31 | .HasColumnType("TEXT");
32 |
33 | b.HasKey("Id");
34 |
35 | b.ToTable("RefreshTokens");
36 | });
37 |
38 | modelBuilder.Entity("AuthenticationServer.API.Models.User", b =>
39 | {
40 | b.Property("Id")
41 | .ValueGeneratedOnAdd()
42 | .HasColumnType("TEXT");
43 |
44 | b.Property("Email")
45 | .HasColumnType("TEXT");
46 |
47 | b.Property("PasswordHash")
48 | .HasColumnType("TEXT");
49 |
50 | b.Property("Username")
51 | .HasColumnType("TEXT");
52 |
53 | b.HasKey("Id");
54 |
55 | b.ToTable("Users");
56 | });
57 | #pragma warning restore 612, 618
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Migrations/20201119182451_initial.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace AuthenticationServer.API.Migrations
5 | {
6 | public partial class initial : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "RefreshTokens",
12 | columns: table => new
13 | {
14 | Id = table.Column(type: "TEXT", nullable: false),
15 | Token = table.Column(type: "TEXT", nullable: true),
16 | UserId = table.Column(type: "TEXT", nullable: false)
17 | },
18 | constraints: table =>
19 | {
20 | table.PrimaryKey("PK_RefreshTokens", x => x.Id);
21 | });
22 |
23 | migrationBuilder.CreateTable(
24 | name: "Users",
25 | columns: table => new
26 | {
27 | Id = table.Column(type: "TEXT", nullable: false),
28 | Email = table.Column(type: "TEXT", nullable: true),
29 | Username = table.Column(type: "TEXT", nullable: true),
30 | PasswordHash = table.Column(type: "TEXT", nullable: true)
31 | },
32 | constraints: table =>
33 | {
34 | table.PrimaryKey("PK_Users", x => x.Id);
35 | });
36 | }
37 |
38 | protected override void Down(MigrationBuilder migrationBuilder)
39 | {
40 | migrationBuilder.DropTable(
41 | name: "RefreshTokens");
42 |
43 | migrationBuilder.DropTable(
44 | name: "Users");
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Migrations/20201211165943_Identity.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using AuthenticationServer.API.Models;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace AuthenticationServer.API.Migrations
10 | {
11 | [DbContext(typeof(AuthenticationDbContext))]
12 | [Migration("20201211165943_Identity")]
13 | partial class Identity
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .HasAnnotation("ProductVersion", "5.0.0");
20 |
21 | modelBuilder.Entity("AuthenticationServer.API.Models.RefreshToken", b =>
22 | {
23 | b.Property("Id")
24 | .ValueGeneratedOnAdd()
25 | .HasColumnType("TEXT");
26 |
27 | b.Property("Token")
28 | .HasColumnType("TEXT");
29 |
30 | b.Property("UserId")
31 | .HasColumnType("TEXT");
32 |
33 | b.HasKey("Id");
34 |
35 | b.ToTable("RefreshTokens");
36 | });
37 |
38 | modelBuilder.Entity("AuthenticationServer.API.Models.User", b =>
39 | {
40 | b.Property("Id")
41 | .ValueGeneratedOnAdd()
42 | .HasColumnType("TEXT");
43 |
44 | b.Property("AccessFailedCount")
45 | .HasColumnType("INTEGER");
46 |
47 | b.Property("ConcurrencyStamp")
48 | .HasColumnType("TEXT");
49 |
50 | b.Property("Email")
51 | .HasColumnType("TEXT");
52 |
53 | b.Property("EmailConfirmed")
54 | .HasColumnType("INTEGER");
55 |
56 | b.Property("LockoutEnabled")
57 | .HasColumnType("INTEGER");
58 |
59 | b.Property("LockoutEnd")
60 | .HasColumnType("TEXT");
61 |
62 | b.Property("NormalizedEmail")
63 | .HasColumnType("TEXT");
64 |
65 | b.Property("NormalizedUserName")
66 | .HasColumnType("TEXT");
67 |
68 | b.Property("PasswordHash")
69 | .HasColumnType("TEXT");
70 |
71 | b.Property("PhoneNumber")
72 | .HasColumnType("TEXT");
73 |
74 | b.Property("PhoneNumberConfirmed")
75 | .HasColumnType("INTEGER");
76 |
77 | b.Property("SecurityStamp")
78 | .HasColumnType("TEXT");
79 |
80 | b.Property("TwoFactorEnabled")
81 | .HasColumnType("INTEGER");
82 |
83 | b.Property("UserName")
84 | .HasColumnType("TEXT");
85 |
86 | b.HasKey("Id");
87 |
88 | b.ToTable("Users");
89 | });
90 | #pragma warning restore 612, 618
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Migrations/20201211165943_Identity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace AuthenticationServer.API.Migrations
5 | {
6 | public partial class Identity : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.RenameColumn(
11 | name: "Username",
12 | table: "Users",
13 | newName: "UserName");
14 |
15 | migrationBuilder.AddColumn(
16 | name: "AccessFailedCount",
17 | table: "Users",
18 | type: "INTEGER",
19 | nullable: false,
20 | defaultValue: 0);
21 |
22 | migrationBuilder.AddColumn(
23 | name: "ConcurrencyStamp",
24 | table: "Users",
25 | type: "TEXT",
26 | nullable: true);
27 |
28 | migrationBuilder.AddColumn(
29 | name: "EmailConfirmed",
30 | table: "Users",
31 | type: "INTEGER",
32 | nullable: false,
33 | defaultValue: false);
34 |
35 | migrationBuilder.AddColumn(
36 | name: "LockoutEnabled",
37 | table: "Users",
38 | type: "INTEGER",
39 | nullable: false,
40 | defaultValue: false);
41 |
42 | migrationBuilder.AddColumn(
43 | name: "LockoutEnd",
44 | table: "Users",
45 | type: "TEXT",
46 | nullable: true);
47 |
48 | migrationBuilder.AddColumn(
49 | name: "NormalizedEmail",
50 | table: "Users",
51 | type: "TEXT",
52 | nullable: true);
53 |
54 | migrationBuilder.AddColumn(
55 | name: "NormalizedUserName",
56 | table: "Users",
57 | type: "TEXT",
58 | nullable: true);
59 |
60 | migrationBuilder.AddColumn(
61 | name: "PhoneNumber",
62 | table: "Users",
63 | type: "TEXT",
64 | nullable: true);
65 |
66 | migrationBuilder.AddColumn(
67 | name: "PhoneNumberConfirmed",
68 | table: "Users",
69 | type: "INTEGER",
70 | nullable: false,
71 | defaultValue: false);
72 |
73 | migrationBuilder.AddColumn(
74 | name: "SecurityStamp",
75 | table: "Users",
76 | type: "TEXT",
77 | nullable: true);
78 |
79 | migrationBuilder.AddColumn(
80 | name: "TwoFactorEnabled",
81 | table: "Users",
82 | type: "INTEGER",
83 | nullable: false,
84 | defaultValue: false);
85 | }
86 |
87 | protected override void Down(MigrationBuilder migrationBuilder)
88 | {
89 | migrationBuilder.DropColumn(
90 | name: "AccessFailedCount",
91 | table: "Users");
92 |
93 | migrationBuilder.DropColumn(
94 | name: "ConcurrencyStamp",
95 | table: "Users");
96 |
97 | migrationBuilder.DropColumn(
98 | name: "EmailConfirmed",
99 | table: "Users");
100 |
101 | migrationBuilder.DropColumn(
102 | name: "LockoutEnabled",
103 | table: "Users");
104 |
105 | migrationBuilder.DropColumn(
106 | name: "LockoutEnd",
107 | table: "Users");
108 |
109 | migrationBuilder.DropColumn(
110 | name: "NormalizedEmail",
111 | table: "Users");
112 |
113 | migrationBuilder.DropColumn(
114 | name: "NormalizedUserName",
115 | table: "Users");
116 |
117 | migrationBuilder.DropColumn(
118 | name: "PhoneNumber",
119 | table: "Users");
120 |
121 | migrationBuilder.DropColumn(
122 | name: "PhoneNumberConfirmed",
123 | table: "Users");
124 |
125 | migrationBuilder.DropColumn(
126 | name: "SecurityStamp",
127 | table: "Users");
128 |
129 | migrationBuilder.DropColumn(
130 | name: "TwoFactorEnabled",
131 | table: "Users");
132 |
133 | migrationBuilder.RenameColumn(
134 | name: "UserName",
135 | table: "Users",
136 | newName: "Username");
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Migrations/AuthenticationDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using AuthenticationServer.API.Models;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
7 |
8 | namespace AuthenticationServer.API.Migrations
9 | {
10 | [DbContext(typeof(AuthenticationDbContext))]
11 | partial class AuthenticationDbContextModelSnapshot : ModelSnapshot
12 | {
13 | protected override void BuildModel(ModelBuilder modelBuilder)
14 | {
15 | #pragma warning disable 612, 618
16 | modelBuilder
17 | .HasAnnotation("ProductVersion", "5.0.0");
18 |
19 | modelBuilder.Entity("AuthenticationServer.API.Models.RefreshToken", b =>
20 | {
21 | b.Property("Id")
22 | .ValueGeneratedOnAdd()
23 | .HasColumnType("TEXT");
24 |
25 | b.Property("Token")
26 | .HasColumnType("TEXT");
27 |
28 | b.Property("UserId")
29 | .HasColumnType("TEXT");
30 |
31 | b.HasKey("Id");
32 |
33 | b.ToTable("RefreshTokens");
34 | });
35 |
36 | modelBuilder.Entity("AuthenticationServer.API.Models.User", b =>
37 | {
38 | b.Property("Id")
39 | .ValueGeneratedOnAdd()
40 | .HasColumnType("TEXT");
41 |
42 | b.Property("AccessFailedCount")
43 | .HasColumnType("INTEGER");
44 |
45 | b.Property("ConcurrencyStamp")
46 | .HasColumnType("TEXT");
47 |
48 | b.Property("Email")
49 | .HasColumnType("TEXT");
50 |
51 | b.Property("EmailConfirmed")
52 | .HasColumnType("INTEGER");
53 |
54 | b.Property("LockoutEnabled")
55 | .HasColumnType("INTEGER");
56 |
57 | b.Property("LockoutEnd")
58 | .HasColumnType("TEXT");
59 |
60 | b.Property("NormalizedEmail")
61 | .HasColumnType("TEXT");
62 |
63 | b.Property("NormalizedUserName")
64 | .HasColumnType("TEXT");
65 |
66 | b.Property("PasswordHash")
67 | .HasColumnType("TEXT");
68 |
69 | b.Property("PhoneNumber")
70 | .HasColumnType("TEXT");
71 |
72 | b.Property("PhoneNumberConfirmed")
73 | .HasColumnType("INTEGER");
74 |
75 | b.Property("SecurityStamp")
76 | .HasColumnType("TEXT");
77 |
78 | b.Property("TwoFactorEnabled")
79 | .HasColumnType("INTEGER");
80 |
81 | b.Property("UserName")
82 | .HasColumnType("TEXT");
83 |
84 | b.HasKey("Id");
85 |
86 | b.ToTable("Users");
87 | });
88 | #pragma warning restore 612, 618
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Models/AccessToken.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace AuthenticationServer.API.Models
7 | {
8 | public class AccessToken
9 | {
10 | public string Value { get; set; }
11 | public DateTime ExpirationTime { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Models/AuthenticationConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace AuthenticationServer.API.Models
7 | {
8 | public class AuthenticationConfiguration
9 | {
10 | public string AccessTokenSecret { get; set; }
11 | public double AccessTokenExpirationMinutes { get; set; }
12 | public string Issuer { get; set; }
13 | public string Audience { get; set; }
14 | public string RefreshTokenSecret { get; set; }
15 | public double RefreshTokenExpirationMinutes { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Models/AuthenticationDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace AuthenticationServer.API.Models
8 | {
9 | public class AuthenticationDbContext : DbContext
10 | {
11 | public AuthenticationDbContext(DbContextOptions options) : base(options)
12 | {
13 |
14 | }
15 |
16 | public DbSet Users { get; set; }
17 | public DbSet RefreshTokens { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Models/RefreshToken.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace AuthenticationServer.API.Models
7 | {
8 | public class RefreshToken
9 | {
10 | public Guid Id { get; set; }
11 | public string Token { get; set; }
12 | public Guid UserId { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Models/User.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace AuthenticationServer.API.Models
8 | {
9 | public class User : IdentityUser
10 | {
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using AuthenticationServer.API.Models;
6 | using Microsoft.AspNetCore.Hosting;
7 | using Microsoft.EntityFrameworkCore;
8 | using Microsoft.Extensions.Configuration;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.Extensions.Hosting;
11 | using Microsoft.Extensions.Logging;
12 |
13 | namespace AuthenticationServer.API
14 | {
15 | public class Program
16 | {
17 | public static void Main(string[] args)
18 | {
19 | IHost host = CreateHostBuilder(args).Build();
20 |
21 | using(IServiceScope scope = host.Services.CreateScope())
22 | {
23 | using (AuthenticationDbContext context = scope.ServiceProvider.GetRequiredService())
24 | {
25 | context.Database.Migrate();
26 | }
27 | }
28 |
29 | host.Run();
30 | }
31 |
32 | public static IHostBuilder CreateHostBuilder(string[] args) =>
33 | Host.CreateDefaultBuilder(args)
34 | .ConfigureWebHostDefaults(webBuilder =>
35 | {
36 | webBuilder.UseStartup();
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:55964",
7 | "sslPort": 44395
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": false,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "AuthenticationServer.API": {
19 | "commandName": "Project",
20 | "launchBrowser": false,
21 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
22 | "environmentVariables": {
23 | "ASPNETCORE_ENVIRONMENT": "Development",
24 | "AZURE_CLIENT_ID": "",
25 | "AZURE_CLIENT_SECRET": "",
26 | "AZURE_TENANT_ID": ""
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Services/Authenticators/Authenticator.cs:
--------------------------------------------------------------------------------
1 | using AuthenticationServer.API.Models;
2 | using AuthenticationServer.API.Services.RefreshTokenRepositories;
3 | using AuthenticationServer.API.Services.TokenGenerators;
4 | using CoreLib.Responses;
5 | using System.Threading.Tasks;
6 |
7 | namespace AuthenticationServer.API.Services.Authenticators
8 | {
9 | public class Authenticator
10 | {
11 | private readonly AccessTokenGenerator _accessTokenGenerator;
12 | private readonly RefreshTokenGenerator _refreshTokenGenerator;
13 | private readonly IRefreshTokenRepository _refreshTokenRepository;
14 |
15 | public Authenticator(AccessTokenGenerator accessTokenGenerator,
16 | RefreshTokenGenerator refreshTokenGenerator,
17 | IRefreshTokenRepository refreshTokenRepository)
18 | {
19 | _accessTokenGenerator = accessTokenGenerator;
20 | _refreshTokenGenerator = refreshTokenGenerator;
21 | _refreshTokenRepository = refreshTokenRepository;
22 | }
23 |
24 | public async Task Authenticate(User user)
25 | {
26 | AccessToken accessToken = _accessTokenGenerator.GenerateToken(user);
27 | string refreshToken = _refreshTokenGenerator.GenerateToken();
28 |
29 | RefreshToken refreshTokenDTO = new RefreshToken()
30 | {
31 | Token = refreshToken,
32 | UserId = user.Id
33 | };
34 | await _refreshTokenRepository.Create(refreshTokenDTO);
35 |
36 | return new AuthenticatedUserResponse()
37 | {
38 | AccessToken = accessToken.Value,
39 | AccessTokenExpirationTime = accessToken.ExpirationTime,
40 | RefreshToken = refreshToken
41 | };
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Services/RefreshTokenRepositories/DatabaseRefreshTokenRepository.cs:
--------------------------------------------------------------------------------
1 | using AuthenticationServer.API.Models;
2 | using Microsoft.EntityFrameworkCore;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 |
8 | namespace AuthenticationServer.API.Services.RefreshTokenRepositories
9 | {
10 | public class DatabaseRefreshTokenRepository : IRefreshTokenRepository
11 | {
12 | private readonly AuthenticationDbContext _context;
13 |
14 | public DatabaseRefreshTokenRepository(AuthenticationDbContext context)
15 | {
16 | _context = context;
17 | }
18 |
19 | public async Task Create(RefreshToken refreshToken)
20 | {
21 | _context.RefreshTokens.Add(refreshToken);
22 | await _context.SaveChangesAsync();
23 | }
24 |
25 | public async Task Delete(Guid id)
26 | {
27 | RefreshToken refreshToken = await _context.RefreshTokens.FindAsync(id);
28 | if(refreshToken != null)
29 | {
30 | _context.RefreshTokens.Remove(refreshToken);
31 | await _context.SaveChangesAsync();
32 | }
33 | }
34 |
35 | public async Task DeleteAll(Guid userId)
36 | {
37 | IEnumerable refreshTokens = await _context.RefreshTokens
38 | .Where(t => t.UserId == userId)
39 | .ToListAsync();
40 |
41 | _context.RefreshTokens.RemoveRange(refreshTokens);
42 | await _context.SaveChangesAsync();
43 | }
44 |
45 | public async Task GetByToken(string token)
46 | {
47 | return await _context.RefreshTokens.FirstOrDefaultAsync(t => t.Token == token);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Services/RefreshTokenRepositories/IRefreshTokenRepository.cs:
--------------------------------------------------------------------------------
1 | using AuthenticationServer.API.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace AuthenticationServer.API.Services.RefreshTokenRepositories
8 | {
9 | public interface IRefreshTokenRepository
10 | {
11 | Task GetByToken(string token);
12 |
13 | Task Create(RefreshToken refreshToken);
14 |
15 | Task Delete(Guid id);
16 |
17 | Task DeleteAll(Guid userId);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Services/RefreshTokenRepositories/InMemoryRefreshTokenRepository.cs:
--------------------------------------------------------------------------------
1 | using AuthenticationServer.API.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace AuthenticationServer.API.Services.RefreshTokenRepositories
8 | {
9 | public class InMemoryRefreshTokenRepository : IRefreshTokenRepository
10 | {
11 | private readonly List _refreshTokens = new List();
12 |
13 | public Task Create(RefreshToken refreshToken)
14 | {
15 | refreshToken.Id = Guid.NewGuid();
16 |
17 | _refreshTokens.Add(refreshToken);
18 |
19 | return Task.CompletedTask;
20 | }
21 |
22 | public Task GetByToken(string token)
23 | {
24 | RefreshToken refreshToken = _refreshTokens.FirstOrDefault(r => r.Token == token);
25 |
26 | return Task.FromResult(refreshToken);
27 | }
28 |
29 | public Task Delete(Guid id)
30 | {
31 | _refreshTokens.RemoveAll(r => r.Id == id);
32 |
33 | return Task.CompletedTask;
34 | }
35 |
36 | public Task DeleteAll(Guid userId)
37 | {
38 | _refreshTokens.RemoveAll(r => r.UserId == userId);
39 |
40 | return Task.CompletedTask;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Services/TokenGenerators/AccessTokenGenerator.cs:
--------------------------------------------------------------------------------
1 | using AuthenticationServer.API.Models;
2 | using Microsoft.IdentityModel.Tokens;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IdentityModel.Tokens.Jwt;
6 | using System.Linq;
7 | using System.Security.Claims;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace AuthenticationServer.API.Services.TokenGenerators
12 | {
13 | public class AccessTokenGenerator
14 | {
15 | private readonly AuthenticationConfiguration _configuration;
16 | private readonly TokenGenerator _tokenGenerator;
17 |
18 | public AccessTokenGenerator(AuthenticationConfiguration configuration, TokenGenerator tokenGenerator)
19 | {
20 | _configuration = configuration;
21 | _tokenGenerator = tokenGenerator;
22 | }
23 |
24 | public AccessToken GenerateToken(User user)
25 | {
26 | List claims = new List()
27 | {
28 | new Claim("id", user.Id.ToString()),
29 | new Claim(ClaimTypes.Email, user.Email),
30 | new Claim(ClaimTypes.Name, user.UserName),
31 | };
32 |
33 | DateTime expirationTime = DateTime.UtcNow.AddMinutes(_configuration.AccessTokenExpirationMinutes);
34 | string value = _tokenGenerator.GenerateToken(
35 | _configuration.AccessTokenSecret,
36 | _configuration.Issuer,
37 | _configuration.Audience,
38 | expirationTime,
39 | claims);
40 |
41 | return new AccessToken()
42 | {
43 | Value = value,
44 | ExpirationTime = expirationTime
45 | };
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Services/TokenGenerators/RefreshTokenGenerator.cs:
--------------------------------------------------------------------------------
1 | using AuthenticationServer.API.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace AuthenticationServer.API.Services.TokenGenerators
8 | {
9 | public class RefreshTokenGenerator
10 | {
11 | private readonly AuthenticationConfiguration _configuration;
12 | private readonly TokenGenerator _tokenGenerator;
13 |
14 | public RefreshTokenGenerator(AuthenticationConfiguration configuration, TokenGenerator tokenGenerator)
15 | {
16 | _configuration = configuration;
17 | _tokenGenerator = tokenGenerator;
18 | }
19 |
20 | public string GenerateToken()
21 | {
22 | DateTime expirationTime = DateTime.UtcNow.AddMinutes(_configuration.RefreshTokenExpirationMinutes);
23 |
24 | return _tokenGenerator.GenerateToken(
25 | _configuration.RefreshTokenSecret,
26 | _configuration.Issuer,
27 | _configuration.Audience,
28 | expirationTime);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Services/TokenGenerators/TokenGenerator.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.IdentityModel.Tokens;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IdentityModel.Tokens.Jwt;
5 | using System.Linq;
6 | using System.Security.Claims;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace AuthenticationServer.API.Services.TokenGenerators
11 | {
12 | public class TokenGenerator
13 | {
14 | public string GenerateToken(string secretKey, string issuer, string audience, DateTime utcExpirationTime,
15 | IEnumerable claims = null)
16 | {
17 | SecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
18 | SigningCredentials credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
19 |
20 | JwtSecurityToken token = new JwtSecurityToken(
21 | issuer,
22 | audience,
23 | claims,
24 | DateTime.UtcNow,
25 | utcExpirationTime,
26 | credentials);
27 |
28 | return new JwtSecurityTokenHandler().WriteToken(token);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Services/TokenValidators/RefreshTokenValidator.cs:
--------------------------------------------------------------------------------
1 | using AuthenticationServer.API.Models;
2 | using Microsoft.IdentityModel.Tokens;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IdentityModel.Tokens.Jwt;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace AuthenticationServer.API.Services.TokenValidators
11 | {
12 | public class RefreshTokenValidator
13 | {
14 | private readonly AuthenticationConfiguration _configuration;
15 |
16 | public RefreshTokenValidator(AuthenticationConfiguration configuration)
17 | {
18 | _configuration = configuration;
19 | }
20 |
21 | public bool Validate(string refreshToken)
22 | {
23 | TokenValidationParameters validationParameters = new TokenValidationParameters()
24 | {
25 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.RefreshTokenSecret)),
26 | ValidIssuer = _configuration.Issuer,
27 | ValidAudience = _configuration.Audience,
28 | ValidateIssuerSigningKey = true,
29 | ValidateIssuer = true,
30 | ValidateAudience = true,
31 | ClockSkew = TimeSpan.Zero
32 | };
33 |
34 | JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
35 |
36 | try
37 | {
38 | tokenHandler.ValidateToken(refreshToken, validationParameters, out SecurityToken validatedToken);
39 | return true;
40 | }
41 | catch (Exception)
42 | {
43 | return false;
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using AuthenticationServer.API.Models;
4 | using AuthenticationServer.API.Services.Authenticators;
5 | using AuthenticationServer.API.Services.RefreshTokenRepositories;
6 | using AuthenticationServer.API.Services.TokenGenerators;
7 | using AuthenticationServer.API.Services.TokenValidators;
8 | using Azure.Identity;
9 | using Azure.Security.KeyVault.Secrets;
10 | using Microsoft.AspNetCore.Authentication.JwtBearer;
11 | using Microsoft.AspNetCore.Builder;
12 | using Microsoft.AspNetCore.Hosting;
13 | using Microsoft.EntityFrameworkCore;
14 | using Microsoft.Extensions.Configuration;
15 | using Microsoft.Extensions.DependencyInjection;
16 | using Microsoft.Extensions.Hosting;
17 | using Microsoft.IdentityModel.Tokens;
18 |
19 | namespace AuthenticationServer.API
20 | {
21 | public class Startup
22 | {
23 | private readonly IConfiguration _configuration;
24 |
25 | public Startup(IConfiguration configuration)
26 | {
27 | _configuration = configuration;
28 | }
29 |
30 | public void ConfigureServices(IServiceCollection services)
31 | {
32 | services.AddControllers();
33 |
34 | services.AddIdentityCore(o =>
35 | {
36 | o.User.RequireUniqueEmail = true;
37 |
38 | o.Password.RequireDigit = false;
39 | o.Password.RequireNonAlphanumeric = false;
40 | o.Password.RequireUppercase = false;
41 | o.Password.RequiredLength = 0;
42 | }).AddEntityFrameworkStores();
43 |
44 | AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
45 | _configuration.Bind("Authentication", authenticationConfiguration);
46 |
47 | //SecretClient keyVaultClient = new SecretClient(
48 | // new Uri(_configuration.GetValue("KeyVaultUri")),
49 | // new DefaultAzureCredential());
50 | //authenticationConfiguration.AccessTokenSecret = keyVaultClient.GetSecret("access-token-secret").Value.Value;
51 |
52 | services.AddSingleton(authenticationConfiguration);
53 |
54 | string connectionString = _configuration.GetConnectionString("sqlite");
55 | services.AddDbContext(o => o.UseSqlite(connectionString));
56 |
57 | services.AddSingleton();
58 | services.AddSingleton();
59 | services.AddSingleton();
60 | services.AddScoped();
61 | services.AddSingleton();
62 | services.AddScoped();
63 |
64 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o =>
65 | {
66 | o.TokenValidationParameters = new TokenValidationParameters()
67 | {
68 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authenticationConfiguration.AccessTokenSecret)),
69 | ValidIssuer = authenticationConfiguration.Issuer,
70 | ValidAudience = authenticationConfiguration.Audience,
71 | ValidateIssuerSigningKey = true,
72 | ValidateIssuer = true,
73 | ValidateAudience = true,
74 | ClockSkew = TimeSpan.Zero
75 | };
76 | });
77 | }
78 |
79 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
80 | {
81 | if (env.IsDevelopment())
82 | {
83 | app.UseDeveloperExceptionPage();
84 | }
85 |
86 | app.UseRouting();
87 |
88 | app.UseAuthentication();
89 | app.UseAuthorization();
90 |
91 | app.UseEndpoints(endpoints =>
92 | {
93 | endpoints.MapControllers();
94 | });
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/AuthenticationServer.API/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*",
10 | "KeyVaultUri": "https://auth-server-ss.vault.azure.net/",
11 | "Authentication": {
12 | "AccessTokenSecret": "4y7XS2AHicSOs2uUJCxwlHWqTJNExW3UDUjMeXi96uLEso1YV4RazqQubpFBdx0zZGtdxBelKURhh0WXxPR0mEJQHk_0U9HeYtqcMManhoP3X2Ge8jgxh6k4C_Gd4UPTc6lkx0Ca5eRE16ciFQ6wmYDnaXC8NbngGqartHccAxE",
13 | "RefreshTokenSecret": "3y7XS2AHicSOs2uUJCxwlHWqTJNExW3UDUjMeXi96uLEso1YV4RazqQubpFBdx0zZGtdxBelKURhh0WXxPR0mEJQHk_0U9HeYtqcMManhoP3X2Ge8jgxh6k4C_Gd4UPTc6lkx0Ca5eRE16ciFQ6wmYDnaXC8NbngGqartHccAxE",
14 | "AccessTokenExpirationMinutes": "15",
15 | "RefreshTokenExpirationMinutes": "131400",
16 | "Issuer": "https://localhost:5001",
17 | "Audience": "https://localhost:5001"
18 | },
19 | "ConnectionStrings": {
20 | "sqlite": "Data Source=Authentication.db"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/AuthenticationServer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30503.244
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthenticationServer.API", "AuthenticationServer.API\AuthenticationServer.API.csproj", "{57C0E66F-A1B6-4EFB-884E-64514F01E2CF}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataServer.API", "DataServer.API\DataServer.API.csproj", "{50E31A1D-389B-4252-BD1E-BFD5EC0523E0}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientApplication", "ClientApplication\ClientApplication.csproj", "{DD4F220E-C0FB-463C-9710-5C5182ABB872}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreLib", "Core\CoreLib.csproj", "{820B0153-643F-4123-8A37-11A73BEEA9C1}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {57C0E66F-A1B6-4EFB-884E-64514F01E2CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {57C0E66F-A1B6-4EFB-884E-64514F01E2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {57C0E66F-A1B6-4EFB-884E-64514F01E2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {57C0E66F-A1B6-4EFB-884E-64514F01E2CF}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {50E31A1D-389B-4252-BD1E-BFD5EC0523E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {50E31A1D-389B-4252-BD1E-BFD5EC0523E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {50E31A1D-389B-4252-BD1E-BFD5EC0523E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {50E31A1D-389B-4252-BD1E-BFD5EC0523E0}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {DD4F220E-C0FB-463C-9710-5C5182ABB872}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {DD4F220E-C0FB-463C-9710-5C5182ABB872}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {DD4F220E-C0FB-463C-9710-5C5182ABB872}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {DD4F220E-C0FB-463C-9710-5C5182ABB872}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {820B0153-643F-4123-8A37-11A73BEEA9C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {820B0153-643F-4123-8A37-11A73BEEA9C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {820B0153-643F-4123-8A37-11A73BEEA9C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {820B0153-643F-4123-8A37-11A73BEEA9C1}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {22080EC3-FAA9-448A-B298-F86B819E92D0}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/ClientApplication/ClientApplication.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ClientApplication/Http/AutoRefreshHttpMessageHandler.cs:
--------------------------------------------------------------------------------
1 | using ClientApplication.Services;
2 | using ClientApplication.Stores;
3 | using CoreLib.Requests;
4 | using CoreLib.Responses;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Net.Http;
9 | using System.Net.Http.Headers;
10 | using System.Text;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 |
14 | namespace ClientApplication.Http
15 | {
16 | public class AutoRefreshHttpMessageHandler : DelegatingHandler
17 | {
18 | private readonly TokenStore _tokenStore;
19 | private readonly IRefreshService _refreshService;
20 |
21 | public AutoRefreshHttpMessageHandler(TokenStore tokenStore, IRefreshService refreshService) : base(new HttpClientHandler())
22 | {
23 | _tokenStore = tokenStore;
24 | _refreshService = refreshService;
25 | }
26 |
27 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
28 | {
29 | if (IsAccessTokenExpired())
30 | {
31 | await RefreshAccessToken();
32 | }
33 |
34 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenStore.AccessToken);
35 |
36 | return await base.SendAsync(request, cancellationToken);
37 | }
38 |
39 | private bool IsAccessTokenExpired()
40 | {
41 | return _tokenStore.AccessTokenExpirationTime <= DateTime.UtcNow;
42 | }
43 |
44 | private async Task RefreshAccessToken()
45 | {
46 | AuthenticatedUserResponse authenticatedUserResponse = await _refreshService.Refresh(new RefreshRequest
47 | {
48 | RefreshToken = _tokenStore.RefreshToken
49 | });
50 |
51 | _tokenStore.AccessToken = authenticatedUserResponse.AccessToken;
52 | _tokenStore.AccessTokenExpirationTime = authenticatedUserResponse.AccessTokenExpirationTime;
53 | _tokenStore.RefreshToken = authenticatedUserResponse.RefreshToken;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/ClientApplication/Program.cs:
--------------------------------------------------------------------------------
1 | using ClientApplication.Http;
2 | using ClientApplication.Services;
3 | using ClientApplication.Stores;
4 | using CoreLib.Requests;
5 | using CoreLib.Responses;
6 | using Refit;
7 | using System;
8 | using System.Linq;
9 | using System.Net.Http;
10 | using System.Threading.Tasks;
11 |
12 | namespace ClientApplication
13 | {
14 | class Program
15 | {
16 | static async Task Main(string[] args)
17 | {
18 | string authenticationBaseUrl = "http://localhost:5000";
19 | string dataBaseUrl = "http://localhost:8000";
20 |
21 | IRefreshService refreshService = RestService.For(authenticationBaseUrl);
22 | TokenStore tokenStore = new TokenStore();
23 | AutoRefreshHttpMessageHandler autoRefreshHttpMessageHandler = new AutoRefreshHttpMessageHandler(tokenStore, refreshService);
24 |
25 | IRegisterService registerService = RestService.For(authenticationBaseUrl);
26 | ILoginService loginService = RestService.For(authenticationBaseUrl);
27 | IDataService dataService = RestService.For(new HttpClient(autoRefreshHttpMessageHandler)
28 | {
29 | BaseAddress = new Uri(dataBaseUrl)
30 | });
31 |
32 | try
33 | {
34 | await registerService.Register(new RegisterRequest
35 | {
36 | Email = "test@gmail.com",
37 | Username = "SingletonSean",
38 | Password = "test123",
39 | ConfirmPassword = "test123"
40 | });
41 | }
42 | catch (ApiException ex)
43 | {
44 | ErrorResponse errorResponse = await ex.GetContentAsAsync();
45 | Console.WriteLine(errorResponse.ErrorMessages.FirstOrDefault());
46 | }
47 |
48 | AuthenticatedUserResponse loginResponse = await loginService.Login(new LoginRequest()
49 | {
50 | Username = "SingletonSean",
51 | Password = "test123",
52 | });
53 | tokenStore.AccessToken = loginResponse.AccessToken;
54 | tokenStore.AccessTokenExpirationTime = loginResponse.AccessTokenExpirationTime;
55 | tokenStore.RefreshToken = loginResponse.RefreshToken;
56 |
57 | DataResponse dataResponse = await dataService.GetData();
58 |
59 | Console.ReadKey();
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/ClientApplication/Services/IDataService.cs:
--------------------------------------------------------------------------------
1 | using CoreLib.Responses;
2 | using Refit;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ClientApplication.Services
10 | {
11 | public interface IDataService
12 | {
13 | [Get("/data")]
14 | Task GetData();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ClientApplication/Services/ILoginService.cs:
--------------------------------------------------------------------------------
1 | using CoreLib.Requests;
2 | using CoreLib.Responses;
3 | using Refit;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace ClientApplication.Services
11 | {
12 | public interface ILoginService
13 | {
14 | [Post("/login")]
15 | Task Login([Body] LoginRequest loginRequest);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ClientApplication/Services/IRefreshService.cs:
--------------------------------------------------------------------------------
1 | using CoreLib.Requests;
2 | using CoreLib.Responses;
3 | using Refit;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace ClientApplication.Services
11 | {
12 | public interface IRefreshService
13 | {
14 | [Post("/refresh")]
15 | Task Refresh([Body] RefreshRequest refreshRequest);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ClientApplication/Services/IRegisterService.cs:
--------------------------------------------------------------------------------
1 | using CoreLib.Requests;
2 | using Refit;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ClientApplication.Services
10 | {
11 | public interface IRegisterService
12 | {
13 | [Post("/register")]
14 | Task Register([Body] RegisterRequest registerRequest);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ClientApplication/Stores/TokenStore.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ClientApplication.Stores
8 | {
9 | public class TokenStore
10 | {
11 | public string AccessToken { get; set; }
12 | public DateTime AccessTokenExpirationTime { get; set; }
13 | public string RefreshToken { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Core/CoreLib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Core/Requests/LoginRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace CoreLib.Requests
8 | {
9 | public class LoginRequest
10 | {
11 | [Required]
12 | public string Username { get; set; }
13 |
14 | [Required]
15 | public string Password { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Core/Requests/RefreshRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace CoreLib.Requests
8 | {
9 | public class RefreshRequest
10 | {
11 | [Required]
12 | public string RefreshToken { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Core/Requests/RegisterRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace CoreLib.Requests
8 | {
9 | public class RegisterRequest
10 | {
11 | [Required]
12 | [EmailAddress]
13 | public string Email { get; set; }
14 |
15 | [Required]
16 | public string Username { get; set; }
17 |
18 | [Required]
19 | public string Password { get; set; }
20 |
21 | [Required]
22 | public string ConfirmPassword { get; set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Core/Responses/AuthenticatedUserResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace CoreLib.Responses
7 | {
8 | public class AuthenticatedUserResponse
9 | {
10 | public string AccessToken { get; set; }
11 | public DateTime AccessTokenExpirationTime { get; set; }
12 | public string RefreshToken { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Core/Responses/DataResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace CoreLib.Responses
6 | {
7 | public class DataResponse
8 | {
9 | public string Value { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Core/Responses/ErrorResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace CoreLib.Responses
7 | {
8 | public class ErrorResponse
9 | {
10 | public IEnumerable ErrorMessages { get; set; }
11 |
12 | public ErrorResponse() : this(new List()) { }
13 |
14 | public ErrorResponse(string errorMessage) : this(new List() { errorMessage }) { }
15 |
16 | public ErrorResponse(IEnumerable errorMessages)
17 | {
18 | ErrorMessages = errorMessages;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/DataServer.API/Controllers/HomeController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Security.Claims;
5 | using System.Threading.Tasks;
6 | using CoreLib.Responses;
7 | using Microsoft.AspNetCore.Authorization;
8 | using Microsoft.AspNetCore.Mvc;
9 |
10 | namespace DataServer.API.Controllers
11 | {
12 | public class HomeController : Controller
13 | {
14 | [Authorize]
15 | [HttpGet("data")]
16 | public IActionResult Index()
17 | {
18 | string id = HttpContext.User.FindFirstValue("id");
19 | string email = HttpContext.User.FindFirstValue(ClaimTypes.Email);
20 | string username = HttpContext.User.FindFirstValue(ClaimTypes.Name);
21 |
22 | return Ok(new DataResponse
23 | {
24 | Value = "123"
25 | });
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/DataServer.API/DataServer.API.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/DataServer.API/DataServer.API.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | MvcControllerEmptyScaffolder
5 | root/Controller
6 | DataServer.API
7 |
8 |
9 | ProjectDebugger
10 |
11 |
--------------------------------------------------------------------------------
/DataServer.API/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.Hosting;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace DataServer.API
11 | {
12 | public class Program
13 | {
14 | public static void Main(string[] args)
15 | {
16 | CreateHostBuilder(args).Build().Run();
17 | }
18 |
19 | public static IHostBuilder CreateHostBuilder(string[] args) =>
20 | Host.CreateDefaultBuilder(args)
21 | .ConfigureWebHostDefaults(webBuilder =>
22 | {
23 | webBuilder.UseStartup();
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/DataServer.API/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:58887",
8 | "sslPort": 44324
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": false,
15 | "launchUrl": "weatherforecast",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "DataServer.API": {
21 | "commandName": "Project",
22 | "launchBrowser": false,
23 | "launchUrl": "weatherforecast",
24 | "applicationUrl": "https://localhost:8001;http://localhost:8000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development",
27 | "AZURE_CLIENT_ID": "",
28 | "AZURE_CLIENT_SECRET": "",
29 | "AZURE_TENANT_ID": ""
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/DataServer.API/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Azure.Identity;
7 | using Azure.Security.KeyVault.Secrets;
8 | using Microsoft.AspNetCore.Authentication.JwtBearer;
9 | using Microsoft.AspNetCore.Builder;
10 | using Microsoft.AspNetCore.Hosting;
11 | using Microsoft.AspNetCore.HttpsPolicy;
12 | using Microsoft.AspNetCore.Mvc;
13 | using Microsoft.Extensions.Configuration;
14 | using Microsoft.Extensions.DependencyInjection;
15 | using Microsoft.Extensions.Hosting;
16 | using Microsoft.Extensions.Logging;
17 | using Microsoft.IdentityModel.Tokens;
18 |
19 | namespace DataServer.API
20 | {
21 | public class Startup
22 | {
23 | public Startup(IConfiguration configuration)
24 | {
25 | Configuration = configuration;
26 | }
27 |
28 | public IConfiguration Configuration { get; }
29 |
30 | public void ConfigureServices(IServiceCollection services)
31 | {
32 | services.AddControllers();
33 |
34 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o =>
35 | {
36 | AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
37 |
38 | //SecretClient keyVaultClient = new SecretClient(
39 | // new Uri(Configuration.GetValue("KeyVaultUri")),
40 | // new DefaultAzureCredential());
41 | //authenticationConfiguration.AccessTokenSecret = keyVaultClient.GetSecret("access-token-secret").Value.Value;
42 |
43 | Configuration.Bind("Authentication", authenticationConfiguration);
44 |
45 | o.TokenValidationParameters = new TokenValidationParameters()
46 | {
47 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authenticationConfiguration.AccessTokenSecret)),
48 | ValidIssuer = authenticationConfiguration.Issuer,
49 | ValidAudience = authenticationConfiguration.Audience,
50 | ValidateIssuerSigningKey = true,
51 | ValidateIssuer = true,
52 | ValidateAudience = true,
53 | ClockSkew = TimeSpan.Zero
54 | };
55 | });
56 | }
57 |
58 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
59 | {
60 | if (env.IsDevelopment())
61 | {
62 | app.UseDeveloperExceptionPage();
63 | }
64 |
65 | app.UseRouting();
66 |
67 | app.UseAuthentication();
68 | app.UseAuthorization();
69 |
70 | app.UseEndpoints(endpoints =>
71 | {
72 | endpoints.MapControllers();
73 | });
74 | }
75 |
76 | private class AuthenticationConfiguration
77 | {
78 | public string AccessTokenSecret { get; set; }
79 | public string Issuer { get; set; }
80 | public string Audience { get; set; }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/DataServer.API/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/DataServer.API/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*",
10 | "KeyVaultUri": "https://authserver.vault.azure.net/",
11 | "Authentication": {
12 | "AccessTokenSecret": "4y7XS2AHicSOs2uUJCxwlHWqTJNExW3UDUjMeXi96uLEso1YV4RazqQubpFBdx0zZGtdxBelKURhh0WXxPR0mEJQHk_0U9HeYtqcMManhoP3X2Ge8jgxh6k4C_Gd4UPTc6lkx0Ca5eRE16ciFQ6wmYDnaXC8NbngGqartHccAxE",
13 | "Issuer": "https://localhost:5001",
14 | "Audience": "https://localhost:5001"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------