OWIN CORS Issue in Web API - asp.net-web-api

I am working with WebApi and trying to add token based authentication using OWIN. It is working fine when client and service are in the same port. But facing a problem when both are on different servers.
I am using Jquery Ajax method to call the token service.
Here is the code sample I have used.
OWIN Code :
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
ConfigureOAuth(app);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/WagtokenService"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new ProjectAuthorizationServiceProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Provider
public class ProjectAuthorizationServiceProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (allowedOrigin == null) allowedOrigin = "*";
bool isValidUser = false;
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
if (context.UserName == "Test#Mail.com" && context.Password == "national")
{
isValidUser = true;
}
if (!isValidUser)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "admin"));
context.Validated(identity);
}
}
WebApi Config
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("http://192.168.2.175:3330", "WagtokenService,accept,accesstoken,authorization,cache-control,pragma,content-type,origin", "GET,PUT,POST,DELETE,TRACE,HEAD,OPTIONS");
config.EnableCors(cors);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "NewActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Following code snippet will be called when login button is clicked.
$('#a_login').click(function (e) {
debugger;
if (isValidEmailAddress($('#txt_UID').val()) && $('#txt_PWD').val() != "") {
var loginData = {
grant_type: 'password',
username: $('#txt_UID').val(),
password: $('#txt_PWD').val()
};
$.ajax({
url: url_bearerToken,
type: 'POST',
data: loginData,
contentType: "application/json",
done: function (data) {
// alert('success fully sign in to the application');
sessionStorage.setItem(bearer_token_key, data.access_token);
},
success: function (data) {
// alert('success fully sign in to the application');
sessionStorage.setItem(bearer_token_key, data.access_token);
window.location.href = "../Admin/UserProfiler.html";
},
error: function (x, h, r) {
///e.preventDefault();
// alert("Invalid user credentials");
$('#div_alert').show();
sessionStorage.setItem(bearer_token_key, '');
}
});
}
else {
$('#div_alert').show();
}
});
Getting Following issue .
XMLHttpRequest cannot load http://localhost:53014/WagtokenService. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://192.168.2.175:3330' is therefore not allowed access.

As Marcus said , it is enough to mention CORS setting either in Web API or in OWIN.

Related

openiddict with Owin - Unable to obtain configuration from .well-known/openid-configuration

I've been trying an OpenID authorization code flow using openiddict. Both the OIDC client and server are implemented using OpenIddict (with Owin) and both are web forms apps hosted in IIS.
The client side config:
public bool SetUp(IAppBuilder app)
{
try
{
var container = CreateContainer();
app.UseAutofacLifetimeScopeInjector(container);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieHttpOnly = true,
CookieSecure = CookieSecureOption.SameAsRequest
});
app.UseMiddlewareFromContainer<OpenIddictClientOwinMiddleware>();
DependencyResolver.SetResolver(new
AutofacDependencyResolver(container));
var scope = container.BeginLifetimeScope();
return true;
}
catch (Exception ex)
{
_logger.Error("OidcController: SetUp", ex);
return false;
}
}
private IContainer CreateContainer()
{
IdentityModelEventSource.ShowPII = true;
var services = new ServiceCollection();
services
.AddOpenIddict()
.AddCore(options => { })
.AddClient(options => {
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate()
.UseOwin()
.EnableRedirectionEndpointPassthrough();
options.UseSystemNetHttp();
//Try to reuse key cloak options
options.AddRegistration(new OpenIddictClientRegistration
{
Issuer = new Uri(_oidcSettings.ExternalUrl.RemoveAuth(), UriKind.Absolute),
ClientId = _oidcSettings.ClientId,
ClientSecret = _oidcSettings.ClientSecret,
RedirectUri = <the redirect uri>,
Scopes = { "email", "profile", "offline_access" }
});
});
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterControllers(typeof(OidcController).Assembly);
return builder.Build();
}
The server config:
protected void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
var services = new ServiceCollection();
services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
options.UseEntityFramework()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the authorization and token endpoints.
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetTokenEndpointUris("/connect/token");
options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles);
options.AllowAuthorizationCodeFlow();
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
options.UseOwin()
.EnableAuthorizationEndpointPassthrough();
})
// Register the OpenIddict validation components.
.AddValidation(options =>
{
options.UseLocalServer();
// Register the OWIN host.
options.UseOwin();
});
// Create a new Autofac container and import the OpenIddict services.
var builder = new ContainerBuilder();
builder.Populate(services);
Provider = new ContainerProvider(builder.Build());
Task.Run(async delegate
{
using var scope = Provider.ApplicationContainer.BeginLifetimeScope();
var context = scope.Resolve<ApplicationDbContext>();
context.Database.CreateIfNotExists();
var manager = scope.Resolve<IOpenIddictApplicationManager>();
//Add the client app
if (await manager.FindByClientIdAsync("SAS-Console") == null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "SAS-Console",
ClientSecret = "413ca0fd-8e24-4362-aaa3-d74095227658",
ConsentType = ConsentTypes.Implicit,
DisplayName = "Console application",
RedirectUris =
{
<client app redirect url>
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.ResponseTypes.Code
}
});
}
}).GetAwaiter().GetResult();
}
But when I run both, I continue to get an error:
IDX20803: Unable to obtain configuration from: 'https:///.well-known/openid-configuration

