Use Active Directory with Web API for SPA - c#

I am building single page application and I would like to know user's identity. We have Active Directory in our intranet but I don't know much about it. I am able to use code like this to verify username and password.
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}
In fact that is everything I need from the Active Directory side of thing. So I could create some AuthorizationFilter with this code but this would mean that username and password must be in every request. I would like to send username and password only once and then use some token for authorization. So there should be some token provider inside my server-side application.
I am building this application from scratch so I can use the latest .net technologies. I found that there are some Owin middlewares which are handling tokens, cookies and OAuth. Could anything of that help me?

When I was searching for something completely different, I found this incredible project on CodePlex: https://angularapp.codeplex.com/ It is exactly what I was looking for.
Update:
The part of this app which was useful to me is the DomainUserLoginProvider
public class DomanUserLoginProvider : ILoginProvider
{
public bool ValidateCredentials(string userName, string password, out ClaimsIdentity identity)
{
using (var pc = new PrincipalContext(ContextType.Domain, _domain))
{
bool isValid = pc.ValidateCredentials(userName, password);
if (isValid)
{
identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
}
else
{
identity = null;
}
return isValid;
}
}
public DomanUserLoginProvider(string domain)
{
_domain = domain;
}
private readonly string _domain;
}
LoginProvider is used in AccountController's action to verify the credentials. Also the AccessToken is created here.
[HttpPost, Route("Token")]
public IHttpActionResult Token(LoginViewModel login)
{
ClaimsIdentity identity;
if (!_loginProvider.ValidateCredentials(login.UserName, login.Password, out identity))
{
return BadRequest("Incorrect user or password");
}
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
return Ok(new LoginAccessViewModel
{
UserName = login.UserName,
AccessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket)
});
}
Last but not least, add the correct middleware to Owin pipeline.
public partial class Startup
{
static Startup()
{
OAuthOptions = new OAuthAuthorizationServerOptions();
}
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static void ConfigureAuth(IAppBuilder app)
{
app.UseOAuthBearerTokens(OAuthOptions);
}
}
On the client side you just send the request with credentials to the Token action of AccountController and you'll get the authentication token. Save this token in browser ("Remember me" feature) and set the Angular's $http service to attach token to each request.
services.factory('$auth', ['$q', '$http', '$path', function ($q, $http, $path) {
var tokenUrl = $path('api/Account/Token');
function setupAuth(accessToken, remember) {
var header = 'Bearer ' + accessToken;
delete $http.defaults.headers.common['Authorization'];
$http.defaults.headers.common['Authorization'] = header;
sessionStorage['accessToken'] = accessToken;
if (remember) {
localStorage['accessToken'] = accessToken;
}
return header;
}
var self = {};
self.login = function(user, passw, rememberMe) {
var deferred = $q.defer();
$http.post(tokenUrl, { userName: user, password: passw })
.success(function (data) {
var header = setupAuth(data.accessToken, rememberMe);
deferred.resolve({
userName: data.userName,
Authorization: header
});
})
.error(function() {
deferred.reject();
});
return deferred.promise;
};
return self;
}]);

Surely the OAuth is a way to go but why don't you consider old good Forms Authentication? It fits the scenario perfectly - with a custom provider you can authenticate users in the AD and then all consecutive client requests carry the authentication cookie which can be used to authenticate server calls.
The Forms cookie in this scenario plays the role of a "token" you think of.

Related

Authenticating .net console applications with .net core web API

