HTTP 500 using OAuthAuthentication with Azure Active Directory to secure ASP.NET 5 Web API - asp.net-web-api

For context, I have OpenIdConnect with an ASP.NET 4 Web App working using Owin (and a lot of help from Modern Authentication with Azure Active Directory for Web Applications.
I now want to secure a separate ASP.NET 5 Web API project (to be hosted in the same AD tenant in Azure as a microservice). I started with the simple ASP.NET 5 WebApi generated in Visual Studio and added the following to the Configure in Startup.cs (at the beginning of the pipeline):
app.UseOAuthAuthentication(new OAuthOptions()
{
ClientId = "71d33a1c-505c-4815-a790-8494dd2bb430",
ClientSecret = "LajQFbf1/Nyt/6zCP5vE5YWj5VC4aNaC3i/SRtEj2sI=",
TokenEndpoint = "https://login.microsoftonline.com/7058f4f0-619f-4c16-ac31-9e209d70ff23/oauth2/token",
AuthorizationEndpoint = "https://login.microsoftonline.com/7058f4f0-619f-4c16-ac31-9e209d70ff23/oauth2/authorize",
AuthenticationScheme = "OAuth2Bearer",
CallbackPath = "/api/values"
});
This gives me an error that indicates SignInScheme must be provided, but I'm not clear on what that value should be. If I add in a string, say "OAuth2Bearer", I get further, but still get a 500 error on the request, but no exception raised in the API app, nor does the breakpoint on the first line in my API controller implementation get hit.
What am I missing? Ideally, I want to then extend the Events of OAuthOptions to add a custom claim, analogous to what I did with OpenIdConnect and the SecurityTokenValidated notification.

The OAuth2 base middleware cannot be used for token validation as it's an OAuth2 client middleware made for handling interactive flows like the authorization code flow. All the existing OAuth2 social providers (e.g Facebook or Google) inherit from this middleware.
You're actually looking for the JWT bearer middleware:
app.UseJwtBearerAuthentication(options => {
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.Authority = "[address of your OIDC server]";
options.Audience = "[audience of the access tokens issued by the OIDC server]";
});
To learn more about the different OAuth2/OIDC middleware in ASP.NET Core, don't miss this other SO post: Configure the authorization server endpoint.

Related

Securing .NET Framework Web API with Azure AD (Client credentials flow)

I have a .NET 4.7 Web API project (not .NET CORE).
I am trying to setup authentication with an Azure AD directory, I setup an application in my AD, and I got the client id (application id)
I would like to use the Client Credentials grant type. So I went ahead and retrieved a token via the access token URL https://login.microsoftonline.com/HIDDEN/oauth2/v2.0/token I am passing in the client id, and secret, for this I am using Postman
Now in my project I've implemented the following logic in my web api project:
var clientId = "AZURE APPLICATION ID";
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AllowedAudiences = new List<string> { clientId },
TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidAudience = clientId
}
});
On my controller, I applied the [Authorize] attribute
When calling the API controller endpoint (making sure I am passing in the Authorization header with the value "Bearer MYTOKEN") I get the error returned in Postman:
"Message": "Authorization has been denied for this request."
Is there a way I can dive deeper to figure out what might be wrong?
I don't see anything in my output window in visual studio, are there some events I can hook into why it is failing?
EDIT: Adding more information per Carl:
The token seems to be valid, here are the results from jwt.ms, i even setup an "admin" role via the manifest:
Here is my code, I am not specifying the public signature (not sure how to do that yet), but I've even turned off IssueSignature validation.
This is what my controller looks like:
My fiddler request and response (does having an http endpoint instead of https for local development make a difference?) I don't believe it does:
Inspect your access token and ensure the aud claim value equals the clientId. Usually the aud claim will be something like api://clientId which is not what you have setup in your code. If that's the case set it as "api://" + clientId
You should get a 401 error, which means that the aud of your token is not your api. The cause of the error is usually that you set the wrong scope when requesting the token. I used the client credential flow Make a demo for you:
You need to create two applications in Azure ad, one representing the client application and the other representing the api application, and then use the client application to call the Web api application.
First, you need to expose the api of the application representing the web api, you can configure it according to the following process:
Azure portal>App registrations>Expose an API>Add a scope>Add a client application
Next, you need to define the manifest of api applications and grant application permissions to your client applications (this is the role permissions you define yourself, you can find it in My APIs when you add permissions)
This is the process of defining the manifest.
This is to grant permissions for the client application (You can find your expose api permissions in My APIs.):
Request access token:
Parse the token:

