context.Request.User is null in OWIN OAuthAuthorizationServerProvider - iis

I'm trying to implement OAuth using OWIN for a Web API v2 endpoint on my local intranet. The API is hosted in IIS using built-in Windows Authentication. In short, this is what I want to happen.
When I ask for my Token at /token
Pull the WindowsPrincipal out of the OWIN context
Use the SID from the WindowsPrincipal to look up some roles for this
user in a SQL table.
Create a new ClaimsIdentity that stores the username and roles
Turn that into a Json Web Token (JWT) that I sent bak
When I request a resource from my API using my token
Convert the JWT Bearer token back to the ClaimsIdentity
Use that ClaimsIdentity for authorizing requests to the resource by
role
This way I don't have to do a database lookup for user roles on each
request. It's just baked into the JWT.
I think I'm setting everything up correctly. My Startup.Configuration method looks like this.
public void Configuration(IAppBuilder app)
{
// token generation
// This is what drives the action when a client connects to the /token route
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
// for demo purposes
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(8),
AccessTokenFormat = GetMyJwtTokenFormat(),
Provider = new MyAuthorizationServerProvider()
});
//// token consumption
app.UseOAuthBearerAuthentication(
new OAuthBearerAuthenticationOptions()
{
Realm = "http://www.ccl.org",
Provider = new OAuthBearerAuthenticationProvider(),
AccessTokenFormat = GetMyJwtTokenFormat()
}
);
app.UseWebApi(WebApiConfig.Register());
}
MyAuthorizationServerProvider looks like this...
public class MyAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Since I'm hosting in IIS with Windows Auth enabled
// I'm expecting my WindowsPrincipal to be here, but it's null :(
var windowsPrincipal = context.OwinContext.Request.User.Identity;
// windowsPrincipal is null here. Why?
// Call SQL to get roles for this user
// create the identity with the roles
var id = new ClaimsIdentity(stuff, more stuff);
context.Validated(id);
}
}
My problem is that context.Request.User is null here. I can't get to my WindowsPrincipal. If I create some other dummy middleware, I can get to the WindowsPrincipal without issue. Why is it null in this context? Am I doing something wrong?

Swap the order of UseOAuthAuthorizationServer and UseOAuthBearerAuthentication. UseOAuthBearerAuthentication calls UseStageMarker(PipelineStage.Authenticate); to make it (and everything before it) run earlier in the ASP.NET pipeline. User is null when you run during the Authenticate stage.

Related

Azure Functions app + Auth0 provider, getting 401 when calling API with auth token