I have a .net core 3.1 web API ,which was built with JWT authentication and it is integrated with Angular UI and it working as expected.
following is my JWT authentication middleware
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
// Adding Jwt Bearer
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
Now i need to create some more Web API methods which will be consumed by Angular UI as well as some existing scheduled tasks (.net console applications which will be consuming the web api methods) which are created for internal operations and will be running in the background.
My API controllers are decorated with [Authorize] attribute. It is working fine with Angular UI where the authentication and authorization are implemented using JWT bearer token.The problem is now with the integration of scheduled tasks which does not have logic for getting the tokens.
How to integrate these console apps with .net core web API in terms of authentication? the easiest option (which i thought) is to create a user login with like username "servicetask" and obtain token based on that username and do the API operation (but this requires more effort since no.of console apps are more and there and some apps from other projects also).
Is there any way to handle authentication in this case?
Is it good practice to pass some API key from console application and by pass the authentication in web API ? is that possible ? then how to handle the request in .net core web api?
Is it possible to create any JWT role or claims for these service account and validate them?
Please help.
Best approach would be to allow both bearer token and API key authorization, especially since you are allowing access for users and (internal) services.
Add API key middleware (I personally use this, it's simple to use - package name is AspNetCore.Authentication.ApiKey) with custom validation (store API keys in database along with regular user data or in config, whatever you prefer). Modify [Authorize] attributes on controllers so both Bearer and ApiKey authorization can be used. Angular app continues to use Bearer authentication and any service/console apps (or any other client, including Angular client if needed in some case) sends X-Api-Key header containing API key assigned to that app.
Middleware configuration should look something like this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddApiKeyInHeader(ApiKeyDefaults.AuthenticationScheme, options =>
{
options.KeyName = "X-API-Key";
options.SuppressWWWAuthenticateHeader = true;
options.Events = new ApiKeyEvents
{
// A delegate assigned to this property will be invoked just before validating the api key.
OnValidateKey = async (context) =>
{
var apiKey = context.ApiKey.ToLower();
// custom code to handle the api key, create principal and call Success method on context. apiUserService should look up the API key and determine is it valid and which user/service is using it
var apiUser = apiUserService.Validate(apiKey);
if (apiUser != null)
{
... fill out the claims just as you would for user which authenticated using Bearer token...
var claims = GenerateClaims();
context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
}
else
{
// supplied API key is invalid, this authentication cannot proceed
context.NoResult();
}
}
};
})
// continue with JwtBearer code you have
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, x => ...
This sorts out Startup.cs part.
Now, in controllers, where you want to enable both Bearer and ApiKey authentication, modify attribute so it looks like this:
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = "ApiKey, Bearer")]
public class SomeController : ControllerBase
Now, Angular client will still work in the same way but console app might call API like this:
using (HttpClient client = new HttpClient())
{
// header must match definition in middleware
client.DefaultRequestHeaders.Add("X-API-Key", "someapikey");
client.BaseAddress = new Uri(url);
using (HttpResponseMessage response = await client.PostAsync(url, q))
{
using (HttpContent content =response.Content)
{
string mycontent = await content.ReadAsStringAsync();
}
}
}
This approach in my opinion makes best use of AuthenticationHandler and offers cleanest approach of handling both "regular" clients using JWT and services using fixed API keys, closely following something like OAuth middleware. More details about building custom authentication handler if someone wants to build something like this from scratch, implementing basically any kind of authentication.
Downside of course is security of those API keys even if you are using them for internal services only. This problem can be remedied a bit by limiting access scope for those API keys using Claims, not using same API key for multiple services and changing them periodically. Also, API keys are vulnerable to interception (MITM) if SSL is not used so take care of that.
1.IMO, no , this won't be good idea.
2. Yes you can use claims for this scenario .
Use a BackgroundService to run your task and inject claims principle on this class.
This sample is for service provider account claims:
serviceAccountPrincipleProvider.cs
public class ServiceAccountPrincipalProvider : IClaimsPrincipalProvider
{
private readonly ITokenProvider tokenProvider;
public ServiceAccountPrincipalProvider(ITokenProvider tokenProvider)
{
this.tokenProvider = tokenProvider;
}
public ClaimsPrincipal CurrentPrincipal
{
get
{
var accessToken = tokenProvider.GetAccessTokenAsync().GetAwaiter().GetResult();
if (accessToken == null)
return null;
var identity = new ClaimsIdentity(AuthenticationTypes.Federation);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, accessToken.Subject));
identity.AddClaim(new Claim(AppClaimTypes.Issuer, accessToken.Issuer));
identity.AddClaim(new Claim(AppClaimTypes.AccessToken, accessToken.RawData));
return new ClaimsPrincipal(identity);
}
}
}
This is your IClaimsProvider interface:
public interface IClaimsPrincipalProvider
{
ClaimsPrincipal CurrentPrincipal { get; }
}
Don`t bypass authentication. You can pass appKey (key to identify the app instance) to webapi endpoint that is responsible for identifying your dotnet console apps. If appkey is part of your registered appkeys list, let the webapi endpoint get token on behalf of the console app by subsequent authentication step with your webapi auth service and return a JWT token to the console app.
In my case I have console apps running on dotnet 4.5, I mention this because HttpClient is not available in previous versions. With HttpClient you can then do the following in your console app.
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("localhost://mywebapi/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/text"));
HttpResponseMessage response= client.GetAsync("api/appidentityendpoint/appkey").GetAwaiter().GetResult();
var bytarr = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
string responsemessage = Encoding.UTF8.GetString(bytarr);
res = JsonConvert.DeserializeObject<Authtoken>(responsemessage);
Authtoken object can be as simple as
public class Authtoken
{
public string JwtToken{ get; set; }
}
and once you have your token you add it to your HttpClient headers for subsequent calls to your protected endpoints
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + res.JwtToken);
client.GetAsync("api/protectedendpoint").GetAwaiter().GetResult();
Error handling is obviously required to handle reauthentication in case of token expiry etc
On server side, a simplified example is as follows
[Produces("application/json")]
[Route("api/Auth")]
public class AuthController : Controller
{
private readonly IAppRegService _regAppService;
public AuthController(IAppRegService regAppService){
_regAppService = regAppService;
};
//api/auth/console/login/585
[HttpGet, Route("console/login/{appkey}")]
public async Task<IActionResult> Login(string appkey)
{
// write logic to check in your db if appkey is the key of a registered console app.
// _regAppService has methods to connect to db or read file to check if key exists from your repository of choice
var appkeyexists = _regAppService.CheckAppByAppKey(appkey);
if(appkeyexists){
//create claims list
List<Claim> claims = new List<Claim>();
claims.Add(new Claim("appname", "console",ClaimValueTypes.String));
claims.Add(new Claim("role","daemon",ClaimValueTypes.String));
//create a signing secret
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("yoursecretkey"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
//create token options
var tokenOptions = new JwtSecurityToken(
issuer: "serverurl",
audience:"consoleappname",
claims: claims,
expires: DateTime.Now.AddDays(5),
signingCredentials: signinCredentials
);
//create token
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
//return token
return new OkObjectResult(new Authtoken { JwtToken= tokenString });
} else {
return Unauthorized();
}
}
}
I will present how I do JWT Auth from a WebAssembly app to a .NET Core API. Everything is based on this YouTube video. It explains everything you need to know. Down below is a sample of code from the video to give you an idea of what you have to do.
This is my Auth Controller:
// A bunch of usings
namespace Server.Controllers.Authentication
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class AuthenticateController : ControllerBase
{
private readonly UserManager<ApplicationUser> userManager;
private readonly RoleManager<IdentityRole> roleManager;
private readonly IConfiguration _configuration;
private readonly AppContext appContext;
public AuthenticateController(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IConfiguration configuration, AppContext appContext)
{
this.userManager = userManager;
this.roleManager = roleManager;
this._configuration = configuration;
this.appContext = appContext;
}
[HttpPost]
[Route("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] LoginModel loginModel)
{
ApplicationUser user = await userManager.FindByNameAsync(loginModel.Username);
if ((user is not null) && await userManager.CheckPasswordAsync(user, loginModel.Password))
{
IList<string> userRoles = await userManager.GetRolesAsync(user);
List<Claim> authClaims = new()
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.AuthenticationMethod, "pwd")
};
foreach (string role in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, role));
}
SymmetricSecurityKey authSigningKey = new(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
//SymmetricSecurityKey authSigningKey = Startup.SecurityAppKey;
JwtSecurityToken token = new(
issuer: _configuration["JWT:ValidIssuer"],
//audience: _configuration["JWT:ValidAudience"],
expires: DateTime.Now.AddHours(3),
claims: authClaims,
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
[HttpPost]
[Route("register")]
[AllowAnonymous]
public async Task<IActionResult> Register([FromBody] RegisterModel model)
{
ApplicationUser userExists = await userManager.FindByNameAsync(model.Username);
if (userExists != null)
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User already exists!" });
}
ApplicationUser user = new()
{
Email = model.Email,
SecurityStamp = Guid.NewGuid().ToString(),
UserName = model.Username
};
IdentityResult result = await userManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User creation failed! Please check user details and try again." });
}
await userManager.AddToRoleAsync(user, UserRoles.User);
return Ok(new Response { Status = "Success", Message = "User created successfully!" });
}
}
}
When a user registers it is automatically added to the User Role. What you should do is to create accounts for each of your console apps, or even a global account for all internal apps, and then assign it to a custom role.
After that, on all API endpoints that are only accessible by your internal apps add this attribute: [Authorize(Roles = UserRoles.Internal)]
UserRoles is a static class that has string properties for each role.
More info about Role-based authorization can be found here.
You can create a login password configuration on appsettings, db or somewhere to send a token (web api).
Worker.cs (console app)
public struct UserLogin
{
public string user;
public string password;
}
// ...
private async Task<string> GetToken(UserLogin login)
{
try {
string token;
var content = new StringContent(JsonConvert.SerializeObject(login), Encoding.UTF8, "application/json");
using (var httpClient = new HttpClient())
using (var response = await httpClient.PostAsync($"{api}/login", content))
{
var result = await response.Content.ReadAsStringAsync();
var request = JsonConvert.DeserializeObject<JObject>(result);
token = request["token"].ToObject<string>();
}
return token;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
Give your console a jwt token without an expiration date or one that gives you enough time. If you need to invalidate the token follow this link. Add the jwt on appsettings.json and read the token as follows:
appsettings.json
{
//...
"Worker" : "dotnet",
"Token": "eyJhbGciOiJIUzI1Ni...",
"ApiUrl": "http://localhost:3005",
//...
}
Worker.cs
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
public Worker(ILogger<Worker> _logger, IConfiguration _cfg)
{
logger = _logger;
//...
api = _cfg["ApiUrl"];
token = _cfg["Token"];
}
private async Task SendResult(SomeModel JobResult)
{
var content = new StringContent(JsonConvert.SerializeObject(JobResult), Encoding.UTF8, "application/json");
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
using (var response = await httpClient.PostAsync($"{api}/someController", content))
{
var result = await response.Content.ReadAsStringAsync();
var rs = JsonConvert.DeserializeObject(result);
Console.WriteLine($"API response {response.ReasonPhrase}");
}
}
}
Update:
If you need to control requests:
Startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
// ...
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = TokenValidation
};
});
private static Task TokenValidation(TokenValidatedContext context)
{
// your custom validation
var hash = someHashOfContext();
if (context.Principal.FindFirst(ClaimTypes.Hash).Value != hash)
{
context.Fail("You cannot access here");
}
return Task.CompletedTask;
}

How to implement the OAuth2 Auth_Code flow?

Purpose:
I work with a third party that has implemented an OAuth2 authorization code flow. The goal is for an authenticated user in my app to be able to access areas of a this third parties app without having to sign in to the third party. I have provided the third party with our authorization url, a clientId and a callback url (not sure how this is used).
I am trying to wire up a basic authorization code flow using OAuth2, but I am stuck on an invalid_grant error and am having trouble determining how the process is intended to work.
Here is what I have implemented thus far:
In the startup class I configure my OAuth server to include an AuthorizeEndpointPath and an AuthorizationCodeProvider:
var allowInsecureHttp = bool.Parse(ConfigurationManager.AppSettings["AllowInsecureHttp"]);
var oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = allowInsecureHttp,
TokenEndpointPath = new PathString("/oauth2/token"),
AuthorizeEndpointPath = new PathString("/oauth2/authorize"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new CustomOAuthProvider(HlGlobals.Kernel),
AccessTokenFormat = new CustomJwtFormat(_baseUrl, HlGlobals.Kernel),
AuthorizationCodeProvider = new SimpleAuthenticationTokenProvider()
};
The CustomOAuthProider contains the following overrides:
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
//We validated that Client Id and redirect Uri are what we expect
if (context.ClientId == "123456" && context.RedirectUri.Contains("localhost"))
{
context.Validated();
}
else
{
context.Rejected();
}
return Task.FromResult<object>(null);
}
public override Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
var ci = new ClaimsIdentity("Bearer");
context.OwinContext.Authentication.SignIn(ci);
context.RequestCompleted();
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
Guid clientIdGuid;
// Validate the context
context.Validated();
return Task.FromResult<object>(0);
}
Finally, the code in the SimpleAuthenticationTokenProvider is implemented as follows:
public SimpleAuthenticationTokenProvider() : base()
{
this.OnCreate = CreateCode;
this.OnReceive = ReceiveCode;
}
private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
public void CreateCode(AuthenticationTokenCreateContext context)
{
context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
_authenticationCodes[context.Token] = context.SerializeTicket();
}
public void ReceiveCode(AuthenticationTokenReceiveContext context)
{
string value;
_authenticationCodes.TryGetValue(context.Token, out value);
context.DeserializeTicket(value);
}
I have wired up a sample request inside of Postman. When I execute the postman oauth2 Authroization Code grant type the following methods from above fire sequentially:
ValidateClientRedirectUri
AuthorizeEndpoint
ValidateClientAuthentication
ReceiveCode
I am getting back an 'invalid_grant' error from postman, and no authorization code has been returned.
Can anyone point out where I may be going wrong here?
Secondly, how is the callbackurl/redirect url supposed to be used? Is it a fallback to the login page if the user has not authenticated?
UPDATE
I see that a 'code' is coming back in the querystring attached to the redirectURL on the response. Should the redirectURL be a page on the third-party site?
How is this code exchanged for a token?
UPDATE 2
Another question: At what phase do I read/parse the code? I can see the code coming in on the request querystring in my GrantClientCredentials method. Should I just parse it from the querystring and validate? At what point should ReceiveCode be called?

Azure Mobile App Authentication With Custom Role Claims - Claims Disappearing

We have an Azure Mobile App using social network authentication. Trying to add user roles as claims using a custom token handler.
This all works when running on localhost -- the tokens are added in the token handler and they are available when the AuthorizationAttribute OnAuthorization method is called. The Authorize Attribute with the Roles specified works as expected.
But when running is Azure -- the claims are added but when the OnAuthorization method is called the custom role claims are gone.
Here is the code:
Startup/Config Class
public class OwinStartup
{
public void Configuration(IAppBuilder app)
{
var config = GlobalConfiguration.Configuration;
new MobileAppConfiguration()
.AddPushNotifications()
.ApplyTo(config);
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().
GetMobileAppSettings();
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions()
{
SigningKey = ConfigurationManager.AppSettings["authSigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["authAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["authIssuer"] },
TokenHandler = new AppServiceTokenHandlerWithCustomClaims(config)
});
//Authenticate stage handler in OWIN Pipeline
app.Use((context, next) =>
{
return next.Invoke();
});
app.UseStageMarker(PipelineStage.Authenticate);
}
Token Handler that Adds the Role Claims
public class AppServiceTokenHandlerWithCustomClaims : AppServiceTokenHandler
{
public AppServiceTokenHandlerWithCustomClaims(HttpConfiguration config)
: base(config)
{
}
public override bool TryValidateLoginToken(
string token,
string signingKey,
IEnumerable<string> validAudiences,
IEnumerable<string> validIssuers,
out ClaimsPrincipal claimsPrincipal)
{
var validated = base.TryValidateLoginToken(token, signingKey, validAudiences, validIssuers, out claimsPrincipal);
if (validated)
{
string sid = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier).Value;
var roleProvider = UnityConfig.Container.Resolve<IRoleProvider>("RoleProvider");
var roles = roleProvider.GetUserRolesBySid(sid);
foreach (var role in roles)
{
((ClaimsIdentity)claimsPrincipal.Identity).AddClaim(new Claim(ClaimTypes.Role, role));
}
}
return validated;
}
}
Role Claim
An example of a role claim from the identity claims collection
{http://schemas.microsoft.com/ws/2008/06/identity/claims/role: admin}
Authorize Attribute on Web Api Controller
[Authorize(Roles = "admin")]
Every call to an endpoint that has an Authorize attribute with one or more roles specified fails (401)
Not sure what is going on with the claims either getting stripped off or not persisted in the Identity when running in Azure.
thanks
Michael
I've got a chapter on this in the book - https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter2/custom/#using-third-party-tokens
Note the bit about custom authentication with additional claims. You need to call a custom API with the original token, check the token for validity, then produce a new token (the zumo token) with the claims you want. You can then use those claims for anything that is required.
According to this blog post, some of your options may be wrong. The AppSettings are only set for local debugging, and won't work in Azure.
Try this:
public void Configuration(IAppBuilder app)
{
var config = GlobalConfiguration.Configuration;
new MobileAppConfiguration()
.AddPushNotifications()
.ApplyTo(config);
MobileAppSettingsDictionary settings = config
.GetMobileAppSettingsProvider()
.GetMobileAppSettings();
// Local Debugging
if (string.IsNullOrEmpty(settings.HostName))
{
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions()
{
SigningKey = ConfigurationManager.AppSettings["authSigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["authAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["authIssuer"] },
TokenHandler = new AppServiceTokenHandlerWithCustomClaims(config)
});
}
// Azure
else
{
var signingKey = GetSigningKey();
string hostName = GetHostName(settings);
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = signingKey,
ValidAudiences = new[] { hostName },
ValidIssuers = new[] { hostName },
TokenHandler = new AppServiceTokenHandlerWithCustomClaims(config)
});
}
//Authenticate stage handler in OWIN Pipeline
app.Use((context, next) =>
{
return next.Invoke();
});
app.UseStageMarker(PipelineStage.Authenticate);
}
private static string GetSigningKey()
{
// Check for the App Service Auth environment variable WEBSITE_AUTH_SIGNING_KEY,
// which holds the signing key on the server. If it's not there, check for a SigningKey
// app setting, which can be used for local debugging.
string key = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
if (string.IsNullOrWhiteSpace(key))
{
key = ConfigurationManager.AppSettings["SigningKey"];
}
return key;
}
private static string GetHostName(MobileAppSettingsDictionary settings)
{
return string.Format("https://{0}/", settings.HostName);
}

Get user claims before any page loads on external ADFS login

What I'm trying to do is accessing user claims which returns from ADFS login. ADFS returns username and with that username I have to run a query to another DB to get user information and store it. I don't really know where to do that and what the best practice is. I can access user claims in the view controller like:
public ActionResult Index()
{
var ctx = Request.GetOwinContext();
ClaimsPrincipal user = ctx.Authentication.User;
IEnumerable<Claim> claims = user.Claims;
return View();
}
But what I need to do is as I said access claims like in global.asax.cs or startup.cs to store user information before the application runs.
This is my Startup.Auth.cs file:
public partial class Startup
{
private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
}
We add an event handler to the WsFederationAuthenticationOptions value in our startup file.
This happens immediately after the security token has been validated.
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions()
{
MetadataAddress = MetadataAddress,
Wtrealm = Wtrealm,
Wreply = CallbackPath,
Notifications = new WsFederationAuthenticationNotifications()
{
SecurityTokenValidated = (ctx) =>
{
ClaimsIdentity identity = ctx.AuthenticationTicket.Identity;
DoSomethingWithLoggedInUser(identity);
}
}
};

Get IPrincipal from OAuth Bearer Token in OWIN

I have successfully added OAuth to my WebAPI 2 project using OWIN. I receive tokens and can use them in the HTTP Header to access resources.
Now I want to use those tokens also on other channels for authentication that are not the standard HTTP requests that the OWIN template is made for. For example, I am using WebSockets where the client has to send the OAuth Bearer Token to authenticate.
On the server side, I receive the token through the WebSocket. But how can I now put this token into the OWIN pipeline to extract the IPrincipal and ClientIdentifier from it? In the WebApi 2 template, all this is abstracted for me, so there is nothing I have to do to make it work.
So, basically, I have the token as a string and want to use OWIN to access the user information encoded in that token.
Thank you in advance for the help.
I found a part of the solution in this blog post: http://leastprivilege.com/2013/10/31/retrieving-bearer-tokens-from-alternative-locations-in-katanaowin/
So I created my own Provider as follows:
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var value = context.Request.Query.Get("access_token");
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
return Task.FromResult<object>(null);
}
}
Then I needed to add it to my App in Startup.Auth.cs like this:
OAuthBearerOptions = new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerProvider(),
AccessTokenProvider = new AuthenticationTokenProvider()
{
OnCreate = create,
OnReceive = receive
},
};
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
With a custom AuthenticationTokenProvider, I can retrieve all other values from the token early in the pipeline:
public static Action<AuthenticationTokenCreateContext> create = new Action<AuthenticationTokenCreateContext>(c =>
{
c.SetToken(c.SerializeTicket());
});
public static Action<AuthenticationTokenReceiveContext> receive = new Action<AuthenticationTokenReceiveContext>(c =>
{
c.DeserializeTicket(c.Token);
c.OwinContext.Environment["Properties"] = c.Ticket.Properties;
});
And now, for example in my WebSocket Hander, I can retrieve ClientId and others like this:
IOwinContext owinContext = context.GetOwinContext();
if (owinContext.Environment.ContainsKey("Properties"))
{
AuthenticationProperties properties = owinContext.Environment["Properties"] as AuthenticationProperties;
string clientId = properties.Dictionary["clientId"];
...
}
By default, OWIN use ASP.NET machine key data protection to protect the OAuth access token when hosted on IIS. You can use MachineKey class in System.Web.dll to unprotect the tokens.
public class MachineKeyProtector : IDataProtector
{
private readonly string[] _purpose =
{
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Access_Token",
"v1"
};
public byte[] Protect(byte[] userData)
{
throw new NotImplementedException();
}
public byte[] Unprotect(byte[] protectedData)
{
return System.Web.Security.MachineKey.Unprotect(protectedData, _purpose);
}
}
Then, construct a TicketDataFormat to get the AuthenticationTicket object where you can get the ClaimsIdentity and AuthenticationProperties.
var access_token="your token here";
var secureDataFormat = new TicketDataFormat(new MachineKeyProtector());
AuthenticationTicket ticket = secureDataFormat.Unprotect(access_token);
To unprotect other OAuth tokens, you just need to change the _purpose content. For detailed information, see OAuthAuthorizationServerMiddleware class here:
http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.OAuth/OAuthAuthorizationServerMiddleware.cs
if (Options.AuthorizationCodeFormat == null)
{
IDataProtector dataProtecter = app.CreateDataProtector(
typeof(OAuthAuthorizationServerMiddleware).FullName,
"Authentication_Code", "v1");
Options.AuthorizationCodeFormat = new TicketDataFormat(dataProtecter);
}
if (Options.AccessTokenFormat == null)
{
IDataProtector dataProtecter = app.CreateDataProtector(
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Access_Token", "v1");
Options.AccessTokenFormat = new TicketDataFormat(dataProtecter);
}
if (Options.RefreshTokenFormat == null)
{
IDataProtector dataProtecter = app.CreateDataProtector(
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Refresh_Token", "v1");
Options.RefreshTokenFormat = new TicketDataFormat(dataProtecter);
}
in addition to johnny-qian answer, using this method is better to create DataProtector. johnny-qian answer, depends on IIS and fails on self-hosted scenarios.
using Microsoft.Owin.Security.DataProtection;
var dataProtector = app.CreateDataProtector(new string[] {
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Access_Token",
"v1"
});
What is your token like, is it an encrypt string or a formatted string, what is it format?
I my code:
public static Action<AuthenticationTokenReceiveContext> receive = new Action<AuthenticationTokenReceiveContext>(c =>
{
if (!string.IsNullOrEmpty(c.Token))
{
c.DeserializeTicket(c.Token);
//c.OwinContext.Environment["Properties"] = c.Ticket.Properties;
}
});
The c.Ticket is always null.

Categories

Resources