added authentication

This commit is contained in:
henrydays 2018-08-17 23:28:12 +01:00
parent 5db0933c89
commit 54d19b3567
27 changed files with 398 additions and 26 deletions

View File

@ -4,6 +4,12 @@
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": ".NET Core Launch (web)",
"type": "coreclr",

View File

@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace API.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase

View File

@ -1,4 +1,4 @@
using API.Models;
using api.Models;
using Microsoft.EntityFrameworkCore;
namespace API.Data
@ -6,8 +6,10 @@ namespace API.Data
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options):base(options) { }
public DbSet<Value> Values{get;set;}
public DbSet<User> Users { get; set;}
}
}

View File

@ -1,4 +1,5 @@
// <auto-generated />
using System;
using API.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -15,7 +16,23 @@ namespace API.Migrations
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("API.Models.Value", b =>
modelBuilder.Entity("api.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("PasswordHash");
b.Property<byte[]>("PasswordSalt");
b.Property<string>("Username");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("api.Models.Value", b =>
{
b.Property<int>("id")
.ValueGeneratedOnAdd();

View File

@ -1,4 +1,4 @@
namespace API.Models
namespace api.Models
{
public class Value
{

View File

@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using api.Data;
using API.Data;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
@ -12,6 +15,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace API
{
@ -33,6 +37,24 @@ namespace API
services.AddDbContext<DataContext>(x=>x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//cors support
services.AddCors();
//cria uma instancia para cada request do cliente (mantem instancia entre CORS)
services.AddScoped<IAuthRepository, AuthRepository>();
//autenticação para o token
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options=> {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
ValidateIssuer = false,
ValidateAudience= false,
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -48,7 +70,11 @@ namespace API
}
// app.UseHttpsRedirection();
app.UseMvc();
//cores supporte
app.UseCors(x=>x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseAuthentication();
app.UseMvc();
}
}
}

Binary file not shown.

View File

@ -1,7 +1,12 @@
{
"AppSettings":
{
"Token":"NbTSjGOaBMfweUyCvINv23Tt9jHhiEz2MBcYrYPhd24xWsztIV3bgDGOsWfRJFb8"
},
"ConnectionStrings":
{
"DefaultConnection":"Data Source= api.db"
},
"Logging": {
"LogLevel": {

Binary file not shown.

Binary file not shown.

2
API/obj/API.csproj.nuget.cache Executable file → Normal file
View File

@ -1,5 +1,5 @@
{
"version": 1,
"dgSpecHash": "pWpXdShPoDL3Y6cQrbcQrVzH5rp5FbLY4oDKQxvRGDuuPI75KZj0JxPxF8tay1NT3PBmWY+++45R5z1YQKm7mA==",
"dgSpecHash": "C2en1YFh4xIIX8rPmhWxyYZ5XptuwTimwDdsRRRxcbl+IKf3uf5g3+8FID5AKf1Woqqa0VFcMiB2OA/aHHfJCw==",
"success": true
}

2
API/obj/API.csproj.nuget.g.props Executable file → Normal file
View File

@ -3,7 +3,7 @@
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">/Users/henrique/enei2019/API/obj/project.assets.json</ProjectAssetsFile>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">/Users/henrique/enei2019/api/obj/project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/Users/henrique/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/Users/henrique/.nuget/packages/;/usr/local/share/dotnet/sdk/NuGetFallbackFolder</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>

View File

@ -1 +1 @@
bfbc916619c950582f70677b5e39995f477abab6
b790b8f5e973558fd81867f820388b2751d038f2

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,96 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using api.Dtos;
using api.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
namespace api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly Data.IAuthRepository repo;
private readonly IConfiguration config;
public AuthController(Data.IAuthRepository repo, IConfiguration config)
{
this.config = config;
this.repo = repo;
}
[HttpPost("register")]
public async Task<IActionResult> Register(UserForRegisterDto UserForRegisterDto)
{
//validar a request
UserForRegisterDto.Username = UserForRegisterDto.Username.ToLower();
if (await repo.UserExists(UserForRegisterDto.Username))
return BadRequest("username already exists");
var userToCreate = new User
{
Username = UserForRegisterDto.Username
};
var createUser = await repo.Register(userToCreate, UserForRegisterDto.Password);
return StatusCode(201);
}
[HttpPost("login")]
public async Task<IActionResult> Login(UserForLoginDto UserForLoginDto)
{
//verifica se o utilizador existe na base de dados e se consegue fazer login
var userFromRepo = await repo.Login(UserForLoginDto.Username.ToLower(), UserForLoginDto.Password);
//Se não conseguir
if (userFromRepo == null)
{
return Unauthorized();
}
//o token vai ter 2 claims, uma vai ser o id e outra vai ser o nome
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, userFromRepo.Id.ToString()),
new Claim(ClaimTypes.Name, userFromRepo.Username)
};
//obtem a key na app settings
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetSection("AppSettings:Token").Value));
//faz hashing da key na app settings
var creds= new SigningCredentials(key,SecurityAlgorithms.HmacSha512Signature);
//criamos um token
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
//data de expiração (atual + 24 horas)
Expires = DateTime.Now.AddDays(1),
//passa as signing credentials definidas em cima
SigningCredentials = creds
};
//criamos um token handler
var tokenHandler = new JwtSecurityTokenHandler();
//em seguida criamos o token
var token = tokenHandler.CreateToken(tokenDescriptor);
//devolvemos ao cliente
return Ok(new {
token= tokenHandler.WriteToken(token)
});
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Threading.Tasks;
using api.Models;
using API.Data;
using Microsoft.EntityFrameworkCore;
namespace api.Data
{
public class AuthRepository : IAuthRepository
{
public AuthRepository(DataContext context)
{
Context = context;
}
public DataContext Context { get; }
public async Task<User> Login(string username, string password)
{
var user =await Context.Users.FirstOrDefaultAsync(x=> x.Username== username);
if(user==null)
{
return null;
}
if(!VerifyPasswordHash(password,user.PasswordHash,user.PasswordSalt))
return null;
return user;
}
private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
{
using(var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
{
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
for(int i=0; i< computedHash.Length; i++)
{
if(computedHash[i]!= passwordHash[i]) return false;
}
return true;
}
}
private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
{
using(var hmac = new System.Security.Cryptography.HMACSHA512())
{
passwordSalt = hmac.Key;
passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
}
}
public async Task<User> Register(User user, string Password)
{
byte[] passwordHash, passwordSalt;
CreatePasswordHash(Password,out passwordHash, out passwordSalt);
user.PasswordHash=passwordHash;
user.PasswordSalt=passwordSalt;
await Context.Users.AddAsync(user);
await Context.SaveChangesAsync();
return user;
}
public async Task<bool> UserExists(string username)
{
if(await Context.Users.AnyAsync(x=>x.Username== username))
{
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,16 @@
using System.Threading.Tasks;
using api.Models;
namespace api.Data
{
public interface IAuthRepository
{
Task<User> Register(User user, string Password);
Task<User> Login(string username, string password);
Task<bool> UserExists(string username);
}
}

View File

@ -0,0 +1,9 @@
namespace api.Dtos
{
public class UserForLoginDto
{
public string Username{get;set;}
public string Password{get;set;}
}
}

View File

@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace api.Dtos
{
public class UserForRegisterDto
{
[Required]
public string Username {get;set;}
[Required]
[StringLength(8,MinimumLength=4,ErrorMessage="You must specify password between 4 and 8 cars")]
public string Password{get;set;}
}
}

View File

@ -0,0 +1,51 @@
// <auto-generated />
using System;
using API.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace API.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20180817200459_AddedUserEntity")]
partial class AddedUserEntity
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("api.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("PasswordHash");
b.Property<byte[]>("PasswordSalt");
b.Property<string>("Username");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("api.Models.Value", b =>
{
b.Property<int>("id")
.ValueGeneratedOnAdd();
b.Property<string>("Name");
b.HasKey("id");
b.ToTable("Values");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace API.Migrations
{
public partial class AddedUserEntity : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Username = table.Column<string>(nullable: true),
PasswordHash = table.Column<byte[]>(nullable: true),
PasswordSalt = table.Column<byte[]>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
}
}
}

12
api/Models/User.cs Normal file
View File

@ -0,0 +1,12 @@
namespace api.Models
{
public class User
{
public int Id{get;set;}
public string Username {get;set;}
public byte[] PasswordHash{get;set;}
public byte[] PasswordSalt{get;set;}
}
}

View File

@ -3,18 +3,8 @@
<h1>
Welcome to {{title}}!
</h1>
<img width="300" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyNTAgMjUwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyNTAgMjUwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDojREQwMDMxO30NCgkuc3Qxe2ZpbGw6I0MzMDAyRjt9DQoJLnN0MntmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxnPg0KCTxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iMTI1LDMwIDEyNSwzMCAxMjUsMzAgMzEuOSw2My4yIDQ2LjEsMTg2LjMgMTI1LDIzMCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAJIi8+DQoJPHBvbHlnb24gY2xhc3M9InN0MSIgcG9pbnRzPSIxMjUsMzAgMTI1LDUyLjIgMTI1LDUyLjEgMTI1LDE1My40IDEyNSwxNTMuNCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAxMjUsMzAgCSIvPg0KCTxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMjUsNTIuMUw2Ni44LDE4Mi42aDBoMjEuN2gwbDExLjctMjkuMmg0OS40bDExLjcsMjkuMmgwaDIxLjdoMEwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMQ0KCQlMMTI1LDUyLjF6IE0xNDIsMTM1LjRIMTA4bDE3LTQwLjlMMTQyLDEzNS40eiIvPg0KPC9nPg0KPC9zdmc+DQo=">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
</li>
<li>
<h2><a target="_blank" href="https://blog.angular.io//">Angular blog</a></h2>
</li>
</ul>
<app-value></app-value>
</div>

View File

@ -1,3 +1,4 @@
<p>
value works!
<p *ngFor="let value of values" >
{{value.id}}/{{value.name}}
</p>

View File

@ -16,6 +16,10 @@ export class ValueComponent implements OnInit {
}
getValues() {
this.values = this.http.get('localhost:5000/api/values');
this.http.get('http://localhost:5000/api/values').subscribe(response => {
this.values = response;
}, error => {
console.log(error);
});
}
}