I have an ASP.NET web application, let's call it Web App A, that is self-hosted using OWIN and NancyFX.
Web App A uses Basic Authentication, which is set up in CustomBootstrapper.cs like this:
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(
container.Resolve<IUserValidator>(),
"MySpecificRealm"));
After querying the user for the username and password, Web App A calls an authentication REST API to validate the credentials and return a security token if they are valid.
I am being asked to make a subset of that app, let's call this subset Web App B, when a valid security token is specified in the URL instead of prompting for the credentials.
The following picture might help explain this:
Per the usual, there are various modules in Web App A that call:
this.RequiresAuthentication();
How can I bypass the authentication when a valid security token is passed as a parameter in the URL?
You need to either add a parameter to a config file or use a conditional compilation symbol. Either of these will set a flag that you can then check to bypass the wiring up of the Basic Authentication in the pipelines and possibly the this.RequiresAuthentication().
For those Nancy Modules that require authentication, add a required URL parameter to the incoming request for the caller to supply the security token.
You are going to need the REST API to have a call that validates the security token, and you will need to call it and receive back the userName so you can create your IUserIndentity.
Related
Actors:
Black box (MS Power App custom connector) with user logged in
API A - .NET Standard WebApi with [Authorize] attribute and AAD authorization enabled (no additional verification in code), ida:audience="A" in web.config
API B - .NET Standard WebApi with [Authorize] attribute and AAD authorization enabled (no additional verification in code), ida:audience="B" in web.config
API C - .NET Standard WebApi with [Authorize] attribute and AAD authorization enabled (no additional verification in code), ida:audience="C" in web.config
Flow:
Power App connector calls API A. Within the code of API A (C#) I can decode JWT from Authorization header and see which user is logged in. Now, I have to call API B and API C and then return something to Power App connector. API B and API C must be able to also decode JWT and get UPN from token.
Problem:
When I tried to reuse whole Authorization header (just get it within API A and add to HttpClient to call API B and C) I get 401.
Now I know that was because of ida:Audience value. I suppose it is part of .NET framework verification, beyond my control. When I changed ida:Audience in API B and C to "A" it worked. But it was only for testing purposes, I am not allowed to do it in production.
Then I tried to set scopes in Power App connector to multiple values (delimited by space) with different audiences - no luck, error from AAD, multiple audiences not allowed.
So, my question is: is it possible in my scenario to do what I need? I have only access token for audience A and have to authorize (with impersonation) in endpoints with other audience value.
This is exactly what the on-behalf-of flow is for.
It allows API A to take the access token it received and ask for an access token to API B (with the same user info in the token).
More info on the flow: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow.
Example request content from documentation to token endpoint that uses a client secret:
//line breaks for legibility only
POST /oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com/<tenant>
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
&client_secret=sampleCredentia1s
&assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InowMzl6ZHNGdWl6cEJmQlZLMVRuMjVRSFlPMCJ9.eyJhdWQiOiIyO{a lot of characters here}
&scope=https://graph.microsoft.com/user.read+offline_access
&requested_token_use=on_behalf_of
Using MSAL.NET is definitely better than using the HTTP endpoint directly though.
You would replace the scope in this request with a scope from API B.
I have set up a Web Application with ASP.NET Razor Pages with -> Individual User Accounts -> Connect to an existing user store in the cloud (Azure AD B2C).
This works really well and I could both sign up and sign in to the web application.
However when I follow the guide for API I don't understand how to sign in.
The example Controller /weatherforecast simply returns a HTTP 401 when the web application is started.
Looking at the file structure I can't find any clues either but this could be similar to scaffolding I guess.
https://stackoverflow.com/a/50677133/3850405
If I comment out [Authorize] from WeatherForecastController I get a HTTP 200 so what I need is probably just a token from Azure AD B2C that is being sent to the Controller in the GET request.
I know that the B2C tenant and application work since I use the same application for the API as I did with the Web Application. It was set up using Microsofts own guide:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant
Update 2020-07-27:
Applications are now Legacy and App registrations should be used instead. See this guide:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications?tabs=app-reg-ga#register-a-web-application
Old:
Fixed it using these guides:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview
https://learn.microsoft.com/en-us/azure/active-directory-b2c/access-tokens
I had some trouble where I got the error "AADB2C90205: This application does not have sufficient permissions against this web resource to perform the operation. numerous times. Turned out I had not declared the correct scopes for the application.
First step is therefore to make sure you have a read scope for your Azure AD B2C application under Published scopes:
Then under API access add your application with the scope read.
Then perform a GET request with this format, simplest way to test is to use it in Chrome or any other browser:
https://<tenant-name>.b2clogin.com/tfp/<tenant-name>.onmicrosoft.com/<policy-name>/oauth2/v2.0/authorize?
client_id=<application-ID>
&nonce=anyRandomValue
&redirect_uri=https://jwt.ms
&scope=https://<tenant-name>.onmicrosoft.com/api/read
&response_type=code
Make sure the redirect_uri is present as Reply URL for your application.
This should give you a result like after logging in like https://jwt.ms/?code=... or https//localhost:44376/signin-oidc?code= depending on redirect_uri. Microsoft example uses https://jwt.ms but I prefer to keep my codes on domains that I control.
Copy the value from code parameter and then perform a POST request, I use Postman.
POST <tenant-name>.onmicrosoft.com/oauth2/v2.0/token?p=<policy-name> HTTP/1.1
Host: <tenant-name>.b2clogin.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=<application-ID>
&scope=https://<tenant-name>.onmicrosoft.com/api/read
&code=eyJraWQiOiJjcGltY29yZV8wOTI1MjAxNSIsInZlciI6IjEuMC...
&redirect_uri=https://jwt.ms
&client_secret=<app-key>
client_secret is from Keys:
Correct response should look like this:
Then you can copy the value for access_token and access your local API with Bearer Authorization. To see the content of your access_token you can copy the value to https://jwt.ms/
A common use case for WebAPI would be to have shell views rendered by MVC controllers, which contain javascript that then hit your API to access data.
But let's say you have some expensive API operations and you don't want people remotely accessing those endpoints -- you only want your MVC views, delivered by your application, to access them. How could you go about protecting them?
In this case Request.IsLocal doesn't work, because javascript is invoking it from the client's browser on their machine. Even if it did work, you need to dig to get the real HttpContext in order to find this property -- and that solution wouldn't work in self-hosted WebAPI.
For API endpoints that require a valid IPrincipal, you could protect them with the [Authorize] attribute. But what about API endpoints that you want your app to be able to access for anonymous users?
I have tried a solution and will post it separately as an answer, because I'm not sure if it's the best (or even a good) approach.
If your MVC site uses authentication, you could enable forms authentication for your Web API methods. You could write a custom [Authorize] attribute that will check for the presence of a forms authentication cookie which will be sent from the AJAX call and if present construct the principal.
Another possible solution is to protect your API with tokens which is a more RESTful style. The idea here is that when a user authenticates on your MVC website you could generate and pass a token to the view which will be used when sending the AJAX request to the Web API which in turn will verify the validity of the token and its signature.
If on the other hand your site doesn't use authentication, then things will get very complicated because you have no way of knowing whether the request comes from a trusted client since you are using javascript to call your API methods.
Before you go harping about "what have you tried", here is what I have tried. It works. Just not sure if there is a better way.
Create an MVC action filter and add it as a global filter during Application_Start.
Create an Http (WebAPI) action filter and use it on actions that should reject remote requests.
The global MVC filter does this:
Looks for a specific cookie in the request. If the cookie is there, its value is decrypted. The decrypted value should be a string representation of a DateTime, so use DateTime.TryParse to get it out. If the value is correctly parsed to a DateTime, and that DateTime is less than a day old, STOP HERE and do nothing else.
If the cookie is not there, or cannot be decrypted / parsed, or is older than a day, write a new cookie to the browser. Use the current DateTime.UtcNow.ToString() as the value, encrypt it, and write it with HttpOnly = false.
The WebAPI filter does this:
Looks for a specific cookie in the request. If the cookie is there, decrypt its value and try to parse it out as a DateTime.
If the value is a valid DateTime and is less than 2 days old, STOP HERE and do nothing else.
Otherwise, throw a 403 Forbidden exception.
A couple of notes about my current implementation of this. First of all, I use AES encryption with a shared secret and a salt. The shared secret is stored as an appSetting in web.config. For the salt, I enabled anonymous identification and used Request.AnonymousID as the salt. I'm not entirely fond of the salt because it's tricker to get at in a WebAPI controller, but not impossible as long as it is not self-hosted.
I have two webprojects: MyApp and Api.MyApp. MyApp is an MVC5 Application and Api.MyApp is a MVC WebAPI application.
MyApp is a rich client application that calls the API project for most of its operations. For authorization I am using a Bearer token for the web api but I would also like to have a cookie
so I can secure certain routes in the main MyApp MVC project.
To get the bearer token I call "http://api.myapp/token" can I throw this bearer token into a cookie and have the MVC project recognize it or do I have to send 2 separate calls, 1 to the api to get the bearer token and 1 to the mvc app to get the cookie. This seems a little redundant, is there a better way?
Yes.
Assuming your applications are responding on something like:
api.example.com
www.example.com
The user comes to your site on www.example.com and provides their credentials. Your app then makes an AJAX call to api.example.com to get the token. You have a couple of options now:
api.example.com returns the token, along with a cookie, with the domain set to .example.com
api.example.com returns the token, and the client-side script sets that into a cookie with the domain set to .example.com
If you do this, then both the API and the client application should have access to the cookie based token.
You may have to roll your own authorisation mechanism based on the bearer cookie if you're not using .Net auth tokens.
If however you are on intranet type domains (i.e. http://myapp/ and http://api.myapp) then you'll have to go with option 2 and use the default domain for the cookie (you can't set a cookie with just a single period in the domain otherwise I could set one to ".com" and splurge data everywhere.)
I have WCF Service where user can add a simple message. Before service put message to database, I need to authorize user, like here:
[OperationContract]
[WebGet(UriTemplate = "/GetMessages/{SessionToken}/{UserPassword}/{UserGLKNumber}")]
Messages GetMessages(string SessionToken, string UserPassword, string UserGLKNumber);
It's obvious that this solution is not good (sending in url user password and number). So, what is other approach?
What is important - I have a client written in Java/PHP/Obj-C (simple, small application) - anyway not in C#.
Write Login method and use ASP.NET auth cookie (forms based authentication), see this. Or use Basic authentication and let client to authenticate by http standard way.
You have to distinguish between Authentication (who is it) and Authorization (what can he do than). For the first you have a variety of options where Windows (the logon credentials of the user) or Basic (username + password) are most straightforward. This is just a manner of configuration on the service side.
On the other hand, authorization can be done on identity (which user is it) or by role (which roles apply to this user). The latter is possible "in code" with if/else constructs but also with attributes on the method [PrincipalPermission(SecurityAction.Demand, Role="Administrator")]. This specifies that you "demand" that the user accessing the method has the "role" administrator (something you specify yourself).
To supply roles to the identity you need some sort of role provider, obviously it is not something the user can provider. Therefore you can use the ASP.NET RoleProvider or a Secure Token Service that stands in between.
You can read more about it here: http://msdn.microsoft.com/en-us/library/ff405740.aspx