I have try to use OpenID to connect with Azure AD, and I use the exact code from the tutorial https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-asp-webapp with no luck.
My Startup:
public class Startup
{
string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture,
System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
/// <summary>
/// Configure OWIN to use OpenIdConnect
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.IdToken,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
}
}
);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
My Login Page Code:
protected void Page_Load(object sender, EventArgs e)
{
try
{
if (!IsPostBack)
{
if (!Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/AMS/Dashboard" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
else
{
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
lblErrorMessage.InnerHtml = userClaims?.FindFirst("preferred_username")?.Value;
//check user info, and create session then redirect to Dashboard
}
}
}
catch (Exception ex)
{
//handle error
}
}
My Website structure is a little bit complicated as follow:
I have a website on the server x: mydomain.com
and I have a subdomain in server y: subdomain.mydomain.com
and I have my website AMS on server z with a redirect to subdomain.mydomain.com/AMS
now to solve cross-site cookie I use the following in web config
<outboundRules>
<rule name="Ensure httpOnly Cookies" preCondition="Missing httpOnly cookie">
<match serverVariable="RESPONSE_Set_Cookie" pattern="^(.*; path=/)" negate="false" />
<action type="Rewrite" value="{R:1}AMS; SameSite=none; secure; HttpOnly" />
</rule>
<preConditions>
<preCondition name="Missing httpOnly cookie">
<!-- Don't remove the first line! -->
<add input="{RESPONSE_Set_Cookie}" pattern="." />
<add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=none; secure; HttpOnly" negate="true" />
</preCondition>
</preConditions>
</outboundRules>
My Problem is Request.IsAuthenticated is always false, so the page keep redirect to the Microsoft login page
Any ideas?
Thanks in advance
Instead of the rewrite rule try this:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieSameSite = Microsoft.Owin.SameSiteMode.None,
CookieSecure = CookieSecureOption.Always
});
Also ensure the secure attribute is set as well
From https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
SameSite NONE - Cookies will be sent in all contexts, i.e in responses to both first-party and cross-origin requests. If SameSite=None is set, the cookie Secure attribute must also be set (or the cookie will be blocked).
Related
I have an issue regarding an .net core app (3.1) trying to consume a WS (SOAP) from a Dynamics Nav Server. (on-premise).
When I'm in debugging mode everything works fine, but when I deployed the app to local IIS server I keep getting
"The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'oRswGaADCgEAoxIEEAEA=,Negotiate'."
Client initialization
private async Task<TXSiteUser_PortClient> GetInstanceAsync()
{
EndpointAddress endpointAddress = new EndpointAddress(serviceUrl);
BasicHttpBinding basicHttpBinding = new BasicHttpBinding()
{
MaxReceivedMessageSize = int.MaxValue,
MaxBufferSize = int.MaxValue,
OpenTimeout = TimeSpan.MaxValue,
CloseTimeout = TimeSpan.MaxValue,
ReceiveTimeout = TimeSpan.MaxValue,
SendTimeout = TimeSpan.MaxValue
};
return await Task.Run(() => new TXSiteUser_PortClient(basicHttpBinding, endpointAddress));
}
Example of call to a WS
var client = await GetInstanceAsync();
var user = await client.ReadAsync(new Read { Id = id });
await client.CloseAsync();
return user.TXSiteUser;
The Dynamics server has the credential type "Windows".
And below is the web.config
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath=".\ArtoilPortal.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
The weird thing is that when I'm in debugging mode and testing the endpoints from Swagger, everything works ok. Also, everytime I try to use postman I get a 401, no matter of Auth type (Basic,NTML).
Also i've tried adding
basicHttpBinding.Security.Mode = BasicHttpSecurityMode.Transport;
basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
var username = _appSettings.NAVCredentials.UserName;
var password = _appSettings.NAVCredentials.Password;
and extended the PortClient method like this
public TXSiteUser_PortClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress, string userName, string password) :
base(binding, remoteAddress)
{
this.ChannelFactory.Credentials.UserName.UserName = userName;
this.ChannelFactory.Credentials.UserName.Password = password;
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
with no luck
managed to solve the errors.
Firstly, the client initialization code was fixed by using "TransportCredentialOnly" instead of "Transport"
basicHttpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
TransportCredentialOnly mode is used when there's no SSL configured for the SOAP services on Dynamics NAV server.
And the version of "PortClient" method that works for me looks like this:
public TXSiteUser_PortClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress, string userName, string password) :
base(binding, remoteAddress)
{
this.ClientCredentials.Windows.ClientCredential.UserName = userName;
this.ClientCredentials.Windows.ClientCredential.Password = password;
this.ClientCredentials.Windows.ClientCredential.Domain = "";
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
In the original post I was setting
this.ChannelFactory.Credentials.UserName.UserName = userName;
Where it should've been
this.ClientCredentials.Windows.ClientCredential.UserName = userName;
Hopefully this will help somebody at some point
I am trying to get gain an Access Token after receiving the authorisation token via postback from Azure.
I simply serve the Home view where there is a button to logon to Azure. Initially i followed this post: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-asp-webapp but then tried to modify the code to follow the Authorisation Code Flow.
Strangely it was working nicely (after googling for days) when hosted on IIS Express but when moving to local IIS I am only able to complete half the flow. I wonder if it's significant that my app is running as an Application on IIS not as a website? The address is https://localhost/testapp. My site on IIS runs on port 80 and 443 as standard. IIS is as per (apologies for the heavy redaction):
In essence the code redemption is not taking place.
Here is my owin Startup.cs:
string clientId = ConfigurationManager.AppSettings["ClientId"];
string clientSecret = ConfigurationManager.AppSettings["ClientSecret"];
string redirectUri = ConfigurationManager.AppSettings["RedirectUri"];
static string tenant = ConfigurationManager.AppSettings["Tenant"];
string authority = string.Format(System.Globalization.CultureInfo.InvariantCulture, ConfigurationManager.AppSettings["Authority"], tenant);
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
ClientSecret = clientSecret,
Authority = authority,
RedirectUri = redirectUri, //struggling to see the difference between RedirectUri and CallbackPath
CallbackPath = new PathString("/home/"), // do i need this as well?
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
RedeemCode = true,
ResponseMode = OpenIdConnectResponseMode.FormPost, // do i need this?
SaveTokens = true, // do i need this?
UsePkce = true, // default is true
ResponseType = OpenIdConnectResponseType.Code,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
},
}
);
}
My home controller is simply:
public class HomeController : Controller
{
[AllowAnonymous]
public ActionResult Index()
{
return View();
}
[AllowAnonymous]
public void SignIn()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
/// <summary>
/// Send an OpenID Connect sign-out request.
/// </summary>
[AllowAnonymous]
public void SignOut()
{
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType,
CookieAuthenticationDefaults.AuthenticationType);
}
}
web.config:
<add key="ClientId" value="xxxxxxxxxxxx" />
<add key="ClientSecret" value="xxxxxxxxxxxx" />
<add key="redirectUri" value="https://localhost/testapp/home/" />
<add key="Tenant" value="xxxxxxxxxxxx" />
<add key="Authority" value="https://login.microsoftonline.com/{0}/v2.0" />
On Azure the Redirect URI is:
this is the trace from fiddler:
You can see from the trace that there is not further call to the token endpoint even though I believe I have the correct configuration for OWIN to make the call automatically?
I am really stumped and I would appreciate some help please.
Trying to fix this Azure Active Directory issue. I have an ASP.Net 4.7 website. It correctly takes me to the SSO page and confirms my identity. However, upon taking me back to my site, I get the message:
AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application: 'MyApplication-ClientID'.
It makes sense that the AD App Registration's | Authentication | Redirect URI does not match what I am sending it. However, as near as I can tell, they do.
Here is my code in the Startup.cs file:
public void Configuration(IAppBuilder app) {
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
ClientId = clientId, // MyApplication-ClientID
Authority = authority, // https://login.microsoftonline.com/MyDirectory-TenantID/v2.0
RedirectUri = redirectUri, // https://MySiteName.azurewebsites.net
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.IdToken,
TokenValidationParameters = new TokenValidationParameters() {
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications {
AuthenticationFailed = OnAuthenticationFailed
}
}
);
}
Here are the settings in my Web.config file:
<add key="ClientId" value="MyApplication-ClientID" />
<add key="Tenant" value="common" />
<add key="Authority" value="https://login.microsoftonline.com/MyDirectory-TenantID/v2.0" />
<add key="RedirectUri" value="https://MySiteName.azurewebsites.net" />
Both the Redirect URI and the Configs RedirectUri are: https://MySiteName.azurewebsites.net
Here is the initial request when I am asked to select an account when trying to log in:
https://login.microsoftonline.com/MyDirectory-TenantID/oauth2/v2.0/authorize
?response_type=code+id_token
&redirect_uri=https%3A%2F%2FMySiteName.azurewebsites.net%2F.auth%2Flogin%2Faad%2Fcallback
&client_id=MyApplication-ClientID
&scope=openid+profile+email
&response_mode=form_post
&nonce=3f63a75d79af449082801c5183d5fbdb_20200710145808
&state=redir%3D%252F
So I see the redirect_uri seems to add the /.auth/login/aad/callback to the end of what I told it to be. So I updated the AD App Registration's | Authentication | Redirect URI to match and instead of the above error I get the following error:
The page cannot be displayed because an internal server error has occurred.
I am just at a loss here trying to figure out what is mismatched or perhaps just missing.
I'm trying to add authentication option to support Azure AD in an asp.net application. The web.config file has some options:
<add key="ida:FederationMetadataLocation" value="https://login.microsoftonline.com/common/FederationMetadata/2007-06/FederationMetadata.xml" />
<add key="ida:Realm" value="..." />
<add key="ida:LogoutReply" value="..." />
<add key="ida:OwinWsFederationEnabled" value="true" />
In addition, the authentication is configured in code like this:
public partial class Startup{
public void ConfigureAuth(IAppBuilder app){
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString(...),
LogoutPath = new PathString(...),
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ExternalCookie",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
});
var authenticationOptions = new WsFederationAuthenticationOptions()
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
MetadataAddress = metadata,
Wtrealm = realm,
Wreply = reply,
Caption = caption,
SignOutWreply = logoutreply,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false
}
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
};
app.UseWsFederationAuthentication(authenticationOptions);
}
}
This kind of works, because it redirects me to azure login and back to the app login path. But there, when all is supposed to be ok, I'm not getting any information about the newly logged in user.
var oid = GetClaim("http://schemas.microsoft.com/identity/claims/objectidentifier");
with this or any other claim type, for that matter. All the claims are returning null-values.
The app is registered to my Azure AD directory as multitenant.
If I set the metadatalocation to be the tenant specific metadata, the AAD won't redirect me back to the application as the azure ad user is from another tenant.
What am I missing here to get the user claims from Azure AD?
I need to authenticate User from a central CAS. The assumption are these:
The UserId for authentication is in a Request Header
The roles for authorization are given by a web service.
The application must cache the authorization phase.
I've tried this:
In the Global.asax:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
const string SiteMinderHeaderToken = "SM_USER";
if (HttpContext.Current.User == null || !HttpContext.Current.User.Identity.IsAuthenticated)
{
var userSSO = HttpContext.Current.Request.Headers[SiteMinderHeaderToken];
GenericIdentity webIdentity = new GenericIdentity(userSSO, "SiteMinder");
string[] roles = { "ROLE1", "ROLE2" };
GenericPrincipal principal = new GenericPrincipal(webIdentity, roles);
HttpContext.Current.User = principal;
// System.Web.Security.FormsAuthentication.SetAuthCookie(userSSO, true);
}
}
In the Web.config
<authentication mode="None" />
<authorization>
<deny users="?" />
</authorization>
The problem is that for every request, the HttpContext.Current.User is always null, and every time all the authentication and authorization phase are done.
If I uncomment
System.Web.Security.FormsAuthentication.SetAuthCookie(userSSO, true);
All is fine, after the first request the User is authenticated.
My questions are:
Is it correct to call System.Web.Security.FormsAuthentication.SetAuthCookie even if there isn't FormAuthentication?
Is there a way to do it better?
Are there some security issues doing this way?
Thanks
FormsAuthentication in this case is just storing the users session.
Here's some more info: Understanding the Forms Authentication Ticket and Cookie https://support.microsoft.com/en-us/kb/910443/
You can set the auth cookie this way, but if would like to store more of the users info within the session, I would recommend using claims.
FormsAuthentication cookie is secure. "The ticket is encrypted and signed using the configuration element of the server's Machine.config file. ASP.NET 2.0 uses the decryptionKey and the new decryption attribute of the element to encrypt forms authentication tickets." There's more information in the link I gave above. I guess the only security issue would be how the user is accessing your code "FormsAuthentication.SetAuthCookie(userSSO, true);"
Try looking into Owin Authentication to store the users session.
http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server
Here's an example of how I use it in my MVC project.
Installed nugget packages:
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Security.OAuth
Microsoft.Owin.Security.Cookies
Created OwinConfig.cs file (in my App_Start folder):
using Microsoft.Owin;
[assembly: OwinStartup(typeof(MyProject.OwinConfig))]
namespace MyProject
{
using System.Web.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;
/// <summary>
/// The startup.
/// </summary>
public class OwinConfig
{
/// <summary>
/// The configuration.
/// </summary>
/// <param name="app">
/// The app.
/// </param>
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = FormsAuthentication.FormsCookieName,
Provider = new CookieAuthenticationProvider()
});
}
}
}
Code I use to log a user in:
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, 1)),
new Claim("CustomClaim", "My bio or something"),
new Claim(ClaimTypes.Name, "set_username")
};
var userWithClaims = new ClaimsIdentity(claims, FormsAuthentication.FormsCookieName);
var owinContext = HttpContext.Current.Request.GetOwinContext();
var authenticationManager = owinContext.Authentication;
authenticationManager.SignIn(userWithClaims);
And then I access the stored claims by creating IdentityExtensions.cs class (also in my app_start folder):
public static class IdentityExtensions
{
public static string GetCustomClaim(this IIdentity identity)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
var ci = identity as ClaimsIdentity;
return ci != null ? ci.FindFirstValue("CustomClaim") : null;
}
private static string FindFirstValue(this ClaimsIdentity identity, string claimType)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
var claim = identity.FindFirst(claimType);
return claim != null ? claim.Value : null;
}
}
So in my controller I could just call:
string customClaim = this.HttpContext.User.Identity.GetCustomClaim();
Then finally, to logout I would use:
var owinContext = HttpContext.Current.Request.GetOwinContext();
var authenticationManager = owinContext.Authentication;
authenticationManager.SignOut();