When I make a POST to my Web API I get a 401 exception

I am making an Ajax call to a Web Core API server using localhost on my development machine.
This is the code that makes the call;
addDataToDatabase = function (callback, errorCallback, url, data) {
$.ajax({
async: true,
url: url,
contentType: "application/json",
dataType: "text",
data: data,
type: "POST",
xhrFields: { withCredentials: true }
})
.done(function (data) {
callback(data);
})
.fail(function (data) {
errorCallback(data);
});
However I get a 401 unauthorised user exception.
In my startup Configure method I set up CORS as follows
var url = Configuration["originUrl"];
app.UseCors(
options => options.WithOrigins(url).AllowAnyHeader().AllowAnyMethod().AllowCredentials()
);
In my appSettings file I set the orginUrl:
"originUrl": "http://localhost:12345"
Which all works for GET type calls but not POST.
What do I need to fix?
EDIT - upon request this is the full startup code;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
namespace Properties.API
{
using Domain;
using Domain.Interfaces;
using Domain.Repo;
using EF6;
using Helper;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog;
using NLog.Config;
using NLog.Extensions.Logging;
using NLog.Web;
using Sir.EF6;
using Sir.EF6.Interfaces;
using Sir.EF6.Repo;
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appSettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appSettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public static IConfigurationRoot Configuration;
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(typeof(Startup).Assembly));
services.AddSingleton(manager);
services.AddCors();
services.AddMvcCore().AddJsonFormatters();
services.Configure<IISOptions>(options => new IISOptions
{
AutomaticAuthentication = true,
ForwardClientCertificate = false,
ForwardWindowsAuthentication = false
});
var connectionStringMSurveyV2 = Configuration.GetConnectionString("MSurveyV2Db");
services.AddScoped<MSurveyV2Db>(_ => new MSurveyV2Db(connectionStringMSurveyV2));
var connectionStringSir = Configuration.GetConnectionString("SirDb");
services.AddScoped<SirDb>(_ => new SirDb(connectionStringSir));
services.AddScoped<IPropertiesRepo, PropertiesRepo>();
services.AddScoped<ISirUoW, SirUoW>();
services.AddScoped<IPropertyUoW, PropertyUoW>();
services.AddScoped<Services.IMailService, Services.MailService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug();
loggerFactory.AddNLog();
LogManager.Configuration = new XmlLoggingConfiguration(#"nlog.config");
var connectionString = Configuration.GetConnectionString("SirNLogDb");
LogManager.Configuration.Variables["SirNLogDb"] = connectionString;
app.AddNLogWeb();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler();
}
var url = Configuration["originUrl"];
app.UseCors(
options => options.WithOrigins(url).AllowAnyHeader().AllowAnyMethod().AllowCredentials()
);
app.UseMvc();
AutomapperInit.Set();
var logger = LogManager.GetCurrentClassLogger();
logger.Info("Started Properties Web API");
logger.Info($"Origin url = {url}");
}
}
}
This is a POST method that provokes the 401 exception;
[HttpPost("add")]
public async Task<IActionResult> Add([FromBody] InspectionVisitInputDto inspectionVisitDto)
{
this.NLogger.Info("api/inspectionVisit/add".ToPrefix());
if (inspectionVisitDto == null)
{
NLogger.Error("No data sent");
return BadRequest(ModelState);
}
if (inspectionVisitDto.InspectionId < 1)
{
NLogger.Error($"Invalid InspectionId < 1 (actual value is {inspectionVisitDto.InspectionId}");
return BadRequest(ModelState);
}
var inspectionVisit = Mapper.Map<InspectionVisit>(inspectionVisitDto);
var dateOfVisit = inspectionVisit.DateOfVisit.Date;
try
{
var existingInspectionVisit = this.SirUoW.InspectionVisit.GetItem(
x => x.InspectionId == inspectionVisit.InspectionId &&
DbFunctions.TruncateTime(x.DateOfVisit) == dateOfVisit &&
x.ContractSubcontractorId == inspectionVisit.ContractSubcontractorId &&
x.SelectedInspectorUsername == inspectionVisit.SelectedInspectorUsername &&
x.WorkPhaseId == inspectionVisit.WorkPhaseId);
if (existingInspectionVisit?.InspectionVisitId > 0)
{
NLogger.Info($"Inspection Visit Id = {existingInspectionVisit.InspectionVisitId} already exists.");
return Ok(existingInspectionVisit.InspectionVisitId);
}
}
catch (Exception e)
{
var message = "Cannot get inspection visit";
await ReportException(e, message);
var status = OperationStatus.CreateFromException(message, e);
return BadRequest(status);
}
NLogger.Info("Add New Inspection Visit");
try
{
this.SirUoW.InspectionVisit.Add(inspectionVisit);
await SirUoW.LoggedInSaveChangesAsync(this.LoggedInUser);
NLogger.Info("New Inspection Visit Saved, id = " + inspectionVisit.InspectionVisitId);
if (inspectionVisit.ContractSubcontractorId != null)
{
await SaveContractSubcontractorIdForInspection(inspectionVisitDto);
}
return Ok(inspectionVisit.InspectionVisitId);
}
catch (Exception e)
{
var message = "Cannot save " + inspectionVisitDto;
await ReportException(e, message);
var status = OperationStatus.CreateFromException(message, e);
return BadRequest(status);
}
}
And this is a GET method that works OK
[HttpGet("getall")]
public async Task<IActionResult> GetContracts()
{
this.NLogger.Info("api/contracts/getall".ToPrefix());
try
{
var contracts = this.SirUoW.ViewAllContracts.GetList(x => x.IsMainDivision && x.EndDate == null).OrderBy(x => x.FullContractNo).ToList();
return Ok(contracts);
}
catch (Exception e)
{
var message = "Error getting contracts";
await ReportException(e, message);
var status = OperationStatus.CreateFromException(message, e);
return BadRequest(status);
}
}
Your scenario is combining IIS Windows Authenticaiton and CORS, since preflight request OPTIONS would not contains any security header, it will hit the error 401.
Try workaround below:
Enable anonymousAuthentication in appsettings.json
{
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": true
}
}
For enable authentication, try Filter to disable anonymous access.
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});

