I would like to use API key to access secured ServiceStack web service simply as possible:
I do not want to be able to register an user
I do not need user permissions or roles
Custom API key permissions would be a plus:
Be able to limit some service to a specific API key.
API keys will be managed directly from the database
What are the classes or methods I need to override? There are many extension points but I do not know what to keep and what to rewrite:
OrmLiteAuthRepository (base?)
ApiKeyAuthProvider
AuthUserSession
I am able to call a service with Bearer token (API key). It returns 200 Forbidden.
ApiKeyAuthProvider.AuthenticateAsync():
// authRepo is ServiceStack.Auth.OrmLiteAuthRepositoryMultitenancy
var userAuth = await authRepo.GetUserAuthAsync(apiKey.UserAuthId, token).ConfigAwait();
userAuth is NULL and this will throw this exception:
throw HttpError.Unauthorized(ErrorMessages.UserForApiKeyDoesNotExist.Localize(authService.Request));
I store my API keys at the 'ApiKey' table in SQL database:
public override void Configure(Container container)
{
string connectionString = GetConnectionStringByName("Main");
// Create and register an OrmLite DB Factory configured to use Live DB by default
var dbFactory = new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider);
container.Register(dbFactory);
// Tell ServiceStack you want to persist User Auth Info in SQL Server
container.Register<IAuthRepository>(c => new OrmLiteAuthRepository(dbFactory) { UseDistinctRoleTables = true });
// It’s safe to always call this in your AppHost as it’s just ignored if you already have the tables created
container.Resolve<IAuthRepository>().InitSchema();
Plugins.Add(new AuthFeature(
() => new AuthUserSession(),
new IAuthProvider[]
{
new ApiKeyAuthProvider(AppSettings) {RequireSecureConnection = false}
}));
}
The API Key AuthProvider may not suit your use-case as it's designed to generate API Keys for registered users to provide an alternative way for them to call protected APIs.
To be able to model this using ServiceStack's built-in Auth API Key Auth Providers I would still have a registered AuthProvider and users representing the client that would use the API Keys.
But instead of providing User registration functionality, add them into the database manually then Generating API Keys for Existing Users.
You'll need to configure your preferred RDBMS to store the API Keys and Users in:
[assembly: HostingStartup(typeof(MyApp.ConfigureDb))]
public class ConfigureDb : IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureServices((context, services) =>
services.AddSingleton<IDbConnectionFactory>(
new OrmLiteConnectionFactory(
context.Configuration.GetConnectionString("DefaultConnection"),
SqliteDialect.Provider)));
}
Configure ServiceStack's Auth Feature configured with the API Key AuthProvider:
[assembly: HostingStartup(typeof(MyApp.ConfigureAuth))]
public class ConfigureAuth : IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureAppHost(appHost =>
{
appHost.Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new ApiKeyAuthProvider(appHost.AppSettings) {
RequireSecureConnection = false,
SessionCacheDuration = TimeSpan.FromMinutes(10),
}
}));
});
}
Then configure an RDBMS OrmLiteAuthRepository pre-populated with the clients you want to allow access to, then generate any missing API Keys for them on startup:
[assembly: HostingStartup(typeof(MyApp.ConfigureAuthRepository))]
public class ConfigureAuthRepository : IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureServices(services => services.AddSingleton<IAuthRepository>(c =>
new OrmLiteAuthRepository(c.Resolve<IDbConnectionFactory>())))
.ConfigureAppHost(appHost => {
var authRepo = appHost.Resolve<IAuthRepository>();
authRepo.InitSchema();
CreateUser(authRepo, "admin#email.com", "Admin User", "p#55wOrd",
roles: new[] { RoleNames.Admin });
CreateUser(authRepo, "admin.client#email.com", "Client Admin", "p#55wOrd",
roles: new[] { "ClientAdmin", "Client" });
CreateUser(authRepo, "client#email.com", "Client User", "p#55wOrd",
roles: new[] { "Client" });
},
afterAppHostInit: appHost => {
var authProvider = (ApiKeyAuthProvider)
AuthenticateService.GetAuthProvider(ApiKeyAuthProvider.Name);
using var db = appHost.TryResolve<IDbConnectionFactory>().Open();
var userWithKeysIds = db.Column<string>(db.From<ApiKey>()
.SelectDistinct(x => x.UserAuthId)).Map(int.Parse);
// Use custom UserAuth if configured
var userIdsMissingKeys = db.Column<string>(db.From<UserAuth>()
.Where(x => userWithKeysIds.Count == 0 || !userWithKeysIds.Contains(x.Id))
.Select(x => x.Id));
var authRepo = (IManageApiKeys)appHost.TryResolve<IAuthRepository>();
foreach (var userId in userIdsMissingKeys)
{
var apiKeys = authProvider.GenerateNewApiKeys(userId);
authRepo.StoreAll(apiKeys);
}
});
// Add initial Users to the configured Auth Repository
public void CreateUser(IAuthRepository authRepo, string email, string name, string password, string[] roles)
{
if (authRepo.GetUserAuthByUserName(email) == null)
{
var newAdmin = new AppUser { Email = email, DisplayName = name };
var user = authRepo.CreateUserAuth(newAdmin, password);
authRepo.AssignRoles(user, roles);
}
}
}
This will let you protect access to different APIs using role-based auth:
[ValidateIsAdmin]
public class AdminOnly { ... }
[ValidateHasRole("ClientAdmin")]
public class ClientAdminOnly { ... }
[ValidateHasRole("Client")]
public class AnyClient { ... }
Note: The Admin is a super user role that can access any protected API
If you don't want all these Auth components for your App you'll have to create your own Custom Auth Provider that implements its own Authentication which doesn't need to use any other components as it has full control over how the request is authenticated.
You can refer to the existing ApiKeyAuthProvider.cs for a guide on how to implement an API Key IAuthWithRequest Auth Provider that validates the BearerToken in its PreAuthenticateAsync() method.
Related
What I am trying to achieve:
1. Getting all the claims after OnTicketReceived event from Auth0.
2. Checking if the user is signing up for first time (bool value in 1 of the claims).
3. Saving new user object into localdb (if it's the first time logging in).
Currently I am stuck on third step.
In order to use OnTicketReceived I need a static method on the right side but then I cannot use DbContext in my controller class because it is not a static variable.
Startup.cs
services.AddAuthentication(options =>
...
.AddOpenIdConnect("Auth0", options =>
{
options.Scope.Add("openid");
...
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = UsersController.CreateOnSignUp,
...
};
});
UsersController.cs
public static async Task<Task> CreateOnSignUp(TicketReceivedContext ticketReceivedContext)
{
List<Claim> claims = ticketReceivedContext.Principal.Claims.ToList();
var claim = claims.FirstOrDefault(x => x.Type.EndsWith("isNewUser"));
bool isNewUser = bool.Parse(claim.Value);
if (isNewUser)
{
string userOId = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
User user = new User() { ExternalId = userOId };
//insert new value into DB
//_context.Add(user);
//await _context.SaveChangesAsync();
}
return Task.CompletedTask;
}
Commented code lines are currently not working.
Could anyone recommend a better way of saving user data to DB after authentication or a workaround to my current solution?
First of all, the controllers are instantiated per call (or for each HTTP request). You shouldn't maintain any state by introducing static properties, fields or methods inside the controllers.
In your case, I would create a service class ServiceSignUp that consists of the CreateOnSignUp method.
public interface IServiceSignUp
{
async Task CreateOnSignUp(TicketReceivedContext ticketReceivedContext);
}
public class ServiceSignUp : IServiceSignUp
{
public async Task CreateOnSignUp(TicketReceivedContext ticketReceivedContext)
{
....
}
}
Then register them with the build-in IoC container like services.AddSingleton<IServiceSignUp, ServiceSignUp>();
In the ConfigureServices() method inside the StartUp class:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServiceSignUp, ServiceSignUp>();
services.AddAuthentication(options =>
...
.AddOpenIdConnect("Auth0", options =>
{
options.Scope.Add("openid");
...
options.Events = new OpenIdConnectEvents
{
OnTicketReceived += ServiceSignUp.CreateOnSignUp,
...
};
});
}
I have my own authorization server built on top identityserver4 where I want to secure all apis on a host. System is just a simple mimic of google developers or facebook developers where application owners sign up and get client id and client secrets for access grant on apis.
So I followed client_credentials flow on identityserver4 samples. All working fine. I built a public UI for app owners to create apps and choose which apis to access from their apps. I make use of IConfigurationDbContext for CRUD procecces on internal tables of identityserver.
The problem is I couldn't find a way to secure apis based on app owners' choices, when a developer crate an app and choose a few logical endpoints to access, they still can reach all enpoints. What I have done is as follows;
Authorization Server Startup
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryCaching()
.AddOperationalStore(storeOpitons =>
{
storeOpitons.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("Default"),
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddConfigurationStore(storeOptions =>
{
storeOptions.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("Default"),
sql => sql.MigrationsAssembly(migrationsAssembly));
});
Saving Client Method
public IActionResult SaveApp(ClientViewModel model, List<SelectedApi> selectedApis)
{
//ommited for brevity
Client client = new Client
{
Description = model.Description,
ClientName = model.Name,
RedirectUris = new[] { model.CallBackUri }
};
client.AllowedScopes = selectedApis.Where(a => a.apiValue == "true").Select(a => a.apiName).ToList();
//e.g : client.AllowedScopes = {"employee_api"};
_isRepository.SaveClient(client, userApp);
}
Api Project Startup
services.AddAuthentication("Bearer").AddJwtBearer(opt => {
opt.Authority = "http://localhost:5000";
opt.Audience = "employee_api";
opt.RequireHttpsMetadata = false;
});
Api Sample Controller
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : BaseController
{
private readonly IEmployeeRepository _employeeRepoistory;
public EmployeeController(IServiceProvider provider, IEmployeeRepository employeeRepository) : base(provider)
{
_employeeRepoistory = employeeRepository;
}
[HttpGet]
public IActionResult GetEmployees([FromQuery] EmployeeResourceParameter parameter)
{
return Ok(_mapper.Map<IEnumerable<EmployeeModel>>(_employeeRepoistory.GetAll(parameter)));
}
[HttpGet("{id:int}")]
public IActionResult GetEmployeeById(int id)
{
var emp = _employeeRepoistory.GetById(id);
return Ok(_mapper.Map<EmployeeModel>(emp));
}
}
What I want is if a developer choose employee_api, they should just reach to EmployeeController's endpoints. However right now, they can reach all the apis no matter of what their choices are.
What are the steps to take for this on api side or auth server side?
Finally I get it done.. First of all, I realized that it is important to grasp the relation between ApiResource -> Scopes, Clients -> AllowedScopes. I suggest you to read the parts about them in the docs and here
When a client is registered to identityserver and then choose the api endpoints (eg: organization, employee, calender) they should be registered as allowedScopes of client (they live in ClientScopes table), I was doing it in right way. What I was doing wrong is I suppose all these scopes are ApiResources (for my case, because all my apis are living in the same host which I call it as CommonServiceApi, just one web api app). I redefined my ApiResources and its Scopes, as follows;
new ApiResource("commonserviceapi", "Common Service API")
{
Scopes = {
new Scope("calender_api", "Calender Api"),
new Scope("employee_api", "Employee Api"),
new Scope("organization_api", "Organization Api"),
}
}
On the api side, endpoints should be authorized with policies as indicated here.
Within the access token, allowed scopes of the requesting client are passed to the api app, so api grants accesses according to these values.
So Api Startup
services.AddAuthentication("Bearer").AddJwtBearer(opt =>
{
opt.Authority = "http://localhost:5000";
opt.Audience = "commonserviceapi";
opt.RequireHttpsMetadata = false;
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiEmployee", builder =>
{
builder.RequireScope("employee_api");
});
options.AddPolicy("ApiOrganization", builder =>
{
builder.RequireScope("organization_api");
});
});
And Api Controllers
[Authorize(Policy = "ApiEmployee")]
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : BaseController
{
...
RequireScope is an extension method of IdentityServer4.AccessTokenValidation package by the way. You should include this package to your api project.
And lastly, this was a confusing point for me; while requesting an access token from server, scope parameter should be empty, as identityserver takes it from client's allowdScopes values. Almost all samples were filling this field, so you'd think it should be filled.
I'm using Identity Server 3 to authenticate and generate Access/Refresh tokens for my angular Client.
I'm currently setting the Refresh Token to expire in 48 hours for my Angular Client.
Some users who use my Angular application will need to be Signed On for 100 days straight without having to re-enter their credentials, is it possible to set the expiration of my Refresh Token for a specific user only instead of the entire client?
I have 100 users in my database, I want just one specific user to not need to re-authenticate in 100 days while the rest should authenticate every 48 hours.
Something along the lines of:
if (user == "Super Man") {
AbsoluteRefreshTokenLifetime = TimeSpan.FromDays(100.0).Seconds,
}
Is this possible to achieve? or am I restricted to only setting the Refresh Token Expiration for the Entire Client?
Thank You
I've never worked with IdentityServer3 and I didn't test the code below, but I think the concept may work.
When I take a look at the code of IdentityServer3 then I can see that in DefaultRefreshTokenService.CreateRefreshTokenAsync the lifetime is set:
int lifetime;
if (client.RefreshTokenExpiration == TokenExpiration.Absolute)
{
Logger.Debug("Setting an absolute lifetime: " + client.AbsoluteRefreshTokenLifetime);
lifetime = client.AbsoluteRefreshTokenLifetime;
}
else
{
Logger.Debug("Setting a sliding lifetime: " + client.SlidingRefreshTokenLifetime);
lifetime = client.SlidingRefreshTokenLifetime;
}
You wouldn't want to change the core code, but you should be able to override the IRefreshTokenService with your own implementation.
When I take the code from CustomUserService sample as example:
internal class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/core", coreApp =>
{
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
var refreshTokenService = new MyDefaultRefreshTokenService();
// note: for the sample this registration is a singletone (not what you want in production probably)
factory.RefreshTokenService = new Registration<IrefreshTokenService>(resolver => refreshTokenService);
Where MyDefaultRefreshTokenService is a copy of the DefaultRefreshTokenService.
In order to make it compile add a NuGet package of IdentityModel (v1.13.1) and add the following class:
using System;
namespace IdentityServer3.Core.Extensions
{
internal static class DateTimeOffsetHelper
{
internal static Func<DateTimeOffset> UtcNowFunc = () => DateTimeOffset.UtcNow;
internal static DateTimeOffset UtcNow
{
get
{
return UtcNowFunc();
}
}
internal static int GetLifetimeInSeconds(this DateTimeOffset creationTime)
{
return (int)(UtcNow - creationTime).TotalSeconds;
}
}
}
Now there are some compilation errors concerning the events. You can remove the events in order to test the code. If it works you can always choose to add them.
And now for the implementation of the RefreshTokenLifetime per user. In your version of the RefreshTokenService you can remove the client code and use your own logic to determine the lifetime per user.
The subject is available, though I don't know if it already contains enough information. But if it does then you can access the userManager to read the lifetime from the store. Or use an alternative to pass the lifetime information (perhaps you can use a claim containing the lifetime value).
Again, I didn't test this, but I think the concept should work.
Considerations
Consider sliding sessions for example. With sliding sessions, you would send a new short-lived token with every authenticated action made by the user. As long as the user is active he will stay authenticated (e.g. it requires user interaction before expiration interval, although it requires token management implementations). If the user sends an expired token, it means he has been inactive for a while.
Let's see how JWT works:
The JWT is mainly suitable for the following cases:
In case of building API services that need to support
server-to-server or client-to-server (like a mobile app or single page app (SPA)) communication, using JWTs as your API tokens is a
very smart idea (clients will be making requests frequently, with
limited scope, and usually authentication data can be persisted in a
stateless way without too much dependence on user data).
If you’re building any type of service where you need three or more
parties involved in a request, JWTs can also be useful.
if you’re using user federation (things like single sign-on and
OpenID Connect), JWTs become important because you need a way to
validate a user’s identity via a third party.
more clarification at stop using jwts as session tokens
So Stop using JWT for sessions, it’s a bad idea to use JWTs as session tokens for most of cases.
Possible Solution
For Refreshing JWT, the JWT refresh tokens and .NET Core may be useful to implement your own code And descriptions inside JWT (JSON Web Token) automatic prolongation of expiration guides you to design a working scenario. You need to inspect desired user before refreshing operation.
I found another implementation at Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token for you, maybe useful.
I'm not familiar with Microsoft's Identity Server (the "Identity Service" I refer to in the code below is a custom implementation), but you could consider writing an authentication handler to intercept the token in HTTP headers, examine a token prefix, then decide whether to process normally or allow an extended lifetime.
In my case, I intercept the token prior to JWT processing it. (I had to do this to get around a SharePoint workflow limitation. Oh, SharePoint.) Here's the AuthenticationHandler class:
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace CompanyName.Core2.Application.Middleware
{
[UsedImplicitly]
public class AuthenticationHandler : AuthenticationHandler<AuthenticationOptions>
{
public const string AuthenticationScheme = "CompanyName Token";
[UsedImplicitly] public const string HttpHeaderName = "Authorization";
[UsedImplicitly] public const string TokenPrefix = "CompanyName ";
public AuthenticationHandler(IOptionsMonitor<AuthenticationOptions> Options, ILoggerFactory Logger, UrlEncoder Encoder, ISystemClock Clock)
: base(Options, Logger, Encoder, Clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue(HttpHeaderName, out StringValues authorizationValues))
{
// Indicate failure.
return await Task.FromResult(AuthenticateResult.Fail($"{HttpHeaderName} header not found."));
}
string token = authorizationValues.ToString();
foreach (AuthenticationIdentity authenticationIdentity in Options.Identities)
{
if (token == $"{TokenPrefix}{authenticationIdentity.Token}")
{
// Authorization token is valid.
// Create claims identity, add roles, and add claims.
ClaimsIdentity claimsIdentity = new ClaimsIdentity(AuthenticationScheme);
claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, authenticationIdentity.Username));
foreach (string role in authenticationIdentity.Roles)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
}
foreach (string claimType in authenticationIdentity.Claims.Keys)
{
string claimValue = authenticationIdentity.Claims[claimType];
claimsIdentity.AddClaim(new Claim(claimType, claimValue));
}
// Create authentication ticket and indicate success.
AuthenticationTicket authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), Scheme.Name);
return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
}
// Indicate failure.
return await Task.FromResult(AuthenticateResult.Fail($"Invalid {HttpHeaderName} header."));
}
}
}
Then in the Startup class of your service, add code to decide which authentication handler to use. The key feature here is the ForwardDefaultSelector:
public void ConfigureServices(IServiceCollection Services)
{
// Require authentication token.
// Enable CompanyName token for SharePoint workflow client, which cannot pass HTTP headers > 255 characters (JWT tokens are > 255 characters).
// Enable JWT token for all other clients. The JWT token specifies the security algorithm used when it was signed (by Identity service).
Services.AddAuthentication(AuthenticationHandler.AuthenticationScheme).AddCompanyNameAuthentication(Options =>
{
Options.Identities = Program.AppSettings.AuthenticationIdentities;
Options.ForwardDefaultSelector = HttpContext =>
{
// Forward to JWT authentication if CompanyName token is not present.
string token = string.Empty;
if (HttpContext.Request.Headers.TryGetValue(AuthenticationHandler.HttpHeaderName, out StringValues authorizationValues))
{
token = authorizationValues.ToString();
}
return token.StartsWith(AuthenticationHandler.TokenPrefix)
? AuthenticationHandler.AuthenticationScheme
: JwtBearerDefaults.AuthenticationScheme;
};
})
.AddJwtBearer(Options =>
{
Options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Program.AppSettings.ServiceOptions.TokenSecret)),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(_clockSkewMinutes)
};
});
Add an extension method to the AuthenticationBuilder class:
public static AuthenticationBuilder AddCompanyNameAuthentication(this AuthenticationBuilder AuthenticationBuilder, Action<AuthenticationOptions> ConfigureOptions = null)
{
return AuthenticationBuilder.AddScheme<AuthenticationOptions, AuthenticationHandler>(AuthenticationHandler.AuthenticationScheme, ConfigureOptions);
}
And authentication options if you need them.
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
namespace CompanyName.Core2.Application.Middleware
{
public class AuthenticationOptions : AuthenticationSchemeOptions
{
[UsedImplicitly]
public AuthenticationIdentities Identities { get; [UsedImplicitly] set; }
public AuthenticationOptions()
{
Identities = new AuthenticationIdentities();
}
}
}
AuthenticationIdentities is just a class I define to associate a token with a username, roles, and claims (the token for the SharePoint workflow engine). It's populated from appsettings.json. Your options class most likely would contain a list of users who are authorized for an extended lifetime.
using System.Collections.Generic;
using JetBrains.Annotations;
namespace CompanyName.Core2.Application.Middleware
{
public class AuthenticationIdentity
{
public string Token { get; [UsedImplicitly] set; }
public string Username { get; [UsedImplicitly] set; }
[UsedImplicitly] public List<string> Roles { get; [UsedImplicitly] set; }
[UsedImplicitly] public Dictionary<string, string> Claims { get; [UsedImplicitly] set; }
public AuthenticationIdentity()
{
Roles = new List<string>();
Claims = new Dictionary<string, string>();
}
}
}
I need to create a Web API C# application for an existing MySQL database. I've managed to use Entity Framework 6 to bind every database table to a RESTful API (that allows CRUD operations).
I want to implement a login/registration system (so that I can implement roles and permissions in the future, and restrict certain API requests).
The MySQL database I have to use has a table for users (called user) that has the following self-explanatory columns:
id
email
username
password_hash
It seems that the de-facto standard for authentication is ASP.Net Identity. I have spent the last hour trying to figure out how to make Identity work with an existing DB-First Entity Framework setup.
If I try to construct ApplicationUser instances storing user instances (entities from the MySQL database) to retrieve user data, I get the following error:
The entity type ApplicationUser is not part of the model for the current context.
I assume I need to store Identity data in my MySQL database, but couldn't find any resource on how to do that. I've tried completely removing the ApplicationUser class and making my user entity class derive from IdentityUser, but calling UserManager.CreateAsync resulted in LINQ to Entities conversion errors.
How do I setup authentication in a Web API 2 application, having an existing user entity?
You say:
I want to implement a login/registration system (so that I can
implement roles and permissions in the future, and restrict certain
API requests).
How do I setup authentication in a Web API 2 application, having an
existing user entity?
It definitely means that you DO NOT need ASP.NET Identity. ASP.NET Identity is a technology to handle all users stuffs. It actually does not "make" the authentication mechanism. ASP.NET Identity uses OWIN Authentication mechanism, which is another thing.
What you are looking for is not "how to use ASP.NET Identity with my existing Users table", but "How to configure OWIN Authentication using my existing Users table"
To use OWIN Auth follow these steps:
Install the packages:
Owin
Microsoft.AspNet.Cors
Microsoft.AspNet.WebApi.Client
Microsoft.AspNet.WebApi.Core
Microsoft.AspNet.WebApi.Owin
Microsoft.AspNet.WebApi.WebHost
Microsoft.Owin
Microsoft.Owin.Cors
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Security
Microsoft.Owin.Security.OAuth
Create Startup.cs file inside the root folder (example):
make sure that [assembly: OwinStartup] is correctly configured
[assembly: OwinStartup(typeof(YourProject.Startup))]
namespace YourProject
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
//other configurations
ConfigureOAuth(app);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
var oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/security/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(2),
Provider = new AuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
try
{
//retrieve your user from database. ex:
var user = await userService.Authenticate(context.UserName, context.Password);
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
//roles example
var rolesTechnicalNamesUser = new List<string>();
if (user.Roles != null)
{
rolesTechnicalNamesUser = user.Roles.Select(x => x.TechnicalName).ToList();
foreach (var role in user.Roles)
identity.AddClaim(new Claim(ClaimTypes.Role, role.TechnicalName));
}
var principal = new GenericPrincipal(identity, rolesTechnicalNamesUser.ToArray());
Thread.CurrentPrincipal = principal;
context.Validated(identity);
}
catch (Exception ex)
{
context.SetError("invalid_grant", "message");
}
}
}
}
Use the [Authorize] attribute to authorize the actions.
Call api/security/token with GrantType, UserName, and Password to get the bearer token. Like this:
"grant_type=password&username=" + username + "&password=" password;
Send the token within the HttpHeader Authorization as Bearer "YOURTOKENHERE". Like this:
headers: { 'Authorization': 'Bearer ' + token }
Hope it helps!
Since your DB schema are not compatible with default UserStore You must implement your own UserStore and UserPasswordStore classes then inject them to UserManager. Consider this simple example:
First write your custom user class and implement IUser interface:
class User:IUser<int>
{
public int ID {get;set;}
public string Username{get;set;}
public string Password_hash {get;set;}
// some other properties
}
Now author your custom UserStore and IUserPasswordStore class like this:
public class MyUserStore : IUserStore<User>, IUserPasswordStore<User>
{
private readonly MyDbContext _context;
public MyUserStore(MyDbContext context)
{
_context=context;
}
public Task CreateAsync(AppUser user)
{
// implement your desired logic such as
// _context.Users.Add(user);
}
public Task DeleteAsync(AppUser user)
{
// implement your desired logic
}
public Task<AppUser> FindByIdAsync(string userId)
{
// implement your desired logic
}
public Task<AppUser> FindByNameAsync(string userName)
{
// implement your desired logic
}
public Task UpdateAsync(AppUser user)
{
// implement your desired logic
}
public void Dispose()
{
// implement your desired logic
}
// Following 3 methods are needed for IUserPasswordStore
public Task<string> GetPasswordHashAsync(AppUser user)
{
// something like this:
return Task.FromResult(user.Password_hash);
}
public Task<bool> HasPasswordAsync(AppUser user)
{
return Task.FromResult(user.Password_hash != null);
}
public Task SetPasswordHashAsync(AppUser user, string passwordHash)
{
user.Password_hash = passwordHash;
return Task.FromResult(0);
}
}
Now you have very own user store simply inject it to the user manager:
public class ApplicationUserManager: UserManager<User, int>
{
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new MyUserStore(context.Get<MyDbContext>()));
// rest of code
}
}
Also please note you must directly inherit your DB Context class from DbContext not IdentityDbContext since you have implemented own user store.
I want to allow two types of authentication on my site :
* Forms authentication: The user login using his/her details in the form. The authentication should be made using cookies.
* Bearer: When calling WebAPI's (for mobile), the authentication should be made only by using bearer tokens.
I've relayed on the SPA template and some questions in SO and did successful made it available.
The only problem I'm facing is the ClaimsIdentity: I wish to use custom identity class. However, I'm being able to do so only in forms authentication, not in bearer WebAPI requests.
My custom identity:
public class MyIdentity : ClaimsIdentity, IMyIdentity
{
#region IMyIdentity
private Account _account = null;
public Account Account
{
get
{
if (_account == null)
{
if (this.IsAuthenticated)
{
Guid claimedAccountId = Guid.Parse(this.FindFirst(ClaimTypes.NameIdentifier).Value);
var accountService = ServiceLocator.SharedInstance.GetInstance<IAccountService>();
_account = accountService.Where(
a => a.Id == claimedAccountId
).FirstOrDefault();
}
_account = _account ?? Membership.Account.GuestAccount;
}
return _account;
}
}
#endregion
}
In Global.asax, I've overridden the Application_OnPostAuthenticateRequest method in order to set the custom identity, and it does working good - but only in forms, not in WebAPI.
In addition, I do set in WebApiConfig.cs
config.SuppressDefaultHostAuthentication();
so it does make sense that MyIdentity being nulled and User.Identity resets back to ClaimsIdentity.
So to sum up my question - is there a way to define which Identity class will be used, so I can set MyIdentity instead of ClaimsIdentity?
For Web API, you could try hooking into the OWIN authentication pipeline, and implement your own Authentication Filter, and use it to change the current principal to your own:
public class MyAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter
{
public Task AuthenticateAsync(HttpAuthenticationContext context, System.Threading.CancellationToken cancellationToken)
{
if (context.Principal != null && context.Principal.Identity.IsAuthenticated)
{
CustomPrincipal myPrincipal = new CustomPrincipal();
// Do work to setup custom principal
context.Principal = myPrincipal;
}
return Task.FromResult(0);
}
And register the filter:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new MyAuthenticationFilter());
...