I want to know wheather it is a valid solution to use JWT token and the Novel AD authentication an authorization library in an ASP.NET Core 5 Web API in a Linux server? Are there any examples on it, please?
Yes you can use Novell for Authentication. For authenticating your users you can use Ldap using Novell.Directory.Ldap Package.
In .csprog file:
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="2.3.8" />
In the configuration:
"Ldap": {
"url": "[Ldap URL]",
"domain": "[Domain Name]"
}
Code:
using Novell.Directory.Ldap;
public bool LoginLdap(string username, string password)
{
LdapConnection connection = new LdapConnection();
var loggedIn = false;
try
{
connection.Connect(_config["Ldap:url"], LdapConnection.DEFAULT_PORT);
connection.Bind(LdapConnection.Ldap_V3, _config["Ldap:domain"] + #"\" + username, password);
loggedIn = true;
}
catch
{
loggedIn = false;
}
connection.Disconnect();
return loggedIn;
}
Related
I have an ASP.NET Core project that has a Web API for mobile device (Xamarin).
I want to secure the api with ASP.NET Core identity, but the problem is when I authenticate a device and authenticated successfully, in another request it not still authenticated:
[HttpPost]
public async Task<IActionResult> Post([FromBody] LogIn l)
{
var user = await userManager.FindByEmailAsync(l.username);
if(user == null)
{
user = await userManager.FindByNameAsync(l.username);
}
if(user != null)
{
await signInManager.SignOutAsync();
Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.PasswordSignInAsync(user, l.password, false, false);
if (result.Succeeded)
{
await signInManager.RememberTwoFactorClientAsync(user);
return Ok("Success");
}
}
return Ok(HttpStatusCode.BadRequest);
}
The code that needs to authorize to return data :
[HttpGet("{id}")]
[Authorize]
public async Task<IActionResult> Get(int id)
{
var b = _context.Books.FirstOrDefault(o => o.BookId == id);
return Ok(b);
}
I read about token and jwt but I don't know how to use them. Any Idea how to secure the API and make the device authenticated once they log in?
I know it's late, but I think the idea is to login the user, and return a token that's then saved to the client's(Xamarin Android/iOS for your case) local storage/Sharedpreferences. The saved token can then be used for subsequent Web API calls for authentication without the need to login. It can then be cleared when a user logs out. For JWT, you can restructure your login function as follows:
var token = await GetJwtSecurityToken(user);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
The GetJwtSecurityToken() can look like this depending on your needs:
private async Task<JwtSecurityToken> GetJwtSecurityToken(ApplicationUser user)
{
var userClaims = await _userManager.GetClaimsAsync(user);
return new JwtSecurityToken(
//issuer: "http://localhost:****/",
//audience: "http://localhost:****/",
audience: "http://localhost:****/",
claims: GetTokenClaims(user).Union(userClaims),//Combine user & claims
//expires: DateTime.UtcNow.AddMinutes(10),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes("x%u<-Q.#w^:qF]2Hz4")), SecurityAlgorithms.HmacSha256)
);
}
The GetTokenClaims() function can look like:
private static IEnumerable<Claim> GetTokenClaims(ApplicationUser user)
{
return new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("UserName", user.UserName),
new Claim("Email", user.Email),
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim("FirstName", user.FirstName)
//Other user info
};
}
You can then save this token in local storage/Sharedpreferences, and use it to authenticate your API calls. You can research on: How to decode JWT token in Xamarin, OpenId..
Let me know how it goes.
How do you create custom provider for openiddict in Asp.net core to allow multiple refresh tokens? This way if the user logs in from their computer and then goes home and logs in on their phone, they don't have to login each time they get on to a different device. The app.UseOAuthValidation() runs in the background before the authorize controller ever gets called so there is no handle to verify if more than 1 refresh token matches. Another issue is that I am using this:
services.AddDbContext<ApplicationDbContext>(options => {
options.UseMySql(Configuration.GetConnectionString("DefaultConnection"))
.UseOpenIddict();
});
So I do not have access to the openiddict tables via DbContext to do this manually.
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using DPInventoryPOAPI.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using OpenIddict.Core;
using OpenIddict.Models;
using System.Threading;
using System.Linq;
namespace DPInventoryPOAPI
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials() );
});
services.AddMvc();
services.AddDbContext<ApplicationDbContext>(options => {
options.UseMySql(Configuration.GetConnectionString("DefaultConnection"))
.UseOpenIddict();
});
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddOpenIddict()
.AddEntityFrameworkCoreStores<ApplicationDbContext>()
.AddMvcBinders()
.EnableTokenEndpoint("/token")
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.DisableHttpsRequirement()
.AddEphemeralSigningKey();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
//app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseCors("CorsPolicy");
app.UseIdentity();
app.UseOpenIddict();
app.UseOAuthValidation();
app.UseMvcWithDefaultRoute();
//SeedDatabase(app);
}
}
}
And authorize controller
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using AuthorizationServer.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Core;
using OpenIddict.Models;
// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
namespace AuthorizationServer.Controllers {
public class AuthorizationController : Controller {
private readonly OpenIddictApplicationManager<OpenIddictApplication> _applicationManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
public AuthorizationController(
OpenIddictApplicationManager<OpenIddictApplication> applicationManager,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager) {
_applicationManager = applicationManager;
_signInManager = signInManager;
_userManager = userManager;
}
[HttpPost("~/connect/token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest request) {
Debug.Assert(request.IsTokenRequest(),
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
if (request.IsPasswordGrantType()) {
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
// Ensure the user is allowed to sign in.
if (!await _signInManager.CanSignInAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The specified user is not allowed to sign in."
});
}
// Reject the token request if two-factor authentication has been enabled by the user.
if (_userManager.SupportsUserTwoFactor && await _userManager.GetTwoFactorEnabledAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The specified user is not allowed to sign in."
});
}
// Ensure the user is not already locked out.
if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
// Ensure the password is valid.
if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
if (_userManager.SupportsUserLockout) {
await _userManager.AccessFailedAsync(user);
}
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
if (_userManager.SupportsUserLockout) {
await _userManager.ResetAccessFailedCountAsync(user);
}
// Create a new authentication ticket.
var ticket = await CreateTicketAsync(request, user);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
else if (request.IsRefreshTokenGrantType()) {
// Retrieve the claims principal stored in the refresh token.
var info = await HttpContext.Authentication.GetAuthenticateInfoAsync(
OpenIdConnectServerDefaults.AuthenticationScheme);
// Retrieve the user profile corresponding to the refresh token.
var user = await _userManager.GetUserAsync(info.Principal);
if (user == null) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The refresh token is no longer valid."
});
}
// Ensure the user is still allowed to sign in.
if (!await _signInManager.CanSignInAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The user is no longer allowed to sign in."
});
}
// Create a new authentication ticket, but reuse the properties stored
// in the refresh token, including the scopes originally granted.
var ticket = await CreateTicketAsync(request, user, info.Properties);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
private async Task<AuthenticationTicket> CreateTicketAsync(
OpenIdConnectRequest request, ApplicationUser user,
AuthenticationProperties properties = null) {
// Create a new ClaimsPrincipal containing the claims that
// will be used to create an id_token, a token or a code.
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
foreach (var claim in principal.Claims) {
// In this sample, every claim is serialized in both the access and the identity tokens.
// In a real world application, you'd probably want to exclude confidential claims
// or apply a claims policy based on the scopes requested by the client application.
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(principal, properties,
OpenIdConnectServerDefaults.AuthenticationScheme);
if (!request.IsRefreshTokenGrantType()) {
// Set the list of scopes granted to the client application.
// Note: the offline_access scope must be granted
// to allow OpenIddict to return a refresh token.
ticket.SetScopes(new[] {
OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Roles
}.Intersect(request.GetScopes()));
}
return ticket;
}
}
}
How do you create custom provider for openiddict in Asp.net core to allow multiple refresh tokens? This way if the user logs in from their computer and then goes home and logs in on their phone, they don't have to login each time they get on to a different device.
OTB, OpenIddict allows you to retrieve multiple (independent) refresh tokens as long as they are requested using different grant_type=password requests. In your case, if the token retrieved by the mobile app is revoked (e.g manually or because it was already used), the refresh token used by the desktop app can still be used to retrieve new access/refresh tokens.
The app.UseOAuthValidation() runs in the background before the authorize controller ever gets called so there is no handle to verify if more than 1 refresh token matches.
The validation middleware never deals with refresh tokens, as it's only responsible of validating access tokens.
So I do not have access to the openiddict tables via DbContext to do this manually.
You can add a DbSet<OpenIddictToken> property in your DbContext or retrieve the DbSet<OpenIddictToken> via context.Set<OpenIddictToken>().
I have a asp.net solution which consists of
1). asp.net identity server rc 3
2). asp.net Core web api
3). asp.net webform ( not in asp.net core, client)
I don't see any sample with identity server 4 and web form client. Can you please suggest how to authenticate web form user using identity server with asp.net identity and then call api with the access token ?
I don't see identity server 4 sample with web form client or sample
identity server 3 has a sample but it is doing everything in startup
When i see mvc client for identity server 4, it has all settings in configure method and then calls it like this
How will i apply Authorize attribute in webform so that i am redirected to identity server 4 for login and then after login when i call api like this:
how to change client for webform ?
new Client()
{
ClientId = "mvcClient",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets = new List<Secret>()
{
new Secret("secret".Sha256())
},
RequireConsent = false;
// where to redirect to after login
RedirectUris = { "http://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5002" },
AllowedScopes =
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
StandardScopes.Roles.Name,
"API"
}
}
new InMemoryUser()
{
Subject = "1",
Username = "testuser",
Password = "password",
Claims = new List<Claim>()
{
new Claim("name", "Alice"),
new Claim("Website", "http://alice.com"),
new Claim(JwtClaimTypes.Role, "admin")
}
}
return new List<Scope>()
{
StandardScopes.OpenId, // subject id
StandardScopes.Profile, // first name, last name
StandardScopes.OfflineAccess,
StandardScopes.Roles,
new Scope()
{
Name = "API",
Description = "API desc",
Type = ScopeType.Resource,
Emphasize = true,
IncludeAllClaimsForUser = true,
Claims = new List<ScopeClaim>
{
new ScopeClaim(ClaimTypes.Name),
new ScopeClaim(ClaimTypes.Role)
}
}
};
public void CallApiUsingClientCredentials()
{
var tokenClient = new TokenClient("http://localhost:5000/connect/token", "mvc", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var content = await client.GetStringAsync("http://localhost:5001/identity");
var result = JArray.Parse(content).ToString();
}
[Authorize(Roles="admin)]
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
Late answer, but hopefully it helps someone, still supporting web forms.
There is no problem to use startup together with web forms. The only limitation is no place for AuthorizeAttribute there, but it's still not a problem, just put:
app.UseStageMarker(PipelineStage.Authenticate);
at the bottom of your
public void Configuration(IAppBuilder app)
method within OWIN Startup.An example Startup implementation could be fetched from my github. It works with MVC, Web Forms and additionally brings JWT validation from IdentityServer v.3' codebase, upgraded to compile with the latest OWIN libraries.
If I still left anything unclear, don't hesitate to ask in the comments.
I have a web app MVC,using auth0 owin regular web app cookie based authentication.
This web app also has webapis which is used internally in the application. However i have a requirement to call this webapis from outside the application. So i created a restclient and tried to implement jwtbearerauthentication in application (but cookie based on authentication still in place).
Now when i call the webapi from other application it validates the bearer token gives no error however it redirects to login page due to cookie based authentication.
startup file:
public partial class Startup
{
private IPlatform platform;
public void ConfigureAuth(IAppBuilder app, IPlatform p, IContainer container)
{
platform = p;
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = System.TimeSpan.FromDays(2),
SlidingExpiration = true
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
var provider = new Auth0.Owin.Auth0AuthenticationProvider
{
OnReturnEndpoint = (context) =>
{
// xsrf validation
if (context.Request.Query["state"] != null && context.Request.Query["state"].Contains("xsrf="))
{
var state = HttpUtility.ParseQueryString(context.Request.Query["state"]);
AntiForgery.Validate(context.Request.Cookies["__RequestVerificationToken"], state["xsrf"]);
}
return System.Threading.Tasks.Task.FromResult(0);
},
OnAuthenticated = (context) =>
{
var identity = context.Identity;
//Add claims
var authenticationManager = container.Resolve<IAuthenticationManager>();
authenticationManager.AddClaims(identity);
if (context.Request.Query["state"] != null)
{
authenticationManager.AddReturnUrlInClaims(identity, context.Request.Query["state"]);
}
return System.Threading.Tasks.Task.FromResult(0);
}
};
var issuer = "https://" + ConfigurationManager.AppSettings["auth0:Domain"] + "/";
var audience = ConfigurationManager.AppSettings["auth0:ClientId"];
var secret = TextEncodings.Base64.Encode(TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["auth0:ClientSecret"]));
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
app.UseAuth0Authentication(
clientId: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientId"),
clientSecret: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientSecret"),
domain: platform.ServerRole.GetConfigurationSettingValue("auth0:Domain"),
provider: provider);
}
}
webapiconfig file:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional});
config.Filters.Add(new AuthorizeAttribute());
ODataConfig.Setup(config);
var clientID = WebConfigurationManager.AppSettings["auth0:ClientId"];
var clientSecret = WebConfigurationManager.AppSettings["auth0:ClientSecret"];
config.MessageHandlers.Add(new JsonWebTokenValidationHandler()
{
Audience = clientID,
SymmetricKey = clientSecret
});
}
Currently creating the jwt token from below code and posting using postman in header just to check if it works.. but redirects to login page.
string token = JWT.Encode(payload, secretKey, JwsAlgorithm.HS256);
I suspect what's happening is that your call to the API has a bearer token which fails validation (or there is no Authorize token at all), your API controller has an Authorize attribute, which, since there is no valid ClaimsPrincipal on the call throws 401. Auth0AuthenticationProvider picks that and assumes the call was to UI so redirects for user authentication. You may want to add an override in the Oauth0Provider to trap OnRedirectToIdP (or something like that), inspect the request and if it is to API, abot further handling and return Unauthorized.
Remove any [Authorize] from your API and see whether it works then. Also make sure your startup does not require Authorize for all controllers.
You may want to remove the authn part of your code (cookie and Oauth2Provider) and see whether you are getting to the API then.
A few years late i know, but i recently came across the same requirement in a project, and found this sample put together by a dev at Auth0.
https://github.com/auth0-samples/aspnet-core-mvc-plus-webapi
The example in the link allows for cookie authentication OR token authentication for the API endpoints.
The key takeaway for me was using attributes on your routes to tell the pipline what authentication mechanism to use. In my case i wanted cookie authentication for the UI and token authentication for the endpoints. i had no requirement to use both for any single area of the project.
controller:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
[Route("api")]
public string TestAuth()
{
return "All good " + this.User.FindFirst(ClaimTypes.NameIdentifier).Value + ". You only get this message if you are authenticated.";
}
For my WebAPI project OAuth handling, I have the following codes:
namespace Synergetic.API.WebAPI
{
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
//app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
}
There is also an Application subclassed from OAuthAuthorizationServerProvider that actually uses this method.
In the web client side I have this javascript for getting the token from server:
var token;
var loginData = {
grant_type: 'password',
username: $('#txtUser').val(),
password: $('#txtPassword').val()
};
var beforeSendFunc = function (request) {
//alert(token);
request.setRequestHeader("Authorization", "Bearer " + token);
};
$.ajax({
type: 'POST',
url: "Token",
data: loginData
}).success(function (data) {
.............
UPDATE: Having done some investigation,I am assuming it is not possible or at least hard to achieve to have more than one token access endpoint for an authorisation server. Can we instead send more information through get token client call?, I mean more data in addition to existing username, password, credential?
I Found this useful thread:
How do you consume extra parameters in OAuth2 Token request within .net WebApi2 application
We need to add the following line of code to the OAuthAuthorizationServerOptions method:
var data = await context.Request.ReadFormAsync();