Response for preflight is invalid (redirect) using Asp.Net Web API

I'm developing a website which uses AngularJS in the frontend and ASP.NET web API in the backend. I'm trying to configure external logins(Facebook) for my site. I have already enabled Cors on the API. The order of API calls are:
API call to
api/Account/ExternalLogins?returnUrl=%2F&generateState=true
to get a list of external login providers.
This returns
[{"name":"Facebook",
"url":"/api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A15359%2F&state=xxxxxxxxxx",
"state":"xxxxxxxxxx"}]
Now I send a GET request to url returned earlier. This triggers a preflight request to https://www.facebook.com/dialog/oauth?response_type=code&client_id=xxxxxxx&redirect_uri=https%3A%2F%2Flocalhost%3A44327%2Fsignin-facebook&scope=manage_pages&state=xxxxxxx which gives error. This is never redirected to Facebook Login Page.
Both the request and response headers for /api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A15359%2F&state=xxxxxxxxxx contains Access-Control-Allow-Origin:*
But for the preflight request https://www.facebook.com/dialog/oauth?response_type=code&client_id=xxxxxxx&redirect_uri=https%3A%2F%2Flocalhost%3A44327%2Fsignin-facebook&scope=manage_pages&state=xxxxxxx Access-Control-Allow-Origin header is missing.
The code snippets are mentioned below.
app.js
$httpProvider.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
$httpProvider.defaults.headers.common['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, Content-Length, X-Requested-With';
$httpProvider.defaults.headers.common['Access-Control-Allow-Credentials'] = true;
$httpProvider.defaults.headers.common['Access-Control-Allow-Method'] = 'GET, PUT, POST, DELETE, OPTIONS';
Start.Auth.cs
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCors(CorsOptions.AllowAll);
// 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
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/api/Account/ExternalLogin"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
//Provider = new AuthorizationServerProvider()
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions
{
AppId = "xxxxxxxxx",
AppSecret = "xxxxxxxxx",
BackchannelHttpHandler = new FacebookBackChannelHandler(),
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return Task.FromResult(0);
}
},
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
SendAppSecretProof = true
};
facebookOptions.Scope.Add(ConfigurationManager.AppSettings["Facebook_Scope"]);
facebookOptions.AuthenticationMode = AuthenticationMode.Passive;
app.UseFacebookAuthentication(facebookOptions);
AccountController
[Authorize]
[EnableCors(origins: "*", headers: "*", methods: "*")]
[RoutePrefix("api/Account")]
public class AccountController : ApiController
{
private const string LocalLoginProvider = "Local";
private ApplicationUserManager _userManager;
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager,
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
{
UserManager = userManager;
AccessTokenFormat = accessTokenFormat;
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }
// GET api/Account/UserInfo
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("UserInfo")]
public UserInfoViewModel GetUserInfo()
{
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
return new UserInfoViewModel
{
Email = User.Identity.GetUserName(),
HasRegistered = externalLogin == null,
LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
};
}
// POST api/Account/Logout
[Route("Logout")]
[AllowAnonymous]
public IHttpActionResult Logout()
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return Ok();
}
// POST api/Account/AddExternalLogin
[Route("AddExternalLogin")]
public async Task<IHttpActionResult> AddExternalLogin(AddExternalLoginBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationTicket ticket = AccessTokenFormat.Unprotect(model.ExternalAccessToken);
if (ticket == null || ticket.Identity == null || (ticket.Properties != null
&& ticket.Properties.ExpiresUtc.HasValue
&& ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow))
{
return BadRequest("External login failure.");
}
ExternalLoginData externalData = ExternalLoginData.FromIdentity(ticket.Identity);
if (externalData == null)
{
return BadRequest("The external login is already associated with an account.");
}
IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(),
new UserLoginInfo(externalData.LoginProvider, externalData.ProviderKey));
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
// GET api/Account/ExternalLogin
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
if (error != null)
{
return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
else
{
IEnumerable<Claim> claims = externalLogin.GetClaims();
ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
Authentication.SignIn(identity);
}
return Ok();
}
// GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true
[AllowAnonymous]
[Route("ExternalLogins")]
public IEnumerable<ExternalLoginViewModel> GetExternalLogins(string returnUrl, bool generateState = false)
{
IEnumerable<AuthenticationDescription> descriptions = Authentication.GetExternalAuthenticationTypes();
List<ExternalLoginViewModel> logins = new List<ExternalLoginViewModel>();
string state;
if (generateState)
{
const int strengthInBits = 256;
state = RandomOAuthStateGenerator.Generate(strengthInBits);
}
else
{
state = null;
}
foreach (AuthenticationDescription description in descriptions)
{
ExternalLoginViewModel login = new ExternalLoginViewModel
{
Name = description.Caption,
Url = Url.Route("ExternalLogin", new
{
provider = description.AuthenticationType,
response_type = "token",
client_id = Startup.PublicClientId,
redirect_uri = new Uri(Request.RequestUri, returnUrl).AbsoluteUri,
state = state
}),
State = state
};
logins.Add(login);
}
return logins;
}
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null)
{
return InternalServerError();
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
protected override void Dispose(bool disposing)
{
if (disposing && _userManager != null)
{
_userManager.Dispose();
_userManager = null;
}
base.Dispose(disposing);
}
}
I know this is achievable using MVC Account Controller. I'm looking for a way to do this entirely using web api with no dependency on MVC.
Thanks!

