IdentityServer4 - is it possible to use local login form with external provider and no round trip? - azure

I'm trying to use a local login form to authenticate a user credentials against its external provider (Azure Active Directory).
I understand that, per client, you can enable local login. That helps, as when set to true, I'll get the local login form but but I'm still unclear as to how to fire off the middle ware for that external provider. Is there a way to send client credentials to the external provider to receive an ID token? My current code redirects to the Microsoft login; and then back to my identity server, and then the client application. I want the user to login in through identity server but not have them know it's really authenticating against Azure.
Here's my start up:
var schemeName = "Azure-AD";
var dataProtectionProvibder = app.ApplicationServices.GetRequiredService<IDataProtectionProvider>();
var distributedCache = app.ApplicationServices.GetRequiredService<IDistributedCache>();
var dataProtector = dataProtectionProvider.CreateProtector(
typeof(OpenIdConnectMiddleware).FullName,
typeof(string).FullName, schemeName,
"v1");
var dataFormat = new CachedPropertiesDataFormat(distributedCache, dataProtector);
///
/// Azure AD Configuration
///
var clientId = Configuration["AzureActiveDirectory:ClientId"];
var tenantId = Configuration["AzureActiveDirectory:TenantId"];
Redirect = Configuration["AzureActiveDirectory:TenantId"];
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = schemeName,
DisplayName = "Azure-AD",
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
ClientId = clientId,
Authority = $"https://login.microsoftonline.com/{tenantId}",
ResponseType = OpenIdConnectResponseType.IdToken,
StateDataFormat = dataFormat,
});
app.UseIdentity();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
This is the login.
[HttpGet]
public async Task<IActionResult> ExternalLogin(string provider, string returnUrl)
{
var context = this.HttpContext.Authentication;
List<AuthenticationDescription> schemes = context.GetAuthenticationSchemes().ToList();
returnUrl = Url.Action("ExternalLoginCallback", new { returnUrl = returnUrl });
// start challenge and roundtrip the return URL
var props = new AuthenticationProperties
{
RedirectUri = returnUrl,
Items = { { "scheme", provider } }
};
//await HttpContext.Authentication.ChallengeAsync(provider, props);
return new ChallengeResult(provider, props);
}

In my opinion ,we shouldn't directly pass the username/password directly from other Idp to azure AD for authentication as a security implementation .And even Azure AD supports the Resource Owner Password Credentials Grant ,it's only available in native client. I suggest you keep the normal way and don't mix them .

Related

Azure App Service Authentication / Authorization and Custom JWT Token

