I am looking for proper way how to implement authentification and authorization.
If I understand it well - this should be realized through "Identity" - it's offering both of these things I need.
My problem is that i can't use a database. I have to use a service (WCF service where our internal DDLs are connected to our system) which is able only Login (I give it user name and password) and after login i can get list of permissons.
I already saw articles how to have custom UserStore, RoleStore, UserManager and SignInManager.. but I am still confused and I don't know how to do it.
Is this even posible through Identity model? If not how I should do it please?
Thank you for every advice.
There are some articles which I already checked:
Microsoft - custom storage providers
Core indentity without entity framework
Sikorsky blog - custom user manager
In fact the WCF service is your authentication service. No need to implement this twice. A call to the service verifies the credentials and returns everything that should be in the access token.
Basically all you have to do is generate an access token using the information from the WCF service. And configure your app to use the created access token.
The flow can be like this: first make a call to the WCF service in order to verify the login and retrieve the information.
public async Task<IActionResult> LoginAsync([FromBody]UserLogin login)
{
var loginInfo = _wcf.LoginUser(login);
if (loginInfo == null)
return Unauthorized();
return Ok(CreateAccessToken(loginInfo));
}
To create an access token:
public class TokenHelper
{
public const string Issuer = "http://www.mywebsite.com/myapp";
public const string Audience = "http://www.mywebsite.com/myapp";
// This should not be hardcoded!
public const string Secret = "My_super_secret";
public AccessToken CreateAccessToken(LoginInfo loginInfo)
{
// Set expiration time of 5 minutes.
DateTime expires = DateTime.UtcNow.AddMinutes(5);
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, loginInfo.UserId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
// Add custom claims, rolepermissions
if (loginInfo.Permissions != null && loginInfo.Permissions.Any())
loginInfo.Permissions.foreach(p => claims.Add(new Claim("Permission", p)));
if (loginInfo.IsUser)
claims.Add(new Claim(ClaimTypes.Role, "User"));
if (loginInfo.IsAdmin)
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
var token = new JwtSecurityToken(
issuer: Issuer,
audience: Audience,
claims: claims,
expires: expires,
signingCredentials: new SigningCredentials(
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Secret)),
SecurityAlgorithms.HmacSha256
)
);
return new AccessToken
{
ServerTime = DateTime.UtcNow.ToString("yyyyMMddTHH:mm:ssZ"),
Expires = expires.ToString("yyyyMMddTHH:mm:ssZ"),
Bearer = new JwtSecurityTokenHandler().WriteToken(token)
};
}
}
Where AccessToken is:
public class AccessToken
{
public string ServerTime { get; set; }
public string Expires { get; set; }
public string Bearer { get; set; }
}
And add authentication in your startup:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidIssuer = TokenHelper.Issuer,
ValidAudience = TokenHelper.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.ASCII.GetBytes(
TokenHelper.Secret))
};
}
);
-- update --
_issuer, _audience and _secret are from some 'external source'. Meaning that all three are fixed string values, but the source (where the value is set) is variable.
For _issuer you usually use the url of the server that issues the token. Like http://www.mywebsite.com/myapp
The _audience is the application that is meant to accept the token. In this case _issuer and _audience are the same, so you can use the same value.
_secret is, well secret and can be any string, like 'my_super_secret'
. This is something you want to stay secret. So you don't hardcode it, but get it from a safe location instead.
I've updated above code in a way so you can test it. Please note that secret should not be hardcoded.