I have read, and implemented local dev projects to match, Auth0's Complete Guide To React User Authentication with Auth0, successfully. I am confident in the implementation, given that all aspects of login and route protection are working correctly, as well as the local express server successfully authenticating API calls that use authentication tokens generated via the Auth0 React SDK.
I have added third button to the sample project's external-apis.js view for use in calling another API that I am trying to integrate with, which is an Azure Functions app. I would like to use Auth0 for this API in the same way I do for the express server, and take advantage of Azure's "Easy Auth" capabilities, as discussed in this MS doc. I have implemented an OpenID Connect provider, which points to my Auth0 application, in my Azure Function app per this MS doc.
This is what the function that calls this Azure Function app API looks like:
const callAzureApi = async () => {
try {
const token = await getAccessTokenSilently();
await fetch(
'https://example.azurewebsites.net/api/ExampleEndPoint',
{
method: 'GET',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${token}`,
},
}
)
.then((response) => response.json())
.then((response) => {
setMessage(JSON.stringify(response));
})
.catch((error) => {
setMessage(error.message);
});
} catch (error) {
setMessage(error.message);
}
};
My issue is that making calls to this Azure Function app API always returns a 401 (Unuthorized) response, even though the authorization token is being sent. If I change the Authorization settings in the Azure portal to not require authentication, then the code correctly retrieves the data, so I'm confident that the code is correct.
But, is there something else I have missed in my setup in order to use Auth0 as my authentication provider for the backend in Azure?
Through continued documentation and blog reading, I was able to determine what was missing from my original implementation. In short, I was expecting a little too much after reading about tge "Easy Auth" features of Azure, at least when using an OpenID Connect provider like Auth0. Specifically, the validation of the JSON Web Token (JWT) does not come for free, and needed further implementation.
My app is using the React Auth0 SDK to sign the user in to the identity provider and get an authorization token to send in its API requests. The Azure documentation for client-directed sign-in flow discusses the ability to validate a JWT using a specific POST call to the auth endpoint with the JWT in the header, but even this feature seems out of reach here, given that OpenID Connect is not listed in the provider list, and my attempts at trying it anyway continued to yield nothing but 401s.
The answer, then, was to implement the JWT validation directly into the Azure function itself, and return the proper response only when the JWT in the request header can be validated. I would like to credit blog posts of Boris Wilhelm and Ben Chartrand for helping to get to this final understanding of how to properly use Auth0 for an Azure Functions backend API.
I created the following Security object to perform the token validation. The static nature of the ConfigurationManager is important for caching the configuration to reduce HTTP requests to the provider. (My Azure Functions project is written in C#, as opposed to the React JS front-end app.)
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
namespace ExampleProject.Common {
public static class Security {
private static readonly IConfigurationManager<OpenIdConnectConfiguration> _configurationManager;
private static readonly string ISSUER = Environment.GetEnvironmentVariable("Auth0Url", EnvironmentVariableTarget.Process);
private static readonly string AUDIENCE = Environment.GetEnvironmentVariable("Auth0Audience", EnvironmentVariableTarget.Process);
static Security()
{
var documentRetriever = new HttpDocumentRetriever {RequireHttps = ISSUER.StartsWith("https://")};
_configurationManager = new ConfigurationManager<OpenIdConnectConfiguration> (
$"{ISSUER}.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever(),
documentRetriever
);
}
public static async Task<ClaimsPrincipal> ValidateTokenAsync(AuthenticationHeaderValue value) {
if(value?.Scheme != "Bearer")
return null;
var config = await _configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameter = new TokenValidationParameters {
RequireSignedTokens = true,
ValidAudience = AUDIENCE,
ValidateAudience = true,
ValidIssuer = ISSUER,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKeys = config.SigningKeys
};
ClaimsPrincipal result = null;
var tries = 0;
while (result == null && tries <= 1) {
try {
var handler = new JwtSecurityTokenHandler();
result = handler.ValidateToken(value.Parameter, validationParameter, out var token);
} catch (SecurityTokenSignatureKeyNotFoundException) {
// This exception is thrown if the signature key of the JWT could not be found.
// This could be the case when the issuer changed its signing keys, so we trigger
// a refresh and retry validation.
_configurationManager.RequestRefresh();
tries++;
} catch (SecurityTokenException) {
return null;
}
}
return result;
}
}
}
Then, I added this small bit of boilerplate code toward the top of any HTTP-triggered functions, before any other code is run to process the request:
ClaimsPrincipal principal;
if ((principal = await Security.ValidateTokenAsync(req.Headers.Authorization)) == null) {
return new UnauthorizedResult();
}
With this in place, I finally have the implementation I was looking for. I'd like to improve the implementation with something more generic like a custom attribute, but I'm not sure that's possible yet either for OpenID Connect providers. Still, this is a perfectly acceptable solution for me, and gives me the level of security I was looking for when using a React front-end with an Azure Functions back-end.
Cheers!

How to access two separate Web APIs protected using Azure AD B2C from a web app

We have two separeate dotnet core apis(API1 & API2) that are protected using azure ad b2c. Both these apis are registered on the b2c tenant and have their scopes exposed.
We have a client web applicaiton that is to access the above protected apis. This web app has been registered as a applicaiton in b2c tenant and has api permissions set for the above apis with proper scopes defined.
We use MSAL.net with a signinpolicy to sign the user in to the web app.
the authentication call requires scopes to mentioned. So we add API1's scope in the call.
(note : one scope of a single resource can be added in a auth call shown below)
public void ConfigureAuth(IAppBuilder app)
{
// Required for Azure webapps, as by default they force TLS 1.2 and this project attempts 1.0
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.Tenant, Globals.DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Globals.ClientId,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claim type that specifies the Name property.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {Globals.ReadTasksScope} {Globals.WriteTasksScope}",
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebCookieManager()
}
);
}
The OnAuthorizationCodeRecieved method in Startup.Auth.cs recieved the code recieved as a result of above auth call and uses it to get a access token based on the scopes provided and stores it in the cache. shown below
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
try
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = MsalAppBuilder.BuildConfidentialClientApplication(new ClaimsPrincipal(notification.AuthenticationTicket.Identity));
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
}
catch (Exception ex)
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.BadRequest,
ReasonPhrase = $"Unable to get authorization code {ex.Message}.".Replace("\n", "").Replace("\r", "")
});
}
}
This access token is then used in the TasksController to call AcquireTokenSilent which retrieves the access token from the cache, which is then used in the api call.
public async Task<ActionResult> Index()
{
try
{
// Retrieve the token with the specified scopes
var scope = new string[] { Globals.ReadTasksScope };
IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiEndpoint);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
// Handle the response
switch (response.StatusCode)
{
case HttpStatusCode.OK:
String responseString = await response.Content.ReadAsStringAsync();
JArray tasks = JArray.Parse(responseString);
ViewBag.Tasks = tasks;
return View();
case HttpStatusCode.Unauthorized:
return ErrorAction("Please sign in again. " + response.ReasonPhrase);
default:
return ErrorAction("Error. Status code = " + response.StatusCode + ": " + response.ReasonPhrase);
}
}
catch (MsalUiRequiredException ex)
{
/*
If the tokens have expired or become invalid for any reason, ask the user to sign in again.
Another cause of this exception is when you restart the app using InMemory cache.
It will get wiped out while the user will be authenticated still because of their cookies, requiring the TokenCache to be initialized again
through the sign in flow.
*/
return new RedirectResult("/Account/SignUpSignIn?redirectUrl=/Tasks");
}
catch (Exception ex)
{
return ErrorAction("Error reading to do list: " + ex.Message);
}
}
The issue is the code recieved by the OnAuthorizationCodeRecieved method can only be used to get the access token for API1 since its scope was mentioned in auth call. When trying to get access token for API2 it returns null.
Question : How to configure the web app so that it is able to access multiple protected apis?
Please suggest.
The code can be found from the sample https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi
A single access token can only contain scopes for a single audience.
You have 2 options:
Combine both services into a single app registration and expose different scopes.
Request multiple tokens - one per service. If your SSO policy is configured correctly in B2C, this should happen silently unbeknownst to the user.
I recommend using option 1 if you own both services (which it sounds like you do). A few tips related to this option.
When declaring the scopes in the combined app registration, use the dot-syntax {LogicalService}.{Operation}. If you do this, the scopes will be grouped by logical service within the Azure portal.
Make sure you are validating scopes in your service. Validating only the audience is not good enough and would allow an attacker to make lateral movements with a token bound for another service.

How to enable Windows Authentication with in-process IIS hosting under IdentityServer4?

My ASP.Net Core MVC app accesses a .Net Core API through IdentityServer. It works fine on IIS server running in-process with Entity Framework based identity store. Now I am trying to enable Windows Authentication and getting stuck here.
What I tried is following the identityserver doc section "Windows Authentication" - I added the code below to the ConfigureServices of my IdentityServer's Startup.cs
// configures IIS in-proc settings
services.Configure<IISServerOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
I also enabled the Windows Authentication in IIS for my API app
The part of the doc that I am confused about is "You trigger Windows authentication by calling ChallengeAsync on the Windows scheme". It doesn't mention where you do that. I am assuming it is in identityserver and I put the code in the Login method of the AccountController of the identityserver as bellow.
/// <summary>
/// Entry point into the login workflow
/// </summary>
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// trigger Windows authentication by calling ChallengeAsync
await ChallengeWindowsAsync(returnUrl);
// build a model so we know what to show on the login page
var vm = await BuildLoginViewModelAsync(returnUrl);
if (vm.IsExternalLoginOnly)
{
// we only have one option for logging in and it's an external provider
return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl });
}
return View(vm);
}
private async Task<IActionResult> ChallengeWindowsAsync(string returnUrl)
{
// see if windows auth has already been requested and succeeded
var result = await HttpContext.AuthenticateAsync("Windows");
if (result?.Principal is WindowsPrincipal wp)
{
// we will issue the external cookie and then redirect the
// user back to the external callback, in essence, treating windows
// auth the same as any other external authentication mechanism
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", "Windows" },
}
};
var id = new ClaimsIdentity("Windows");
// the sid is a good sub value
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.FindFirst(ClaimTypes.PrimarySid).Value));
// the account name is the closest we have to a display name
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
// add the groups as claims -- be careful if the number of groups is too large
var wi = wp.Identity as WindowsIdentity;
// translate group SIDs to display names
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
await HttpContext.SignInAsync(
IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
return Redirect(props.RedirectUri);
}
else
{
// trigger windows auth
// since windows auth don't support the redirect uri,
// this URL is re-triggered when we call challenge
return Challenge("Windows");
}
}
What I expect to happen, if everything goes well, is that the authentication happens automatically (without a login box?) because the "Challenge" call will require the client side (the browser) to send in Windows identity info and a token will be issued based on that.
It doesn't seem to work that way now - I am getting an Unauthorized error from API when starting the MVC app:
Am I doing that in the wrong place? Or am I missing something else?

Azure App Service with websockets and AD authentication

we got an application deployed as App Service and we are using SignalR for communication. After enabling AAD authentication - in browsers we started receiving 302 responses with redirect location to Azure AD.
Seems like the authentication layer on App Service is ignoring access_token passed by query string.
Request
Request URL: wss://<url>/hubs/chat?access_token=<token>
Request Method: GET
Response
Status Code: 302 Redirect
Location: https://login.windows.net/common/oauth2/authorize?...
After looking everywhere we couldn't find any solution to make this work.
The only solution to this issue that we see is either to disable authentication on App Service or use Long-Pooling, but both options are not acceptable in our situation.
By default, you web application will not get the access token from query string. Commonly, it will get the access token from authorization header or the cookie.
To get the access token from query string, you need to implement your custom authentication way.
Install Microsoft.Owin.Security.ActiveDirectory NuGet package.
Create an authentication provider which will get access token from query string.
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);
}
}
Add map in .
app.Map("/yourpath", map =>
{
map.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Provider = new QueryStringOAuthBearerProvider(),
Tenant = tenantId,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = clientId
}
});
map.RunSignalR(hubConfiguration);
});
After multiple calls with Microsoft Technical Support, MS confirmed that App Service Authentication layer doesn't support access token passed in query string and there are no plans for this support yet. So there are two options:
Use different protocol for SignalR (long pooling works just fine)
Drop App Service Authentication
Using a custom middleware, I was able to update the request prior to authorization occurring:
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace Stackoverflow.Example.Security.Middleware
{
public class BearerTokenFromQueryToHeaderMiddleware
{
private readonly RequestDelegate _next;
public BearerTokenFromQueryToHeaderMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Query["access_token"];
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", $"Bearer {token}");
}
await _next(context);
}
}
}
I didn't try to get this working with the OpenID framework, but I did test using a custom policy. As long as this is registered earlier than the authentication, then this middleware should execute prior to the framework looking for the token in the header.

Application access to SharePoint Online using Azure AD Token

How can I get an application token to query SharePoint with application credentials (= without user impersonation) using Azure AD?
The following code works perfectly for querying data as a user but we need to fetch information without impersonation like listing all sites in the collection regardless of user permissions etc.
Exception thrown:
An exception of type
'Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException'
occurred in mscorlib.dll but was not handled in user code
Additional information: AADSTS70001: Application with identifier 'xxx'
was not found in the directory sharepoint.com
Code to get token:
internal static async Task<string> GetSharePointAccessToken(string url, string userAccessTokenForImpersonation)
{
string clientID = #"<not posted on stack overflow>";
string clientSecret = #"<not posted on stack overflow>";
var appCred = new ClientCredential(clientID, clientSecret);
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/common");
// Use user assetion if provided, otherwise use principal account
AuthenticationResult authResult = null;
if (string.IsNullOrEmpty(userAccessTokenForImpersonation))
{
authResult = await authContext.AcquireTokenAsync(new Uri(url).GetLeftPart(UriPartial.Authority), appCred);
}
else
{
authResult = await authContext.AcquireTokenAsync(new Uri(url).GetLeftPart(UriPartial.Authority), appCred, new UserAssertion(userAccessTokenForImpersonation));
}
return authResult.AccessToken;
}
Test code:
// Auth token from Bearer https://xxx.azurewebsites.net/.auth/me
string authHeader = #"<valid jwt bearer token from azure auth>";
var sharePointUrl = #"https://xxx.sharepoint.com/sites/testsite/";
string sharePrincipalToken = await GetSharePointAccessToken(sharePointUrl, null); // <-- doesn't work
string sharePointUserToken = await GetSharePointAccessToken(sharePointUrl, authHeader); // <-- works
Permissions in Azure AD:
The error message you are getting implies that you are signing in with a user that is pointing our token service to get a token in the context of "sharepoint.com"
This is because you are using the "common" endpoint. Read more about that here.
Instead try using a fixed endpoint, where the tenant is the same as where the application is registered and see if that solves your issue.
If your plan is to make this application accessible by multiple tenants, make sure that you have explicitly set your application to be multi-tenant, and then make sure you have a user from the external tenant try and sign into the application before you try doing service to service calls.
Let me know if this helps.

Resources