I need to add OpenID Connect authentication to an existing ASP.NET MVC web application. For that, I am using OWIN.
Once the user has been successfully authenticated by the authentication server (Azure Active Directory in my case), I need to check that the user can be mapped to a local account in my web application. If the user has no valid local account, the actual login process should fail. Is there a standard way to do that?
In the sample I have found (https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-asp-webapp), it seems that as soon as the user is successfully authenticated by Azure, then it is logged in the application. Can we add custom code to the OWIN-OIDC sign-in process to perform additional checks?
Thanks for your help.
Good question and there are 2 levels here, as you are finding:
Authenticating with the Authorization Server / Identity Provider
Access to the application being allowed
If the first is successful but the second is not, then the standard behaviour is to redirect the user to an 'Access Denied' page (this page must allow anonymous access, to avoid a redirect loop). The login has not failed, but the user is not authorized to access the app.
OWIN uses response_type='code id_token' meaning that you will have an Open Id Connect User Identity to work with. If you can't find the user in your own data you can perform a redirect in the below callback:
var openidOptions = new OpenIdConnectAuthenticationOptions
{
Notifications = new OpenIdConnectAuthenticationNotifications
{
// Point to a callback to check the identity against my own system
// This callback can perform a redirect if the app does not recognise the user
AuthorizationCodeReceived = HandleAuthorizationCodeReceived
}
}
Related
I have an MVC client app (APP1) protected by Identity Server. I use a backchannel mechanism to log out of clients as soon as user logs out of idsrv. Pretty similar to this one: https://github.com/IdentityServer/IdentityServer4/blob/main/samples/Clients/src/MvcHybridBackChannel/Startup.cs
Now the thing is: I need to add a support for external provider, Azure AD. And in turn it also should support automatic log out: when a user logs out of Azure AD, he should be logged out of idsrv and from the client apps.
My first idea was to implement the same approach: Azure App registration supports the ability to call client's endpoint upon log out. But I'm struggling with the part when I need to set up custom CookieAuthenticationEvents. The code which I have in a client app APP1:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.EventsType = typeof(CookieEventHandler);
}
But the same code doesn't work in idsrv. But this I mean that my cookie is not being validated by my custom CookieEventHandler. Can anyone point me in a right direction?
One idea is to let Azure AD call the Logout method on the AccountController to sign out the user. (or if you make your own "logout" endpoint).
The logout method is protected by a ValidateAntiForgeryToken, so you might need to get around that by creating your "own" logout endpoint.
But basically what you need to do on a high level is to let AzureAD trigger a call to the
HttpContext.SignOutAsync(); method.
I have an existing website that I want to do a proof of concept with OAuth2 / OIDC. To this end I've configured a locally running IdentityServer4 MVC app as my demo OIDC server following the IdentityServer4 quick setup guidelines. This works fine and navigating to:
http://localhost:5000/.well-known/openid-configuration
Lets me see the discovery document.
I have created a fake login page on this OIDC app which consists of just a login button with no user credentials required.
There's no actual user database and I'm just hard coding some user details to return when the 'authentication' occurs.
In my pre-existing site I've added the OWIN middle wear and am configuring OIDC using the OpenIdConnectAuthenticationOptions. The clientId, scopes, secret etc all match as required and the authority is set to point to my locally running demo OIDC app (http://localhost:5000). The redirect url is set to return to my pre-existing site once authentication is complete.
This all appears to be fine but here's what I want to achieve and can't get working. On my pre-existing site when I navigate to any page that requires authentication I want the user to be redirected to the login page I created on OIDC app. They click the login button (no user details required) and are authenticated and redirected back to the original page.
Currently when I navigate to a protected page I am successfully redirected to the OIDC app but I am redirected to an error page and I don't know why. The error page gives me no detail, it's actually hard coded in the app.
When I look at the discovery document I see that the setting for the 'authorization_endpoint' is set to:
http://localhost:5000/connect/authorize
So I thought maybe I needed to either change that to point to Home/Login which is where I've created the dummy login form, or else I needed to actually create that connect/authorize endpoint and put my form there. Creating the end point makes no difference, it never gets hit and instead I just get the error page on my OIDC app. Changing it to home/login also appears to be ignored.
I am away from my main PC at the moment hence the lack of code snippets but essentially the set up is as per the IdentityServer4 quick setup guide and the OIDC app does appear to be working.
The issue is getting my pre-existing site to properly redirect to the login page.
I've been stuck on this for quite a while now and would like to even get to the stage of seeing the dummy login page. Any pointers are appreciated and again apologies for the lack of sample code.
UPDATE
I've got the login form appearing by setting the openidconfiguration like so:
Configuration = new OpenIdConnectConfiguration()
{
AuthorizationEndPoint = "http://localhost:5000/home/login"
}
However, this isn't logging me in when I click login. On that login action I'm doing this:
await HttpContext.SignInAsync("subjectId","username", authenticationProps);
And then redirecting back to my existing site. However it's not authenticating me and the redirect ends up being redirected back again to the login page.
UPDATE 2
I think the redirect URI should possibly be doing something more. Currently I do the following:
Try and access a restricted page -> Redirected to OIDC server -> Click Login (this sets the subject and user successfully) -> Am redirected to redirect URI which immediately bounces me back to the OIDC server.
So maybe the redirect URI is supposed to confirm login or otherwise do something?
So in the open id connect protocol, the authorize endpoint is used to validate the client information passed as query parameters (client_id, scopes, redirect_uri, etc). In your authentication server, none of that is being checked if all the endpoint does is return a form. Then again the validation can be tedious so keeping the authorize endpoint separate from the endpoint for logging in might be worth a thought.
The developers of Identity Server thought the same thing which is why they set up the endpoint (and endpoint validation) for you as part of the middleware. The validation uses the components that were injected (primarily the client_store, and your defined scopes) to be used by Identity Server.
After the framework validates your authorize request using your client store implementation, it will redirect the user to whichever login page you specify. The login page can be specified by changing it with the a delegate that can be passed in as the second parameter of 'AddIdentityServer' (that takes in something of type IdentityServerOptions that we'll refer to as just 'options'). In the delegate you can specify the login url of the page by changing the value of 'options.UserInteraction.LoginUrl' to the url of the login page.
After the user logs in and you call the signInAsync method on the HttpContext, you're actually supposed to redirect back to a query parameter passed to the login page referred to as the 'return_url' (which is basically the initial authorize endpoint request). This authorize endpoint further validates the cookie and will send the user back to the 'redirect_uri' (if consent on the client is set to false) with a code (if using the authorization code flow) or the id_token and optionally the access token (if using the implicit flow).
Assuming the implicit flow for simplicity, the tokens can be found in the request to the 'redirect_uri' and from there it's all up to you. Commonly the client will issue some kind of cookie (which can potentially contain the id or access token) to mark the successful authentication by the identity provider.
I successfully host an app service on azure (ASP.Net MVC 5). There are some areas that are publically accessible (...mywebsite). To access the admin area (...mywebsite/admin) a user needs needs to authorize which is done via Azure AD and the [Authorize] attribute on all controller actions.
Everything worked perfectly via http.
Now in Azure I added a certificate to the app service and enabled „always use https“ which means every user accessing the app service via http gets redirected to https (http://mywebsite -> https://mywebsite).
Fine, it also works. But: when I try to access the admin area, after the login page from Microsoft where I enter my username and pwd it doesn’t get redirected to https://mywebsite/admin (which I expected) but back to the base url https://mywebsite.
Seems that I am missing something...
This will be due to your web application's Azure AD registration containing the https://mywebsite/ as the Return URL and not the https://mywebsite/admin. You can either add your admin site as another return URL or use some code to determine the location to return to after login. See How to set Azure Authentication custom login return url?
Secure 2 different web applications with one identify service
Identity Service: Thinktecture Identity Service V2
Application 1: Asp.net MVC 5 application
Application 2: Asp.net Web API application
The above Applcation 1 and Application 2 are different projects and hosted in different servers. Now my scenarios are
1. Want to secure Application 1 with Identity Service.
2. Want to secure Application 2 with Identity Service.
Use case 1: If user access Application 1 it should redirect to identity service login page, once I entered the credentials and login in to application 1, the same token allow me to access Application 2.
Use case 2: If user try to access any API from Application 2 without login into Identity service, the request should reject.
I found a way to do this using pure SAML tokens. The trick is you need to create a delegation account in Identity Server to allow your web app to delegate identity to a specific realm (where your service lives). Then in the web app you make a service call using the the token that the user already has to get a new token which you use to access your service.
I asked a very similar question and answered it myself here.
Ok. I did eactly the same thing just now. Everything required to get that done is written here. If you're using IdentityServer, you need to configure the Token Type of your RP to be JWT:
this allows you to later extract the token from your authenticated MVC 5 application (see the link above to see how to do this) and then send that token to your Web API. You then need to tell you web api to accept that token, using Microsoft's JwtSecurityTokenHandler class. This class has a ValidateToken() method which accepts 2 parameters, the first being the access token that you put into your auth headers of the requests to the Web API, and the second, the validation parameters are basically what you've defined in IdentityServer's config:
validationParams = new TokenValidationParameters
{
AllowedAudiences = _allowedAudiencesAndSigningKeys.Select(x => x.Key),
ValidIssuer = ConfigurationManager.AppSettings["IssuerIdentity"],
ValidateIssuer = true,
SigningTokens = _allowedAudiencesAndSigningKeys.Select(x => new BinarySecretSecurityToken(Convert.FromBase64String(x.Value)))
};
The Audience(s)/Realm(s) you want to allow access to, the issuer name (your Identity Server name) and the signing symmetric key(s) of the applications you have defined in Identity Server and want to grand access to. The ValidateToken() method returns an ClaimsPrincipal with a list of the claims extracted from the token. The code to do all this can be put in a message handler:
public static void Configure(HttpConfiguration config)
{
var authNConfig = new AuthenticationConfiguration();
config.MessageHandlers.Add(new MyTokenValidationHandler());
}
I have a working site aaa.com with custom membership provider
connected as http module.
I need a create OAuth 2.0 Authorization
Server + Resource Server
Main scenario of using is Authorization
Code Grant.
Here is a good example of creating what I need http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server (see source code of example)
What is my problem:
I want to bind Authorization Server to subdomain of main site -
oauth.aaa.com and when user came from external site to my
Authorization Server, this server check if user is already
authenticated on main site(aaa.com) and if yes - he must just press
Grant button and allow external site access, if no - he must enter user name and password and after this he must automatically be log in
to main site and Authorization Server must be authenticated too.
I've already connected my custom membership provider as http
module(another variant of using not impossible now) to Authorization
Server and Authorize action of this server is already authenticated
when user already logged in to main site.
I don't understand how I must configure Authorization Server(or create some customizations) that it can accept a cookie from main
site(or automatically synchronize and create a new cookie for
Authorization Server using info from already authenticated request).
I was try to configure CookieAuthenticationOptions with same cookie name as cookie name of main site and check that in cookie of main site domain is .aaa.com ...but nothing happend and variable ticket in code = null
var authentication = HttpContext.GetOwinContext().Authentication;
var ticket = authentication.AuthenticateAsync(Auth.AuthenticationType).Result;
Or may be I need another library, not based on OWIN? I saw DotNetOpenAuth library but seems its OAuth client, not server.