which TokenValidationParameters are needed with UseJwtBearerAuthentication

I am tying to do JwtBearerAuthentication on my .net WebAPI and it is just not working. The Authorize attribute is always claiming isAuthorized = false.
I am working with Okta as the SSO. I am authenticating on my client side and getting both an access token and id token. On a webapi get request I am providing the access token (i have also tried the id token) in the authorize header and I am able to see the authorize header with the token in the webapi actioncontext.
In my startup.cs I have the following
var clientID = WebConfigurationManager.AppSettings["okta:ClientId"];
var oidcIssuer = WebConfigurationManager.AppSettings["okta:OIDC_Issuer"];
TokenValidationParameters tvps = new TokenValidationParameters
{
ValidAudience = clientID,
ValidateAudience = true,
ValidIssuer = oidcIssuer,
ValidateIssuer = true
};
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
TokenValidationParameters = tvps,
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new OpenIdConnectCachingSecurityTokenProvider(oidcIssuer + "/.well-known/openid-configuration")
}
});
Am i missing some TokenValidationParameters that I need?
As I was typing this, I see that twaldron was able to figure it out!
I also realized that he was asking about WebAPI, and not MVC. However, here is the code that I needed to get the following working with ASP.NET Core MVC, of particular interest might be this line, which is necessary to get access to the additional claims in the JWT:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
Here is how this code sample works from the command line, the $ID_TOKEN variable contains a valid JWT:
$ curl -H "Authorization: Bearer ${ID_TOKEN}" http://localhost:3000/test/test
sub: 01a23b4cd5eFgHI6j7k8 email:test#example.com
Setup.cs:
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
namespace WebApplication
{
public class Startup
{
readonly string clientId = string.Empty;
readonly string issuer = string.Empty;
readonly string audience = string.Empty;
public Startup(IHostingEnvironment env)
{
clientId = "A0b1CDef2GHIj3k4lm5n";
issuer = "https://example.okta.com";
audience = "A0b1CDef2GHIj3k4lm5n";
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddDebug();
// https://github.com/aspnet/Security/issues/1043#issuecomment-261937401
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
TokenValidationParameters tvps = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = audience,
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
MetadataAddress = issuer + "/.well-known/openid-configuration",
TokenValidationParameters = tvps
});
app.UseStaticFiles();
// Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715
app.UseMvc(routes =>
{
routes.MapRoute(
name: "test-controller",
template: "test/{action}",
defaults: new { controller = "Test", action = "Index" }
);
routes.MapRoute(
name: "default",
template: "{controller=Test}/{action=Index}/{id?}");
});
}
}
}
In Controllers/Test.cs:
[Authorize]
public IActionResult Test()
{
var contextUser = User.Identity as ClaimsIdentity;
Dictionary<string, string> claim = contextUser.Claims.ToDictionary(x => x.Type, x => x.Value);
var output = "sub: " + claim["sub"] + " email:" + claim["email"];
return Content(output);
}
My problem was not with the options.
It was 100% the need to move
app.UseWebApi(config);
below all the owin setup stuff.

