Openiddict multiple refresh tokens - asp.net-web-api

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>().

Related

_userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email)) returns null

I am having an issue with Claims not populating with ClaimsPrinciple after creating a JWT. I am using ASP.NET Core 6 on VS 2022. The issue raised after configuring identity to include Roles and RolesUsers. I had no issues prior to including these 2 identity tables from the automated generated ones from IdentityModel.
now on creation, I show no errors and receive the JWT token without any issues, but afterwards when I try to authorize the user that log in the ClaimIdentity does not propagate and errors on _userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email)) showing null.
Here is some code to show the current state of the project.
First is the Method that handles the validation for login users.
[Authorize]
[HttpGet]
public async Task<ActionResult<UserDto>> GetCurrentUser()
{
// Null Exception Error
var user = await _userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email));
return CreateUserObject(user);
}
UserDto CreateUserObject( AppUser user )
{
return new UserDto
{
DisplayName = user.DisplayName,
Image = null,
Token = _tokenService.CreateToken(user),
Username = user.UserName
};
}
This is my Token Service that handles creating the JWT token from users that Register or Login.
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.0),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
This is what I changed prior to having this issue in my IdentityServiceExtension Class.
public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration config)
{
services.AddIdentity<AppUser, AppRole>(opt => //Changed AddIdentityCore to AddIdentity to apply AppUser & AppRole
{
opt.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<DataContext>()
.AddSignInManager<SignInManager<AppUser>>()
.AddRoleManager<RoleManager<AppRole>>(); //Added Role Manager for Roles to loaded.
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>();
// Added Roles to Policy
services.AddAuthorization(opt =>
{
opt.AddPolicy("Verified", pol =>
pol.RequireRole("User", "Staff", "Admin", "Guest"));
opt.AddPolicy("Restricted", pol =>
pol.RequireRole("User", "Staff", "Admin"));
opt.AddPolicy("EmployeeAccess", pol =>
pol.RequireRole("Staff", "Admin"));
opt.AddPolicy("ManagerAccess", pol =>
pol.RequireRole("Admin"));
});
//////////////////////
return services;
}
Hopefully this is enough information to help me with this issue. I have searched all over online and the resolutions I have seen does not match to my particular issue to solve the problem.
I surprisingly found the issue, so the reason I was having errors was due to not configuring Identity to handle all Identity Models. Prior to my change, I only handled users, but by adding roles and roleusers I had to handle all of Identity Model to prevent losing the claims. Due to this fact, I had to install another Microsoft Package,Microsoft.AspNetCore.Identity.UI, to gain access to the Identity Helper Method (.AddDefaultIdentity()) to configure the generated identity tables. Once added, Identity was fully configured and the issue was resolved. I hope anyone else that need help can use this as a possible solution.

ASP.NET Core Secure Api

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.

IdentityServer4 move login UI on the client

I am implementing IdentityServer4 with variety of clients, One of the clients is a Javascript application, I have implemented the implicit flow for authentication and everything is working fine.
On my Javascript application , I have a button to login, once I click on the button I am redirected to IdentityServer and after successful login I am redirected back to my application along with my access token.
Now what I want to do is, move the login to the client side so that each application can have its own login UI (with its own theme).
app.js
function log() {
document.getElementById('results').innerText = "";
Array.prototype.forEach.call(arguments, function (msg) {
if (msg instanceof Error) {
msg = "Error:" + msg.message;
}
else if (typeof msg !== 'string') {
msg = JSON.stringify(msg, null, 2);
}
document.getElementById('results').innerHTML += msg + "\r\n";
});
}
document.getElementById("login").addEventListener('click', login, false);
document.getElementById('api').addEventListener('click', api, false);
document.getElementById("logout").addEventListener("click", logout, false);
//configure client
var config = {
authority: "http://localhost:5000",
client_id: "js",
redirect_uri: "http://localhost:5004/callback.html",
response_type: "id_token token",
scope: "openid profile api1 role",
post_logout_redirect_uri: "http://localhost:5004/index.html"
};
//init user manager
var mgr = new Oidc.UserManager(config);
//check if user is logged in already
mgr.getUser().then(function (user) {
if (user) {
log("User logged in", user.profile);
} else {
log("User is not logged in.");
}
});
function login() {
mgr.signinRedirect();
}
function api() {
mgr.getUser().then(function (user) {
var url = "http://localhost:5001/identity/getfree";
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function () {
log(xhr.status, JSON.parse(xhr.responseText));
};
xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
xhr.send();
});
}
function logout() {
mgr.signoutRedirect();
}
IdentityServer StartUp.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
var connectionString = "Server=localhost;port=3306;database=netcore;uid=root;Password=Liverpool1";
services.AddApplicationInsightsTelemetry(Configuration);
services.AddDbContext<ApplicationDbContext>(options => options.UseMySQL(connectionString));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryScopes(Config.GetScopes())
.AddInMemoryClients(Config.GetClients())
// .AddConfigurationStore(builder => builder.UseMySQL(connectionString))
//.AddOperationalStore(builder => builder.UseMySQL(connectionString))
.AddAspNetIdentity<ApplicationUser>();
}
This is not possible and breaks the whole point of implicit flow and all the other federated sign on flows. The whole point of implicit flow is that you do not pass user credentials through the client but rather it goes to the identity provider.
You have two options:
Finding out a way to serve up different logins per "tenant" in
ASP.NET Core.
Use Resource Owner flow and pass the user credentials
through the client.
Option 1 is probably the best but requires more work, option 2 is a cop out and using RO flow is an anti-pattern.

AspNetCore OpenIdConnect Error Handling

In my MVC Core project, after authenticating against Azure AD, I check to see if the User exists in my application database. If User does not exist I want to throw an exception and redirect to my Home/Error page.
Instead, the redirect code in OnAuthenticationFailed results in a redirect loop and finally quits with the error:
Bad Request - Request Too Long HTTP Error 400. The size of the request
headers is too long.
app.UseExceptionHandler("/Home/Error");
app.UseCookieAuthentication();
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
...
Events = new OpenIdConnectEvents()
{
OnAuthorizationCodeReceived = async (context) =>
{
...
upn = identity.FindFirst(ClaimTypes.Upn).Value;
MyDbContext db =
new MyDbContext(Configuration.GetConnectionString("DefaultConnection"));
if (db.Users.FirstOrDefault(b => (b.UPN == upn)) == null)
{
throw new System.IdentityModel.Tokens.SecurityTokenValidationException("You are not registered to use this application.");
}
},
OnAuthenticationFailed = (context) =>
{
context.Response.Redirect("/Home/Error");
context.HandleResponse();
return Task.FromResult(0);
}
}
});
UPDATE:
This has been resolved. By default the HomeController has [Authroize] attribute set on the entire class so the redirect could not reach the Error action without being authenticated.

Web app and web api authentication in same application

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.";
}

Resources