Skip to content

Commit

Permalink
End of section 12
Browse files Browse the repository at this point in the history
  • Loading branch information
fredrik-hjelmaeus committed Mar 3, 2021
1 parent 7249aa4 commit 56b81e9
Show file tree
Hide file tree
Showing 19 changed files with 1,041 additions and 5 deletions.
1 change: 1 addition & 0 deletions API/API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
93 changes: 93 additions & 0 deletions API/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Security.Claims;
using System.Threading.Tasks;
using API.DTOs;
using API.Services;
using Domain;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace API.Controllers
{
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class AccountController : ControllerBase
{
private readonly UserManager<AppUser> _userManager;
private readonly SignInManager<AppUser> _signInManager;
private readonly TokenService _tokenService;
public AccountController(UserManager<AppUser> userManager,
SignInManager<AppUser> signInManager, TokenService tokenService)
{
_tokenService = tokenService;
_signInManager = signInManager;
_userManager = userManager;
}

[HttpPost("login")]
public async Task<ActionResult<UserDto>> Login(LoginDto loginDto)
{
var user = await _userManager.FindByEmailAsync(loginDto.Email);

if (user == null) return Unauthorized();

var result = await _signInManager.CheckPasswordSignInAsync(user, loginDto.Password, false);

if (result.Succeeded)
{
return CreateUserObject(user);
}
return Unauthorized();
}

[HttpPost("register")]
public async Task<ActionResult<UserDto>> Register(RegisterDto registerDto)
{
if(await _userManager.Users.AnyAsync(x => x.Email == registerDto.Email))
{
return BadRequest("Email taken");
}
if(await _userManager.Users.AnyAsync(x => x.UserName == registerDto.UserName))
{
return BadRequest("Username taken");
}

var user = new AppUser
{
DisplayName = registerDto.DisplayName,
Email = registerDto.Email,
UserName = registerDto.UserName
};
var result = await _userManager.CreateAsync(user, registerDto.Password);

if(result.Succeeded)
{
return CreateUserObject(user);
};

return BadRequest("Problem registering user");
}

[Authorize]
[HttpGet]
public async Task<ActionResult<UserDto>> GetCurrentUser()
{
var user = await _userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email));

return CreateUserObject(user);
}

private UserDto CreateUserObject(AppUser user)
{
return new UserDto
{
DisplayName = user.DisplayName,
Image = null,
Token = _tokenService.CreateToken(user),
Username = user.UserName
};
}
}
}
1 change: 1 addition & 0 deletions API/Controllers/ActivitiesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using Application.Activities;
using Domain;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;


Expand Down
8 changes: 8 additions & 0 deletions API/DTOs/LoginDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace API.DTOs
{
public class LoginDto
{
public string Email { get; set; }
public string Password { get; set; }
}
}
20 changes: 20 additions & 0 deletions API/DTOs/RegisterDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;

namespace API.DTOs
{
public class RegisterDto
{
[Required]
public string DisplayName { get; set; }

[Required]
[EmailAddress]
public string Email { get; set; }

[Required]
[RegularExpression("(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{4,8}$",ErrorMessage = "Password must be complex")]
public string Password { get; set; }
[Required]
public string UserName { get; set; }
}
}
10 changes: 10 additions & 0 deletions API/DTOs/UserDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace API.DTOs
{
public class UserDto
{
public string DisplayName { get; set; }
public string Token { get; set; }
public string Username { get; set; }
public string Image { get; set; }
}
}
43 changes: 43 additions & 0 deletions API/Extensions/IdentityServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Text;
using API.Services;
using Domain;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Persistence;

namespace API.Extensions
{
public static class IdentityServiceExtensions
{
public static IServiceCollection AddIdentityServices(this IServiceCollection services,
IConfiguration config)
{
services.AddIdentityCore<AppUser>(opt =>
{
opt.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<DataContext>()
.AddSignInManager<SignInManager<AppUser>>();

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"]));

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddScoped<TokenService>();

return services;
}
}
}
5 changes: 4 additions & 1 deletion API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Domain;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -25,8 +27,9 @@ public static async Task Main(string[] args)
try
{
var context = services.GetRequiredService<DataContext>();
var userManager = services.GetRequiredService<UserManager<AppUser>>();
await context.Database.MigrateAsync();
await Seed.SeedData(context);
await Seed.SeedData(context,userManager);
}
catch (Exception ex)
{
Expand Down
47 changes: 47 additions & 0 deletions API/Services/TokenService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Domain;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;

namespace API.Services
{
public class TokenService
{
private readonly IConfiguration _config;
public TokenService(IConfiguration config)
{
_config = config;

}

public string CreateToken(AppUser user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Email, user.Email),
};

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["TokenKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds
};

var tokenHandler = new JwtSecurityTokenHandler();

var token = tokenHandler.CreateToken(tokenDescriptor);

return tokenHandler.WriteToken(token);
}
}
}
11 changes: 10 additions & 1 deletion API/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
using API.Extensions;
using FluentValidation.AspNetCore;
using API.Middleware;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;

namespace API
{
Expand All @@ -36,11 +38,17 @@ public Startup(IConfiguration config)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddFluentValidation(config =>
services.AddControllers(opt =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
opt.Filters.Add(new AuthorizeFilter(policy));
})
.AddFluentValidation(config =>
{
config.RegisterValidatorsFromAssemblyContaining<Create>();
});
services.AddApplicationServices(_config);
services.AddIdentityServices(_config);
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand All @@ -60,6 +68,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

app.UseCors("CorsPolicy");

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
Expand Down
3 changes: 2 additions & 1 deletion API/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
},
"ConnectionStrings": {
"DefaultConnection": "Data source=reactivities.db"
}
},
"TokenKey": "super secret key"
}
Binary file modified API/reactivities.db
Binary file not shown.
10 changes: 10 additions & 0 deletions Domain/AppUser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Identity;

namespace Domain
{
public class AppUser : IdentityUser
{
public string DisplayName { get; set; }
public string Bio { get; set; }
}
}
4 changes: 4 additions & 0 deletions Domain/Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.1" />
</ItemGroup>

</Project>
3 changes: 2 additions & 1 deletion Persistence/DataContext.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Domain;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Persistence
{
public class DataContext : DbContext
public class DataContext : IdentityDbContext<AppUser>
{
public DataContext(DbContextOptions options) : base(options)
{
Expand Down
Loading

0 comments on commit 56b81e9

Please sign in to comment.