Hooking up IdentityServer4.AccessTokenValidation and Reference Tokens with ASP.NET Core Identity

I am try to build out an Angular 6 application backed by a ASP.NET Web API back end. The application authenticates users against a separate Identity Server 4 that federates Azure AD ultimately returning a Bearer reference token.
From a login and then authenticate (the token) perspective, everything is working. The user access the site, gets redirected to Azure AD, logs in, and gets redirected back to the Angular 6 app. For the API calls, the token is injected into the header and the ASP.NET Core authentication middleware validates the token and returns the data.
services
.AddAuthentication(options => {
options.DefaultScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
})
.AddIdentityServerAuthentication(options => {
options.Authority = AuthClient.SsoBaseUrl;
options.RequireHttpsMetadata = false;
options.ApiName = AuthClient.SsoClientName;
options.ApiSecret = AuthClient.SsoClientSecret;
});
My problem is that I am trying to incorporate ASP.NET Core Identity and I am unsure of where/how to tie the claims principal returned via AddIdentityServerAuthentication.
In a previous version (ASP.NET Framework 4.7) of this application, we used Ws-Federation which redirected back to ExternalCallbackLogin() in AccountController which executed the usual AuthenticationManager.GetExternalLoginInfoAsync() and then SignInManager.ExternalSignInAsync() and then (if required) AppUserManager.CreateAsync() and AppUserManager.AddLoginAsync().
This code basically checked the [AspNetUserLogins] table for the provider specific key (from the claims) to map to a user in the [AspNetUsers] table.
With the change to IdentityServer4.AccessTokenValidation I cannot figure out where to put this logic. Since I am using reference tokens it does not look like the JwtBearerEvents (specifically the OnTokenValidated event) is not being raised. In fact, it does not look like there are any Introspection events...??
I am tried testing the logic in a test endpoint that called the above "Manager" methods (technically it was the updated for ASP.NET Core Identity equivalent methods) but that is not working either.
For example, the very first call to SignInManager.GetExternalLoginInfoAsync() returns null.
What am I doing wrong?

OWIN Authentication Server for multiple applications