In my web project i want to enable the user to login with username / password and Microsoft Account.
Tech - Stack:
Asp.Net Core WebApi
Angular
Azure App Service
First i created the username / password login. Like this:
StartUp.cs:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(Configuration["JWTKey"].ToString())),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true
};
});
Login Method:
public async Task<IActionResult> ClassicAuth(AuthRequest authRequest)
{
tbl_Person person = await _standardRepository.Login(authRequest.Username, authRequest.Password);
if (person != null)
{
var claims = new[]
{
new Claim(ClaimTypes.GivenName, person.PER_T_Firstname),
};
var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_config["JWTKey"].ToString()));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddHours(24),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return Ok(tokenHandler.WriteToken(token));
}
else
return Unauthorized("Invalid login data");
}
And secured my api enpoints with [Authorize].So far so good...that works.
Now i want to add a login method with Microsoft Account. I use Azure App Service Authentication / Authorization for that (https://learn.microsoft.com/de-de/azure/app-service/overview-authentication-authorization).
I configured the auth provider and i'm able to start the auth flow with a custom link in my angular app:
Login with Microsoft - Account
This works and i can retrieve the access token from my angular app with this:
this.httpClient.get("https://mysite.azurewebsites.net/.auth/me").subscribe(res => {
console.log(res[0].access_token);
});
Now the problem:
access_token seems not a valid JWT Token. If i copy the token and go to https://jwt.io/ it is invalid.
When i pass the token to my API i get a 401 - Response. With seems logical because my API checks if the JWT Token is signed with my custom JWT Key and not the Key from Microsoft.
How can I make both login methods work together? I may have some basic understanding problems at the moment.
It seems you want your Angular app calling an ASP.NET Core Web API secured with Azure Active Directory, here is a sample works well for that.
The most important step is register the app in AAD.
By the way, if you want to enable users to login one project with multiple ways in azure, you can use multiple sign-in providers.

OWIN Authentication with Azure AD B2C OP: SIgnOut Doesn't Remove Cookie when Called from External iFrame

I have an authentication module in Umbraco using OWIN/OIDC, authenticating against our Azure AD B2C resource. As part of this module, there is a LogOut controller method, which is working correctly.
We are trying to develop single sign out for applications within our Azure tenant. We are still working on having Azure AD B2C call the logout method for each application. In order to test initiation of sign out from other applications, I have set up an iframe in one of our custom applications (also authenticating via Azure AD B2C) that calls the LogOut method in our Umbraco implementation when users sign out from that application. I can see that the LogOut method is being called when the external method opens the iframe, and all of the objects look the same as when the method is called from within Umbraco. However, the user is not logged off of the application. The authentication cookie, which is .AspNet.ApplicationCookie, has SameSite as None, Secure as true and HttpOnly as false, but it is not removed as it is when Umbraco calls the method.
Any tips on how to get the LogOut method to work from the external application would be appreciated.
Here is my configuration:
private void ConfigureAzureB2CAuthentication(object sender, OwinMiddlewareConfiguredEventArgs args) {
//get appbuilder
AppBuilder app = (AppBuilder)args.AppBuilder;
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
app.UseCookieAuthentication(Current.Factory.GetInstance<FrontEndCookieAuthenticationOptions>(), PipelineStage.Authenticate);
//Set configuration on appbuilder
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
MetadataAddress = string.Format(
ConfigurationManager.AppSettings["ida:AzureInstance"],
ConfigurationManager.AppSettings["ida:Tenant"],
ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"]),
ClientId = ConfigurationManager.AppSettings["ida:ClientId"],
RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"],
PostLogoutRedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"],
Notifications = new OpenIdConnectAuthenticationNotifications {
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed
},
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters {
NameClaimType = ConfigurationManager.AppSettings["ida:ClaimsLabelEmail"],
ValidateIssuer = false
},
Scope = ConfigurationManager.AppSettings["ida:ScopesOpenIDConnect"],
});
//reafirm backoffice and preview authentication
app.UseUmbracoBackOfficeCookieAuthentication(_umbracoContextAccessor, _runtimeState, _userService, _globalSettings, _securitySection, PipelineStage.Authenticate)
.UseUmbracoBackOfficeExternalCookieAuthentication(_umbracoContextAccessor, _runtimeState, _globalSettings, PipelineStage.Authenticate)
.UseUmbracoPreviewAuthentication(_umbracoContextAccessor, _runtimeState, _globalSettings, _securitySection, PipelineStage.PostAuthenticate);
}
and this is the LogOut method:
public void LogOut(string redirectUrl = "/") {
if (Request.IsAuthenticated) {
RemoveLoggedInMemberAccessToken();
IEnumerable<AuthenticationDescription> authTypes = HttpContext.GetOwinContext().Authentication.GetAuthenticationTypes();
AuthenticationProperties authenticationProperties = new AuthenticationProperties { RedirectUri = redirectUrl };
HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties, authTypes.Select(t => t.AuthenticationType).ToArray());
}
}

ADFS : Acquiring a Token programmatically