Unable to call web api POST action

Below is my web api stuff related post/save the record:
var c = $.ajax({
url: 'api/values/CreateAjaxNew',
type: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: form.serializeArray(),
success: function (data) {
alert(data);
$("#msg").html("Saved Successfully.");
$("#msg").css("color", "green");
//window.location = "index";
//alert("in success");
},
error: function (e1, e2, e3) { alert(e3); }
});
Problem: it just gieves me error as Not Found.
Values controller api:
[HttpPost]
public HttpResponseMessage CreateAjaxNew(ProductViewModel vm)
{
try
{
// TODO: Add insert logic here
//manager.ProductManager m = new manager.ProductManager();
using (aRef.ServiceIntf2Client r = new aRef.ServiceIntf2Client())
{
r.InsertProduct(new common.DTO.ProductDTO() { Id = vm.Id, ProductName = vm.ProductName, Description = vm.Description, Cost = vm.Cost, ProductTypeId = vm.ProductTypeId });
}
//m.InsertProduct(new common.DTO.ProductDTO() { Id = vm.Id, ProductName = vm.ProductName, Description = vm.Description, Cost = vm.Cost, ProductTypeId = vm.ProductTypeId });
return Request.CreateResponse(HttpStatusCode.OK,true);
}
catch
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, false);
}
}
Please guide me how i can resolve this issue so, request reach to the api action.
routes as follow:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute("DefaultApiGet",
"api/{controller}",
new { action = "AllProductTypes" },
new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
config.Routes.MapHttpRoute("DefaultApiGet1",
"api/{controller}",
new { action = "GetAllProducts" },
new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
config.Routes.MapHttpRoute("DefaultApiGetWithId",
"api/{controller}/{id}",
new { id = RouteParameter.Optional, action = "Get" },
new { id = #"\d+" });
config.Routes.MapHttpRoute("DefaultApiWithAction",
"api/{controller}/{action}");
config.Routes.MapHttpRoute("DefaultApiWithActionAndId",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional },
new { id = #"\d+(_\d+)?" });
The route ordering seems to be wrong. You need to move the default route to the last and the more generic route first .
http://richarddingwall.name/2008/08/09/three-common-aspnet-mvc-url-routing-issues/
As of now the api/{controller}/{id} the default route get executed and the controller is not found.
config.Routes.MapHttpRoute("DefaultApiGet",
"api/{controller}",
new { action = "AllProductTypes" },
new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
config.Routes.MapHttpRoute("DefaultApiGet1",
"api/{controller}",
new { action = "GetAllProducts" },
new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
config.Routes.MapHttpRoute("DefaultApiGetWithId",
"api/{controller}/{id}",
new { id = RouteParameter.Optional, action = "Get" },
new { id = #"\d+" });
config.Routes.MapHttpRoute("DefaultApiWithAction",
"api/{controller}/{action}");
config.Routes.MapHttpRoute("DefaultApiWithActionAndId",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional },
new { id = #"\d+(_\d+)?" });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
This ordering should be able to solve the issue. Hope this helps

Resources