I am in the process of implementing a solution that has an MVC client (lets call this CLIENT at localhost:4077/) with a WebAPI service (called API at localhost:4078/)
I have implemented OWIN OAuth in the API but wanted to know whether the OWIN could be implemented in a separate solution (lets call it AUTH at localhost:4079/token) to generate the token for the CLIENT, then the CLIENT passes this to the API (as the Bearer authorisation token)
The reason i am querying this is that there is likely to be additional WebAPI services that will be accessed by the CLIENT and i'd like to use OWIN between the client and all API services.
The issue is i am not sure if the token generated by the AUTH service could be used to authorise all requests on the CLIENT and all API services.
Has anyone implemented anything like this and if so could you provide an example, i am pretty new to OWIN and OAUTH so any help would be greatly appreciated
Separating the authorization server from the resource server is extremely easy: it will even work without any extra code if you use IIS and if you have configured identical machine keys on both applications/servers.
Supporting multiple resource servers is a bit harder to implement with the OWIN OAuth2 server if you need to select which endpoints an access token can gain access to. If you don't care about that, just configure all your resource servers with the same machine keys, and you'll be able to access all your APIs with the same tokens.
To have more control over the endpoints that can be used with an access token, you should take a look at AspNet.Security.OpenIdConnect.Server - a fork of the OAuth2 server that comes with OWIN/Katana - that natively supports this scenario: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server.
It's relatively easy to set up:
Add a new middleware issuing tokens in your authorization server application (in Startup.cs):
app.UseOpenIdConnectServer(new OpenIdConnectServerOptions
{
Provider = new AuthorizationProvider()
});
Add new middleware validating access tokens in your different API servers (in Startup.cs):
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
// AllowedAudiences MUST contain the absolute URL of your API.
AllowedAudiences = new[] { "http://localhost:11111/" },
// X509CertificateSecurityTokenProvider MUST be initialized with an issuer corresponding to the absolute URL of the authorization server.
IssuerSecurityTokenProviders = new[] { new X509CertificateSecurityTokenProvider("http://localhost:50000/", certificate) }
});
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
// AllowedAudiences MUST contain the absolute URL of your API.
AllowedAudiences = new[] { "http://localhost:22222/" },
// X509CertificateSecurityTokenProvider MUST be initialized with an issuer corresponding to the absolute URL of the authorization server.
IssuerSecurityTokenProviders = new[] { new X509CertificateSecurityTokenProvider("http://localhost:50000/", certificate) }
});
Finally, add a new OpenID Connect client middleware in your client app (in Startup.cs):
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
// Some essential parameters have been omitted for brevity.
// See https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/dev/samples/Mvc/Mvc.Client/Startup.cs for more information
// Authority MUST correspond to the absolute URL of the authorization server.
Authority = "http://localhost:50000/",
// Resource represents the different endpoints the
// access token should be issued for (values must be space-delimited).
// In this case, the access token will be requested for both APIs.
Resource = "http://localhost:11111/ http://localhost:22222/",
});
You can have a look at this sample for more information: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/dev/samples/Mvc/
It doesn't use multiple resource servers, but it shouldn't be hard to adapt it using the different steps I mentioned. Feel free to ping me if you need help.

Understanding the VS2013 MVC5 SPA Template WebAPI Security Features

I am trying to understand the security features of the VS2013 MVC5 SPA Template WebAPI.
In Startup.Auth.cs there is this -
TokenEndpointPath = new PathString("/Token")
and this -
AuthorizeEndpointPath = new PathString("/Account/Authorize")
In my understanding, the AuthorizeEndPointPath is for when your acting as a 3rd Party OAuth authorization server.
However how it is used in the template, the flow seems to be
Login via Forms Authentication
Redirect to a secure page
Javascript checks for a bearer token in the local storage, it doesn't exist, so it redirects to this Authorize endpoint.
window.location = "/Account/Authorize?client_id=web&response_type=token&state=" + encodeURIComponent(window.location.hash);
Logs into app with OAuth security, returns token, which will be passed on subsequent API requests.
The /Token endpoint never seems to be used. A prior version of this templated did an ajax post to /Token to login. I have a similar situation where the website is secured via Forms authentication/cookies but the WebApi is secured by Bearer token.
Is the Authorize endpoint being used correctly in this SPA template - is this the correct pattern to follow? It seems like the appropriate way to authenticate for bearer token security "internally" for the app would be a "client credentials grant" though not sure how to generate the "secret".

Logout from access control service with custom STS