So we have built a set of Azure Functions that are secured by ADFS (MSAL)
We have configured an App in ADFS and got it all working well with our Android client.
We now want to do some API testing so we want to programmatically generate Auth tokens to test the API's
I can't get the following code to work at all, maybe I have the tenant ID wrong, in the App config, its a GUID (42b03d0b-d7f2-403e-b764-0dbdcf0505f6), but examples say it's our domain
string userName = "-";
string password = "-";
string clientId = "ee13c922-bf4b-4f0a-ba39-ea74e1203c6e";
var credentials = new UserPasswordCredential(userName, password);
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/acostaonline.onmicrosoft.com");
var result = await authenticationContext.AcquireTokenAsync("https://graph.windows.net", clientId, credentials);
UPDATE
So changed the code to be MSAL and still trying to login via username and password. Now it just times out
string authority = "https://login.microsoftonline.com/42b03d0b-d7f2-403e-b764-0dbdcf0505f6/";
string[] scopes = new string[] { "user.read" };
PublicClientApplication app = new PublicClientApplication("ee13c922-bf4b-4f0a-ba39-ea74e1203c6e", authority);
var accounts = await app.GetAccountsAsync();
Microsoft.Identity.Client.AuthenticationResult result = null;
if (accounts.Any())
{
result = await app.AcquireTokenSilentAsync(scopes, accounts.FirstOrDefault());
}
else
{
try
{
var securePassword = new SecureString();
foreach (char c in "PASSWORD") // you should fetch the password keystroke
securePassword.AppendChar(c); // by keystroke
result = await app.AcquireTokenByUsernamePasswordAsync(scopes, "AUSER#acosta.com",
securePassword);
}
}
Error
SocketException: A connection attempt failed because the connected
party did not properly respond after a period of time, or established
connection failed because connected host has failed to respond
172.26.200.77:443
It seems that the code you provided is using ADAL instead of MSAL.
The main difference is that with ADAL you would use an AuthenticationContext to acquire tokens, whereas in MSAL you use ConfidentialClientApplication or PublicClientApplication, depending on if the application is running in a back-end or on the user's device.
Here is the article about Differences between ADAL.NET and MSAL.NET applications.
When you use MSAL.Net to get a token for the Microsoft Graph API, you could use the following code:
public static PublicClientApplication PublicClientApp = new
PublicClientApplication(ClientId);
var app = App.PublicClientApp;
ResultText.Text = string.Empty;
TokenInfoText.Text = string.Empty;
var accounts = await app.GetAccountsAsync();
authResult = await app.AcquireTokenSilentAsync(_scopes, accounts.FirstOrDefault());
For more details, you could refer to this article, in left menu also includes Android and iOS.

Add Claims (asp.net core mvc OpenID Owin Katana Authentication )

I am following this tutorial link. I am able login with azure ad user. but once the user gets authenticated. we want to store it into Identity claims for authentication.
We are migrating Asp.net MVC application into asp.net core MVC 1.0. In Asp.net MVC application we are adding the claims like this
context.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim("urn:Projectname:access_token", result.AccessToken, XmlSchemaString, "Projectname"));
I want to know how to add the claims identity in the above tutorial.
Code Snippet
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = clientId,
ClientSecret = clientSecret,
Authority = authority,
CallbackPath = Configuration["AzureAd:AuthCallback"],
ResponseType = OpenIdConnectResponseType.CodeIdToken,
PostLogoutRedirectUri = "/signed-out",
Events = new OpenIdConnectEvents()
{
OnAuthorizationCodeReceived = async context =>
{
var request = context.HttpContext.Request;
var currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host,request.PathBase, request.Path);
var credential = new ClientCredential(clientId, clientSecret);
var authContext = new AuthenticationContext(authority, AuthPropertiesTokenCache.ForCodeRedemption(context.Properties));
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.ProtocolMessage.Code, new Uri(currentUri), credential, resource);
// In result variable , we are getting the AccessToken and we want to add this into claims identity here.
context.HandleCodeRedemption();
}
}
});
Update
we are storing tokens,domain name ( Getting it from DB), Tenant Info for middle layer Authentication. Like in very controller action methods, we are getting the stored info from claims.
Something like that(Old Asp.net MVC Application code).
In Startup.Auth.cs class
In All controller action methods
We are migrating Asp.net MVC application into asp.net core MVC 1.0. So is there any equivalent method in asp.net core for adding the claims. I am following This sample. I am able login with azure ad user. but once the user gets authenticated. we want to store it into Identity claims for authentication(middle layer).
The Code
ClaimsPrincipal claimsPrincipal = await TransformClaims(context.Ticket.Principal, result);
context.Ticket = new AuthenticationTicket(
claimsPrincipal,
context.Ticket.Properties,
context.Ticket.AuthenticationScheme);
TransformClaims method Something like that
private Task<ClaimsPrincipal> TransformClaims(ClaimsPrincipal principal, AuthenticationResult result)
{
if (principal.Identity.IsAuthenticated)
{
// get this from cache or db
var nickname = "Nanu";
(principal.Identity as ClaimsIdentity).AddClaim(new Claim("Nickname", nickname));
(principal.Identity as ClaimsIdentity).AddClaim(new Claim("urn:innubex:access_token", result.AccessToken));
}
return Task.FromResult(principal);
}
Access the claims
string accesstoken = "", Nickname = "";
var claimsIdentity = User.Identity as ClaimsIdentity;
if (claimsIdentity.IsAuthenticated)
{
accesstoken = claimsIdentity.FindAll("urn:access_token").FirstOrDefault().Value;
Nickname = claimsIdentity.FindAll("Nickname").FirstOrDefault().Value;
}
This is how I was able to login using Claims Identity:
using System.Security.Claims;
private void registerLogin(Person person)
{
var userClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, person.LoginName),
new Claim(ClaimTypes.GivenName, person.FirstName),
new Claim(ClaimTypes.Surname, person.LastName),
new Claim(ClaimTypes.Email, person.Email)
};
var principal = new ClaimsPrincipal(new ClaimsIdentity(userClaims, "local"));
Context.Authentication.SignInAsync("PutNameHere", principal);
}

