I've seen numerous examples of how to use JWT authentication with Angular, React, Vue etc... clients but can't find any examples of using JWT authentication with ASP.NET Core (specifically 2.2) Web App Mvc.
Does anyone have any examples or advice on how to do this?
Thanks,
You can use this class based on nuget package JWT 3.0.3
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Newtonsoft.Json;
using System;
namespace Common.Utils
{
public class JwtToken
{
private IJwtEncoder encoder;
private IJwtDecoder decoder;
/// <remarks>
/// This requires a key value randomly generated and stored in your configuration settings.
/// Consider that it is a good practice use keys as at least long as the output digest bytes
/// length produced by the hashing algorithm used. Since we use an HMAC-SHA-512 algorithm,
/// then we can provide it a key at least 64 bytes long.
/// <see cref="https://tools.ietf.org/html/rfc4868#page-7"/>
/// </remarks>
public string SecretKey { get; set; }
public JwtToken()
{
IJwtAlgorithm algorithm = new HMACSHA512Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider datetimeProvider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, datetimeProvider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
decoder = new JwtDecoder(serializer, validator, urlEncoder);
SecretKey = "";
}
public JwtToken(string secretKey) : this()
{
SecretKey = secretKey;
}
public bool IsTokenValid(string token)
{
return !string.IsNullOrWhiteSpace(DecodeToken(token));
}
public string GetToken(object payload)
{
try
{
return encoder.Encode(payload, SecretKey);
}
catch (Exception)
{
return encoder.Encode(new DataModel(payload), SecretKey);
}
}
public string DecodeToken(string token)
{
try
{
if (string.IsNullOrWhiteSpace(token) || token == "null")
{
return null;
}
return decoder.Decode(token, SecretKey, true);
}
catch (TokenExpiredException)
{
return null;
}
catch (SignatureVerificationException)
{
return null;
}
}
public T DecodeToken<T>(string token) where T : class
{
try
{
if (string.IsNullOrWhiteSpace(token))
{
return null;
}
return decoder.DecodeToObject<T>(token, SecretKey, true);
}
catch (TokenExpiredException)
{
return null;
}
catch (SignatureVerificationException)
{
return null;
}
catch (Exception)
{
var data = decoder.DecodeToObject<DataModel>(token, SecretKey, true).Data;
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(data));
}
}
}
public class DataModel
{
public DataModel(object data)
{
Data = data;
}
public object Data { get; set; }
}
}
Then in your Startup class Configure method set the jwt middleware
for check authentication status of each request:
app.Use((context, next) =>
{
// verify app access token if not another service call
var appAccessToken = context.Request.Headers["Authorization"];
if (appAccessToken.Count == 0)
{
context.Items["User"] = null;
}
else
{
var token = appAccessToken.ToString().Replace("Bearer ", "");
var jwtToken = new JwtToken(config.JwtTokenSecret); //you need a secret (with requirements specified above) in your configuration (db, appsettings.json)
if (string.IsNullOrWhiteSpace(token) || !jwtToken.IsTokenValid(token))
{
context.Response.StatusCode = 401;
return Task.FromResult(0);
}
dynamic user = jwtToken.DecodeToken<dynamic>(token);
var cachedToken = cache.Get(user.Id); //you need some cache for store your token after login success and so can check against
if (cachedToken == null || cachedToken.ToString() != token)
{
context.Response.StatusCode = 401;
return Task.FromResult(0);
}
context.Items["User"] = new Dictionary<string, string>() {
{ "FullName",user.Name?.ToString()},
{ "FirstName",user.FirstName?.ToString()},
{ "LastName",user.LastName?.ToString()},
{ "Role",user.Role?.ToString()},
{ "Email",user.Email?.ToString()}
};
}
return next();
});
And finally you need generate the token and return it after
authentication:
[AllowAnonymous]
public IActionResult Login(string username, string password)
{
User user = null; //you need some User class with the structure of the previous dictionary
if (checkAuthenticationOK(username, password, out user)) //chackAuthenticationOk sets the user against db data after a succesfull authentication
{
var token = new JwtToken(_config.JwtTokenSecret).GetToken(user); //_config is an object to your configuration
_cache.Set(user.id, token); //store in the cache the token for checking in each request
return Ok(token);
}
return StatusCode(401, "User is not authorized");
}
Add following code to startup
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Issuer"],
ValidAudience = Configuration["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SigningKey"]))
};
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,, ILoggerFactory loggerFactory)
{
app.UseAuthentication();
}
Code for login action in AccountController
[Route("api/[controller]")]
public class AccountController : Controller
{
[AllowAnonymous]
[HttpPost]
[Route("login")]
public IActionResult Login([FromBody]LoginViewModel loginViewModel)
{
if (ModelState.IsValid)
{
var user = _userService.Authenticate(loginViewModel);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, loginViewModel.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken
(
issuer: _configuration["Issuer"],
audience: _configuration["Audience"],
claims: claims,
expires: DateTime.UtcNow.AddDays(10),
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])),
SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
access_token = new JwtSecurityTokenHandler().WriteToken(token),
expires_in = (int)token.ValidTo.Subtract(DateTime.UtcNow).TotalSeconds,// TimeSpan.FromTicks( token.ValidTo.Ticks).TotalSeconds,
sub = loginViewModel.Username,
name = loginViewModel.Username,
fullName = user.FullName,
jobtitle = string.Empty,
phone = string.Empty,
email = user.EmailName,
});
}
}
}
I assume you have implemented JWT on the server side. To handle this on client side, first you have to add token to web browser local storage. Add to your main layout javascript (let's named it AuthService.js)
below code adds token to local storage after login button clicked. gettokenfromlocalstorage() retrieve token from local storage.
<script>
var token = "";
function Loginclick() {
var form = document.querySelector('form');
var data = new FormData(form);
var authsevice = new AuthService();
authsevice.LogIn(data.get("username").toString(), data.get("password").toString());
}
function gettokenfromlocalstorage() {
var authserv = new AuthService();
var mytoken = authserv.getAuth();
authserv.LogOut();
}
var AuthService = /** #class */ (function () {
function AuthService() {
this.authKey = "auth";
}
AuthService.prototype.LogIn = function (username, password) {
this.username = username;
this.password = password;
this.grant_type = "password";
this.client_id = "MyClientId";
var loginurl = "/api/Token/Auth";
var xhr = new XMLHttpRequest();
xhr.open("POST", loginurl, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(this));
xhr.onreadystatechange = function () {
console.log("onreadystatechange");
};
xhr.onerror = function () {
var aaa = this.responseText;
};
xhr.onload = function () {
var data = JSON.parse(this.responseText);
var auth = new AuthService();
auth.setAuth(data);
};
};
AuthService.prototype.LogOut = function () {
this.setAuth(null);
return true;
};
AuthService.prototype.setAuth = function (auth) {
if (auth) {
localStorage.setItem(this.authKey, JSON.stringify(auth));
}
else {
localStorage.removeItem(this.authKey);
}
return true;
};
AuthService.prototype.getAuth = function () {
var i = localStorage.getItem(this.authKey);
return i === null ? null : JSON.parse(i);
};
AuthService.prototype.isLoggedIn = function () {
return localStorage.getItem(this.authKey) !== null ? true : false;
};
return AuthService;
}());
var aa = new AuthService();
var gettoken = aa.getAuth();
if (gettoken !== null) {
token = gettoken.token;
}
</script>
To add token to the header of each anchor tag put below script also to
your main layout.
<script>
var links = $('a');
for (var i = 0; i < links.length; i++) {
links[i].onclick = function check() {
addheader(this.href);
return false;
}
}
function addheader(object) {
let xhr = new XMLHttpRequest();
xhr.open("GET", object, true);
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.send(null);
xhr.onload = function () {
window.history.pushState("/", "", xhr.responseURL);
//mycontainer is a div for parialview content
$("#mycontainer").html(xhr.responseText);
window.onpopstate = function (e) {
if (e.state) {
$("html").html = e.state;
document.title = e.state.pageTitle;
}
};
};
}
</script>
Remember that using of this approach, each view has to be loaded as a partial view.
If you insert url address in a web browser bar directly this solution doesn't work. I haven't figured it out yet. That's why to manage token authentication is better using single page application, not multipage application.
You can use this boilerplate to understand how to implement JWT tokenization with .Net Core. In the project you can find JWT, Swagger and EF features.
Related
I have an app that uses Oidc to login. This works fine in most cases. But there is one iphone 8+ running iOS 15.6.1 that keeps downloading the return url from the login request.
i use Xamarin forms v 5.0.0.2515, Xamarin.Essentials 1.7.3, Xamarin.CommunityToolkit 2.0.5
IdentityModel.OidcClient 3.1.2
The problem is that it works on all other devices and i can't replicate it in a dev environment.
public class WebauthenticatorBrowser : IBrowser
{
private readonly string callbackUrl;
public WebauthenticatorBrowser(string? callbackUrl = null)
{
this.callbackUrl = callbackUrl;
}
public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
{
try
{
var callBack = string.IsNullOrWhiteSpace(callbackUrl) ? options.EndUrl : callbackUrl;
var authResult = await WebAuthenticator.AuthenticateAsync(new WebAuthenticatorOptions
{
Url = new Uri(options.StartUrl),
CallbackUrl = new Uri(callBack),
PrefersEphemeralWebBrowserSession = false
});
var authorizeResponse = ToRawIdentityUrl(options.EndUrl, authResult);
return new BrowserResult()
{
Response = authorizeResponse
};
}
catch (Exception ex)
{
return new BrowserResult()
{
Error = ex.Message
};
}
}
private string ToRawIdentityUrl(string redirectUrl, WebAuthenticatorResult result)
{
IEnumerable<string> parameters = result.Properties.Select(x => $"{x.Key}={x.Value}");
var values = string.Join("&", parameters);
return $"{redirectUrl}#{values}" ;
}
}
In my oidc class where i create my client
private async Task createOidcClient() {
document = await httpClient.GetDiscoveryDocumentAsync(idsUrl);
if (document.IsError) {
alertPresenter.Alert("Connection issue", "please try again");
return;
}
var options = new OidcClientOptions {
Authority = document.Issuer,
ClientId = "---clientID-",
Scope = "-- all the scopes --",
RedirectUri = "-- callback url--",
PostLogoutRedirectUri = " -- callback url--",
Browser = new WebauthenticatorBrowser(),
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect
};
client = new OidcClient(options);
}
the login call in the OicdClass
public async Task<LoginResult> Login() {
await createOidcClient();
var result = await client.LoginAsync(new LoginRequest());
if (result.IsError) return result;
setAuthHeader(result.AccessToken);
await accessTokenUpdater.SetAllTokens(result.AccessToken, result.RefreshToken, result.IdentityToken);
return result;
}
Following this guide (https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-asp.net-mvc) I made the code work in localhost but it gives an Error 400: invalid_request device_id and device_name are required for private IP: http://xx.xx.xx.xx:xxxxx/AuthCallback/IndexAsync while redirecting to Authorization page. We added subdomain in dns for our windows server ip address and registered the same subdomain in google api console. Why google gets ip address of the server instead of subdomain? Is the problem related to dns?
These are the working codes on local machine:
HomeController.cs
public class HomeController : Controller
{
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var service = new GmailService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "BPM Mail"
});
var gmailProfile = service.Users.GetProfile("me").Execute();
var userGmailEmail = gmailProfile.EmailAddress;
Utils.userMail = userGmailEmail;
Utils.cred = result.Credential;
// SAMPLE CODE:
//var list = await service.Files.List().ExecuteAsync();
//ViewBag.Message = "FILE COUNT IS: " + list.Items.Count();
return new RedirectResult("~/Apps/Mail/Default.aspx");
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
}
AppFlowMetadata.cs
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "ClientId ",
ClientSecret = "ClientSecret "
},
Scopes = new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.MailGoogleCom, GmailService.Scope.GmailModify },
DataStore = new FileDataStore("D:/bpm_mail/mytokens/token.json", true)
});
public override string GetUserId(Controller controller)
{
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
var user = controller.Session["user"];
//Utils.userId = user.ToString();
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
//public override string AuthCallback
//{
// get { return #"/AuthCallback/IndexAsync"; }
//}
}
AuthCallbackController.cs
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
Issue solved by changing private ip to public one and redirect it to domain.
My application first uses cookie auth to login. I then have it calling an API which I would like to use JWT auth which opens a websocket stream (Im debating and putting this stream on the UI) when the API gets an update it calls the UI and I want this one call to be a JWT Auth, I tried a bunch of things this past weekend but I keep getting invalid signature or SSL cert is invalid.
Both projects are .Net Core. One is a web api and the other is a MVC project
Its actually 2 APIS and MVC (userAPI, TraderAPI and the UI)
User API
public string GenerateJWTToken(UserRoleVM user)
{
if (user != null)
{
var secretKey = Base64UrlEncoder.DecodeBytes(_config.GetSection("Tokens:Key").ToString());
var securityKey = new SymmetricSecurityKey(secretKey);
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
var currentTime = DateTime.Now;
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName)
,new Claim(JwtRegisteredClaimNames.Sid, user.UserGUID)
,new Claim(JwtRegisteredClaimNames.NameId, user.UserID.ToString())
,new Claim(JwtRegisteredClaimNames.Email, user.Email)
,new Claim(JwtRegisteredClaimNames.AuthTime, currentTime.ToString())
,new Claim(ClaimTypes.Role, user.Role)
//,new Claim(JwtRegisteredClaimNames.)
,new Claim(type: "Subscribed", value: user.IsSubscribed.ToString())
,new Claim(type: "SubscribedDate", value: user.DateSubscribed.ToString())
};
var jwtSecurityToken = new JwtSecurityToken
(
signingCredentials: signingCredentials,
audience: _config.GetValue<string>("Tokens:Audience"),
issuer: _config.GetValue<string>("Tokens:Issuer"),
expires: currentTime.AddMinutes(_config.GetValue<int>("Tokens:LifeTime")),
//notBefore: currentTime,
claims: claims
);
return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}
return null;
}
//public Task<LoginResponseVM> Login(string username, string password)
public async Task<string> Login(string username, string password)
{
string jwtToken = null;
if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password))
{
try
{
var result = await _signInManager.PasswordSignInAsync(username, password, false, false);
if (result.Succeeded)
{
//var savedUser = await _userService.GetUserByNameAsync(username);
//var role = await _userService.GetUserRolesAsync(savedUser);
var userVM = await _userService.GetUserNRole2Async(username);
//jwtToken = Convert.ToBase64String(Encoding.ASCII.GetBytes(GenerateJWTToken(userVM)));
jwtToken = GenerateJWTToken(userVM);
}
}
catch (Exception ex)
{
}
}
return jwtToken;
}
TraderAPI
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpPost]
//public async Task<IActionResult> GetTradeStatusUpdate([FromBody] TradeResultVM model)
public async Task<IActionResult> SendTradeStatusUpdate([FromBody] TradeResultVM model)
{
_logger.LogInformation("Inside GetTradeStatus");
var lstTradeStatuses = await GetLstTradeStatuses();
if (lstTradeStatuses != null)
{
await _semaphoreSlimS.WaitAsync();
try
{
foreach (var activeTrade in lstTradeStatuses)
{
if (activeTrade.TradeID == model.TradeID)
{
_logger.LogInformation("Trade found in lstTradeStatuses");
...
}
[HttpPost("SendUITradeStatusV4")]
public async Task<IActionResult> SendUITradeStatusV4(BinanceStreamOrderUpdate orderUpdate, string token)
{
if (orderUpdate != null)
{
TradeResultVM tradeVM = new TradeResultVM
{
//TradeName = "Test",
TradeID = orderUpdate.OrderId,
TradeSymbol = orderUpdate.Symbol,
Price = orderUpdate.Price,
Quantity = orderUpdate.Quantity
};
string trade = JsonSerializer.Serialize(tradeVM);//
using var client = _httpClient.CreateClient("TraderUI");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
//client.BaseAddress = new Uri("https://localhost:50111");
//using var response = await client.PostAsync($"/HarkinsTrader/GetTradeStatusUpdate/",
_logger.LogInformation("Sending Key");
using var response = await client.PostAsync($"/HarkinsTrader/SendTradeStatusUpdate/",
new StringContent(trade, Encoding.UTF8, "application/json"));
if (response.IsSuccessStatusCode)
{
_logger.LogInformation("Success StreamStatus Update");
return Ok();
}
}
return BadRequest();
}
I registered a web API https://dev.office.com/app-registration. I kept sign URL
localhost:8000 and RedirectURI as localhost:8000.
After this, I created a console application to access the graph API. Here is the code. But it doesnt work. I get the error "access denied".
Am I missing anything here?
One more thing, I checked the access_token and I dont see any user information there.
string clientId = "<>";
string clientsecret = "<>";
string tenant = "my.com";
var authUri = "https://login.microsoftonline.com/" + tenant + "/oauth2/token";
var RESOURCE_URL = "https://graph.microsoft.com";
HttpClient client = new HttpClient();
var authContext = new AuthenticationContext(authUri);
var credential = new ClientCredential(clientId: clientId, clientSecret: clientsecret);
var result = authContext.AcquireTokenAsync(RESOURCE_URL, credential).Result;
Console.WriteLine(result.AccessToken.ToString());
client.DefaultRequestHeaders.Add("Authorization", "bearer " + result.AccessToken);
var httpContent = new StringContent(content, Encoding.GetEncoding("utf-8"), "application/json");
var response = client.GetAsync("https://graph.microsoft.com/v1.0/users/ankushb/calendar/events").Result;
// var response = client.PostAsync("https://graph.microsoft.com/v1.0/groups", httpContent).Result;
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
There are two basic authentication flow for OAuth2 that Azure AD use.
First is authorization code grant flow which used to access to web APIs by native clients and websites.In this flow, the user delegates access to a client application.
Second is Client Credentials grant flow which permits a web service (a confidential client) to use its own credentials to authenticate when calling another web service, instead of impersonating a user. In this scenario, the client is typically a middle-tier web service, a daemon service, or web site.
The code above is using the Client Credentials grant flow, that's the reason there is no user information.
In your scenario, you should register an native app(console application is not confidential clients which should not use the secret to request for the token ) and use the authorization code grant flow to enable users sign-in with their account. And to use this flow in a console application, we need to prompt a web dialog navigate to the login-in page and get the auth code to request for the access token.
Here is an sample to authenticate the app with authorization code grant flow:
var oauth = new OauthConfiguration
{
Authority = "https://login.microsoftonline.com",
Tenant = "common",
ClientId = "{clientId}",
RedirectURI = "{redirectURL}",
Secret = ""
};
var tokenResponse = new OAuth2.OauthWebAuthHelper(oauth).AcquireTokenWithResource("https://graph.microsoft.com");
var accessToken = tokenResponse.GetValue("access_token").Value<string>();
var refreshToken = tokenResponse.GetValue("refresh_token").Value<string>();
namespace OAuth2
{
public class OauthWebAuthHelper
{
public enum Version
{
V1 = 1,
V2 = 2
}
private OauthConfiguration _configuration;
private const string OAUTH2_AUTHORIZE_V1_SUFFIX = #"oauth2/";
private const string OAUTH2_AUTHORIZE_V2_SUFFIX = #"oauth2/v2.0";
private string _authorizeSuffix;
public OauthWebAuthHelper(OauthConfiguration configuration, Version version = Version.V1)
{
_configuration = configuration;
switch (version)
{
case Version.V1: _authorizeSuffix = OAUTH2_AUTHORIZE_V1_SUFFIX; break;
case Version.V2: _authorizeSuffix = OAUTH2_AUTHORIZE_V2_SUFFIX; break;
}
}
public void LogOut()
{
var dialog = new WebBrowserDialog();
dialog.Open(string.Format("{0}/logout", EndPointUrl));
}
protected string EndPointUrl
{
get
{
return string.Format("{0}/{1}/{2}", _configuration.Authority, _configuration.Tenant, _authorizeSuffix);
}
}
public JObject GetAuthorizationCode()
{
JObject response = new JObject();
var parameters = new Dictionary<string, string>
{
{ "response_type", "code" },
{ "client_id", _configuration.ClientId },
{ "redirect_uri", _configuration.RedirectURI },
{ "prompt", "login"}
};
var requestUrl = string.Format("{0}/authorize?{1}", EndPointUrl, BuildQueryString(parameters));
var dialog = new WebBrowserDialog();
dialog.OnNavigated((sender, arg) =>
{
if (arg.Url.AbsoluteUri.StartsWith(_configuration.RedirectURI))
{
var collection = HttpUtility.ParseQueryString(arg.Url.Query);
foreach (var key in collection.AllKeys)
{
response.Add(key, collection[key]);
}
dialog.Close();
}
});
dialog.Open(requestUrl);
return response;
}
public JObject GetAuthorizationCode(string scope)
{
JObject response = new JObject();
var parameters = new Dictionary<string, string>
{
{ "response_type", "code" },
{ "client_id", _configuration.ClientId },
{ "redirect_uri", _configuration.RedirectURI },
{ "prompt", "login"},
{ "scope", scope}
};
var requestUrl = string.Format("{0}/authorize?{1}", EndPointUrl, BuildQueryString(parameters));
var dialog = new WebBrowserDialog();
dialog.OnNavigated((sender, arg) =>
{
if (arg.Url.AbsoluteUri.StartsWith(_configuration.RedirectURI))
{
var collection = HttpUtility.ParseQueryString(arg.Url.Query);
foreach (var key in collection.AllKeys)
{
response.Add(key, collection[key]);
}
dialog.Close();
}
});
dialog.Open(requestUrl);
return response;
}
public JObject AcquireTokenWithResource(string resource)
{
var codeResponse = GetAuthorizationCode();
var code = codeResponse.GetValue("code").Value<string>();
var parameters = new Dictionary<string, string>
{
{ "resource", resource},
{ "client_id", _configuration.ClientId },
{ "code", code},
{ "grant_type", "authorization_code" },
{ "redirect_uri", _configuration.RedirectURI},
{ "client_secret",_configuration.Secret}
};
var client = new HttpClient();
var content = new StringContent(BuildQueryString(parameters), Encoding.GetEncoding("utf-8"), "application/x-www-form-urlencoded");
var url = string.Format("{0}/token", EndPointUrl);
var response = client.PostAsync(url, content).Result;
var text = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject(text) as JObject;
}
public JObject RefreshTokenWithResource(string refreshToken)
{
var parameters = new Dictionary<string, string>
{
{ "client_id", _configuration.ClientId },
{ "refresh_token", refreshToken},
{ "grant_type", "refresh_token" }
};
var client = new HttpClient();
var content = new StringContent(BuildQueryString(parameters), Encoding.GetEncoding("utf-8"), "application/x-www-form-urlencoded");
var url = string.Format("{0}/token", EndPointUrl);
var response = client.PostAsync(url, content).Result;
var text = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject(text) as JObject;
}
public JObject AcquireTokenWithScope(string scope)
{
var codeResponse = GetAuthorizationCode(scope);
var code = codeResponse.GetValue("code").Value<string>();
var parameters = new Dictionary<string, string>
{
{ "client_id", _configuration.ClientId },
{ "code", code},
{ "grant_type", "authorization_code" },
{ "redirect_uri", _configuration.RedirectURI},
};
var client = new HttpClient();
var content = new StringContent(BuildQueryString(parameters), Encoding.GetEncoding("utf-8"), "application/x-www-form-urlencoded");
var url = string.Format("{0}/token", EndPointUrl);
var response = client.PostAsync(url, content).Result;
var text = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject(text) as JObject;
}
private string BuildQueryString(IDictionary<string, string> parameters)
{
var list = new List<string>();
foreach (var parameter in parameters)
{
if (!string.IsNullOrEmpty(parameter.Value))
list.Add(string.Format("{0}={1}", parameter.Key, HttpUtility.UrlEncode(parameter.Value)));
}
return string.Join("&", list);
}
}
public class OauthConfiguration
{
public string Authority { get; set; }
public string Tenant { get; set; }
public string ClientId { get; set; }
public string RedirectURI { get; set; }
public string Secret { get; set; }
}
public class WebBrowserDialog
{
private const int DEFAULT_WIDTH = 400;
private const int DEFAULT_HEIGHT = 500;
private Form _displayLoginForm;
private string _title;
private WebBrowser _browser;
private WebBrowserNavigatedEventHandler _webBrowserNavigatedEventHandler;
public WebBrowserDialog()
{
_title = "OAuth Basic";
_browser = new WebBrowser();
_browser.Width = DEFAULT_WIDTH;
_browser.Height = DEFAULT_HEIGHT;
_browser.Navigated += WebBrowserNavigatedEventHandler;
_displayLoginForm = new Form();
_displayLoginForm.SuspendLayout();
_displayLoginForm.Width = DEFAULT_WIDTH;
_displayLoginForm.Height = DEFAULT_HEIGHT;
_displayLoginForm.Text = _title;
_displayLoginForm.Controls.Add(_browser);
_displayLoginForm.ResumeLayout(false);
}
public void OnNavigated(WebBrowserNavigatedEventHandler handler)
{
_webBrowserNavigatedEventHandler = handler;
}
protected void WebBrowserNavigatedEventHandler(object sender, WebBrowserNavigatedEventArgs e)
{
if(_webBrowserNavigatedEventHandler != null)
{
_webBrowserNavigatedEventHandler.Invoke(sender, e);
}
}
public void Open(string url)
{
_browser.Navigate(url);
_displayLoginForm.ShowDialog();
}
public void Close()
{
_displayLoginForm.Close();
}
}
}
And you can get more samples about Microsoft Graph from here.
I may be over-complicating things, but we have an internal ASP.NET MVC5 SPA with AngularJS using Windows Authentication. This application has a SQL back-end database that has a table of users, containing their account names, and their respective roles in the application. We will be making calls to another Web API application that also has Windows Authentication enabled.
I have tried to do research on how to handle authorization using OWIN but couldn't find any specific examples regarding OWIN and Windows Authentication. Everything that turns up uses forms authentication with a username and password.
How can I go about using OWIN and Windows Auth for my app? Here's a sample of my OAuthAuthorizationServerProvider class.
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var container = UnityHelper.GetContainerInstance("***");
var securityHelper = container.Resolve<ISecurityHelper>();
User currentUser = securityHelper.GetCurrentUser(); // Validates user based on HttpContext.Current.User
if (currentUser == null)
{
context.SetError("invalid_grant", "The user could not be found.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", currentUser.AccountName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
UPDATE:
Oops, I forgot to include more information on what we'd like to accomplish. If possible, we'd like to use bearer authentication tickets so we don't have to look up the user and their roles everytime we make a call to a web api method.
UPDATE 2:
As requested by Andrew, below is the TLDR version of my _securityHelper class, specifically the GetCurrentUser() method. You'll notice that I'm attempting to call:
HttpContext.Current.GetOwinContext().Request.User.Identity.Name
This always returns null for User.
public class SecurityHelper : ISecurityHelper
{
private readonly ISecurityGroupController _securityGroupController;
private readonly IUserController _userController;
private readonly IEmployeeController _employeeController;
private readonly IFieldPermissionController _fieldPermissionController;
private readonly IOACController _oacController;
public SecurityHelper(ISecurityGroupController securityGroupController,
IUserController userController,
IEmployeeController employeeController,
IFieldPermissionController fieldPermissionController,
IOACController oacController)
{
_securityGroupController = securityGroupController;
_userController = userController;
_employeeController = employeeController;
_fieldPermissionController = fieldPermissionController;
_oacController = oacController;
}
// ... other methods
public User GetCurrentUser()
{
User user = _userController.GetByAccountName(HttpContext.Current.GetOwinContext().Request.User.Identity.Name);
if (user != null)
{
List<OAC> memberships = _oacController.GetMemberships(user.SourceId).ToList();
if (IsTestModeEnabled() && ((user.OACMemberships != null && user.OACMemberships.Count == 0) || user.OACMemberships == null))
{
user.OACMemberships = memberships;
}
else if (!IsTestModeEnabled())
{
user.OACMemberships = memberships;
}
}
return user;
}
}
This article series would be a good place to start: http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
of note, would be the following code, which essentially stores the bearer token in local storage and attaches it to the headers. There is obviously a lot more to it than this, including the forms and the actual server authentication system, but this should give you a decent start.
server component:
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
//Rest of code is here;
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
and the following client side code:
'use strict';
app.factory('authService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {
var serviceBase = 'http://ngauthenticationapi.azurewebsites.net/';
var authServiceFactory = {};
var _authentication = {
isAuth: false,
userName : ""
};
var _saveRegistration = function (registration) {
_logOut();
return $http.post(serviceBase + 'api/account/register', registration).then(function (response) {
return response;
});
};
var _login = function (loginData) {
var data = "grant_type=password&username=" + loginData.userName + "&password=" + loginData.password;
var deferred = $q.defer();
$http.post(serviceBase + 'token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).success(function (response) {
localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName });
_authentication.isAuth = true;
_authentication.userName = loginData.userName;
deferred.resolve(response);
}).error(function (err, status) {
_logOut();
deferred.reject(err);
});
return deferred.promise;
};
var _logOut = function () {
localStorageService.remove('authorizationData');
_authentication.isAuth = false;
_authentication.userName = "";
};
var _fillAuthData = function () {
var authData = localStorageService.get('authorizationData');
if (authData)
{
_authentication.isAuth = true;
_authentication.userName = authData.userName;
}
}
authServiceFactory.saveRegistration = _saveRegistration;
authServiceFactory.login = _login;
authServiceFactory.logOut = _logOut;
authServiceFactory.fillAuthData = _fillAuthData;
authServiceFactory.authentication = _authentication;
return authServiceFactory;
}]);
along with
'use strict';
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
}
var _responseError = function (rejection) {
if (rejection.status === 401) {
$location.path('/login');
}
return $q.reject(rejection);
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
return authInterceptorServiceFactory;
}]);
Check out this article for steps on enabling Windows Authentication in OWIN:
http://www.asp.net/aspnet/overview/owin-and-katana/enabling-windows-authentication-in-katana
From the article:
Katana does not currently provide OWIN middleware for Windows Authentication, because this functionality is already available in the servers.
The linked article covers enabling Windows Authentication for development. For deployments, these settings are in IIS under Authentication. Users will be prompted for their username and password by the browser when they first arrive on your application's page.