I'm using Windows azure access control service with custom STS. I can login to my application through ACS, but I have trouble with logout function. I've tried this code in my application.
WSFederationAuthenticationModule fam = FederatedAuthentication.WSFederationAuthenticationModule;
try
{
FormsAuthentication.SignOut();
}
finally
{
fam.SignOut(true);
}
Page.Response.Redirect("default.aspx");
But it seems that it logout the user from ACS but not from the custom STS. What should I do to logout from STS. Where could be the problem in the appliacation (RP), ACS or in STS?
I think that ACS should ask custom STS to logout the user, but it seems it doesnt do that. What I am missing?
I have created a helper method for doing FederatedSignout, with comments in the code for what I discovered along the way (hth)
public static void FederatedSignOut(string reply = null)
{
WSFederationAuthenticationModule fam = FederatedAuthentication.WSFederationAuthenticationModule;
// Native FederatedSignOut doesn't seem to have a way for finding/registering realm for singout, get it from the FAM
string wrealm = string.Format("wtrealm={0}", fam.Realm);
// Create basic url for signout (wreply is set by native FederatedSignOut)
string signOutUrl = WSFederationAuthenticationModule.GetFederationPassiveSignOutUrl(fam.Issuer, null, wrealm);
// Check where to return, if not set ACS will use Reply address configured for the RP
string wreply = !string.IsNullOrEmpty(reply) ? reply : (!string.IsNullOrEmpty(fam.Reply) ? fam.Reply : null);
WSFederationAuthenticationModule.FederatedSignOut(new Uri(signOutUrl), !string.IsNullOrEmpty(wreply) ? new Uri(wreply) : null);
// Remarks! Native FederatedSignout has an option for setting signOutUrl to null, even if the documentation tells otherwise.
// If set to null the method will search for signoutUrl in Session token, but I couldn't find any information about how to set this. Found some Sharepoint code that use this
// Michele Leroux Bustamante had a code example (from 2010) that also uses this form.
// Other examples creates the signout url manually and calls redirect.
// FAM has support for wsignoutcleanup1.0 right out of the box, there is no need for code to handle this.
// That makes it even harder to understand why there are no complete FederatedSignOut method in FAM
// When using native FederatedSignOut() no events for signout will be called, if you need this use the FAM SignOut methods instead.
}
This code is used in a standard RP library we created for Web SSO with ACS.
The December 2012 update of ACS includes support for federated single sign-out:
Using the WS-Federation protocol. Web applications that use ACS to
enable single sign-on (SSO) with identity providers using the
WS-Federation protocol can now take advantage of single sign out
capabilities. When a user signs out of a web application, ACS can
automatically sign the user out of the identity provider and out of
other relying party applications that use the same identity provider.
This feature is enable for WS-Federation identity providers, including
Active Directory Federation Services 2.0 and Windows Live ID
(Microsoft account). To enable single sign out, ACS performs the
following tasks for WS-Federation protocol endpoints:
ACS recognizes wsignoutcleanup1.0 messages from identity providers
and responds by sending wsignoutcleanup1.0 messages to relying party
applications.
ACS recognizes wsignout1.0 and wreply messages from relying party
applications and responds by sending wsignout1.0 messages to identity
providers and wsignoutcleanup1.0 messages to relying party
applications.
From the Code Sample: ASP.NET MVC 4 with Federated Sign-out, implement an Action like this to sign out from ACS:
(Note that Windows Identity Foundation is now incorporated into .NET 4.5 Framework, that's why the new namespaces below)
using System.IdentityModel.Services;
using System.IdentityModel.Services.Configuration;
public ActionResult Logout()
{
// Load Identity Configuration
FederationConfiguration config = FederatedAuthentication.FederationConfiguration;
// Get wtrealm from WsFederationConfiguation Section
string wtrealm = config.WsFederationConfiguration.Realm;
string wreply;
// Construct wreply value from wtrealm (This will be the return URL to your app)
if (wtrealm.Last().Equals('/'))
{
wreply = wtrealm + "Logout";
}
else
{
wreply = wtrealm + "/Logout";
}
// Read the ACS Ws-Federation endpoint from web.Config
// something like "https://<your-namespace>.accesscontrol.windows.net/v2/wsfederation"
string wsFederationEndpoint = ConfigurationManager.AppSettings["ida:Issuer"];
SignOutRequestMessage signoutRequestMessage = new SignOutRequestMessage(new Uri(wsFederationEndpoint));
signoutRequestMessage.Parameters.Add("wreply", wreply);
signoutRequestMessage.Parameters.Add("wtrealm", wtrealm);
FederatedAuthentication.SessionAuthenticationModule.SignOut();
string signoutUrl = signoutRequestMessage.WriteQueryString();
return this.Redirect(signoutUrl);
}

Resources