Skipping home realm discovery with Ws-Federation OWIN Middleware

Our Mvc/WebAPI solution currently has four trusted identity providers which we have registered in ADFS3. Each of these identity providers can be used by our users by direct links, effectively working around any home-realm-cookies that ADFS may have created (eg: www.ourportal.com/accounts/facebook or www.ourportal.com/accounts/twitter). Currently we are migrating from WIF to OWIN but will keep using WS-Federation protocol for the time being by implementing wsfederation and cookie authentication middleware. When using WIF, we did the following in order to go directly to a known identity provider:
var signInRequest = new SignInRequestMessage(stsUrl, realm) { HomeRealm = homeRealm };
return new RedirectResult(signInRequest.WriteQueryString());
This seems to have two concerning behaviors, it does not pass the WsFedOwinState parameter, and on the return back to the Relying Party, the Home.cshtml is built (with a windows principal) before the the Owin authentication middleware is fired. The Home.cshtml being fired before the Owin middleware is the most concering as this view relies on Claims that would is provided in the transformation done by the authentication pipeline, which is fired afterwards and thus our view does not work. It works in the correct order when going to the portal in the normal way (eg www.ourportal.com)
I understand that in order to provide the Whr parameter, you do the following when configuring the ws-federation middleware:
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.Whr = "SomeUrnOfAnIdentityProvider";
return Task.FromResult(0);
}
but this sets a single identity provider for the whole solution and does not allow our users to go directly to one of a list of identity providers.
The non-working method which builds the sign-in-request is currently:
private RedirectResult FederatedSignInWithHomeRealm(string homeRealm)
{
var stsUrl = new Uri(ConfigurationManager.AppSettings["ida:Issuer"]);
string realm = ConfigurationManager.AppSettings["ida:Audience"];
var signInRequest = new SignInRequestMessage(stsUrl, realm)
{
HomeRealm = homeRealm
};
HttpContext.Request.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return new RedirectResult(signInRequest.WriteQueryString());
}
The ws-federation and cookie middleware are configured as the first middleware in OWIN startup and the default authentication is set to
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
I think I found a solution. The new method for skipping the home realm screen would be like this :
private void FederatedSignInWithHomeRealm(string homeRealm)
{
HttpContext.Request
.GetOwinContext()
.Authentication
.SignOut(CookieAuthenticationDefaults.AuthenticationType);
var authenticationProperties = new AuthenticationProperties { RedirectUri = "/" };
authenticationProperties.Dictionary.Add("DirectlyToIdentityProvider", homeRealm);
HttpContext.GetOwinContext().Authentication.Challenge(authenticationProperties);
}
And the OWIN WS-Federation middleware would be configured like this :
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = notification =>
{
string homeRealmId = null;
var authenticationResponseChallenge = notification.OwinContext
.Authentication
.AuthenticationResponseChallenge;
var setIdentityProvider = authenticationResponseChallenge != null
&& authenticationResponseChallenge.Properties
.Dictionary
.TryGetValue("DirectlyToIdentityProvider", out homeRealmId);
if (setIdentityProvider)
{
notification.ProtocolMessage.Whr = homeRealmId;
}
return Task.FromResult(0);
}
},
MetadataAddress = wsFedMetadata,
Wtrealm = realm,
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = realm
}
});

Resources