My objective is to have an Asp.Net Mvc action secured with OpenId authentication, and support 2 types of clients: browser and a native WPF application. The STS I will use is ADFS 2016.
Currently clients browsers works well. For this, I have UseOpenIdConnectAuthentication configured in my startup class.
I'm able to call my Mvc action (secured with Authorize attribute), user is redirected to STS, and once authentication is done, I come back to my Mvc action with a ClaimsIdentity properly filled.
Now I'm trying to have a native WPF app able to authenticate to the same Mvc action in the same Web app, and things are getting tricky.
On the client side (my WPF application), I'm using ADAL and the following code:
var authContext = new AuthenticationContext("<MySTSUri>");
var authResult = await authContext.AcquireTokenAsync(
"http://localhost:1276/openid/login",
"MyNativeAppId",
new Uri("myapp://openid"),
new PlatformParameters(PromptBehavior.Auto),
UserIdentifier.AnyUser,
"");
if (!string.IsNullOrEmpty(authResult.AccessToken))
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(authResult.AccessTokenType, authResult.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync("http://localhost:1276/openid/login");
if (response.IsSuccessStatusCode)
{
var text = await response.Content.ReadAsStringAsync();
}
}
}
The problem is basically that I can't tell the Web app to be able to validate this type of ADAL request.
I've tried various things in the Web application Owin startup file configuration:
leaves UseOpenIdConnectAuthentication: it doesn't seem sufficient, I'm redirected to STS with the ClientId of the Web application
UseActiveDirectoryFederationServicesBearerAuthentication api since I know my STS will always be an ADFS
UseOAuthBearerAuthentication
None of them are working.
Please can someone help how to achieve this?
Am I going in the right direction?
Any ideas/pointers would be greatly appreciated.
Thanks,
Alex
I've managed to get it working. I post the answer for the record.
What helped me a lot is to enable Owin logs in the web.config:
<system.diagnostics>
<switches>
<add name="Microsoft.Owin" value="Verbose" />
</switches>
</system.diagnostics>
Then with Owin, you can simply chain multiple authentication methods. So in my case, I've just used:
app.UseActiveDirectoryFederationServicesBearerAuthentication(
new ActiveDirectoryFederationServicesBearerAuthenticationOptions
{
MetadataEndpoint = adfsMetadataEndpoint,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudiences = new[] { validAudience }
}
});
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
AuthenticationType = "OpenId",
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
Scope = "openid",
SignInAsAuthenticationType = "Cookies"
});
Cheers,
Alex
Related
I was tasked with writing an ASP.NET website that uses Azure Active Directory. I went with the route of OAuth and OpenID Connect. I am not able to use implicit flow and therefore must set the ResponseType to be code.
Using MSAL code samples I got most of it working but the problem is that all the samples are using a response type that returns tokens. I think I need to do it in 2 separate steps, first get the authorization code and then get the id token. I'm not exactly sure how to do this and would much appreciate some guidance here.
I have a startup class that look like this:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = authority,
ClientId = clientId,
RedirectUri = redirectUri,
Scope = "openid profile email offline_access user.readbasic.all", // a basic set of permissions for user sign in & profile access
ResponseType = OpenIdConnectResponseType.Code,
ClientSecret = clientSecret,
TokenValidationParameters = new TokenValidationParameters
{
// In a real application you would use ValidateIssuer = true for additional checks and security.
ValidateIssuer = false,
NameClaimType = "name",
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
}
});
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
// Handle any unexpected errors during sign in
context.OwinContext.Response.Redirect("/Error?message=" + context.Exception.Message);
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = GroupManager.Utils.MsalAppBuilder.BuildConfidentialClientApplication(null);
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(new[] { "openid profile email offline_access user.readbasic.all" }, context.Code).ExecuteAsync();
}
How do I take the information from the result's tokens and create a claims identity for the AuthenticationTicket.Identity and access the user info?
Please note that this is an ASP.NET application. Not MVC and not Core.
If you use MSAL, you don't need to handle the code yourself. MSAL will return the token to you after you log in interactively, please see:Overview of Microsoft Authentication Library (MSAL).
Before that, you need to take a look at Add sign-in to Microsoft to an ASP.NET web app,the workflow is:
Code example please check: https://github.com/AzureAdQuickstarts/AppModelv2-WebApp-OpenIDConnect-DotNet
Update:
Try to enable ID token
I have implemented Azure Active Directory authentication in my asp.net web app.
startup.auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = redirectUri,
RedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
//
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
//
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed
}
});
}
when i hit application url , its going to AD authentication, when i enter my credentials after completing two factor authentication it keeps trying to connect and finally says "we couldnt signin you, please try again later". please help
Authentication issue
its single page application created in asp.net mvc with type script.
please help me to resolve this issue. thanks!!!
(Moving from Comments to answer)
The above issue is resolved by following in the Document and GitHub Sample.
I have an ASP.NET Web API site that is using an authentication cookie generated from another ASP.NET Web Form site.
Since both the ASP.NET Web API site and the ASP.NET Web Form site have the same "machineKey" and same "form auth cookie name", it would allow a user to login to the "Web Form Site" and then pull data from the "Web API Site" without re-authentication since the "auth" cookie would pass between sites.
All this was done inside that web.config and did not require any special code to work.
Now we would like to create an ASP NET Core Web API site and have that same auth cookie work.
I can't seem to find any good articles on how do this in .NET Core.
There are plenty of articles on how to us ASP Identity which we don't have and plenty of articles on auth cookies but none explaining how to get it from another site...
Here is the web.config sections that I had in the Web API:
<system.web>
<authentication>
<forms name=".TESTAUTH" cookieless="AutoDetect" requireSSL="true" domain=".test.com" slidingExpiration="true" protection="All" timeout="600"
enableCrossAppRedirects="true" />
</authentication>
<!-- This is used to share the auth cookie between web sites -->
<machineKey validationKey="SOMEKEY"
decryptionKey="SOMEOTHERKEY" validation="SHA1" decryption="AES" compatibilityMode="Framework20SP2" />
</system.web>
EDIT A
We are using "Framework20SP2" and that seems to complicate the issue.
Here is a link to some code that will decrypt the auth ticket based on "Framework20SP2"
https://github.com/dazinator/AspNetCore.LegacyAuthCookieCompat
If I use the NuGet above, it does decrypt the auth cookie in the .NET Core site but I can't seem to figure out how to get that to work in the .NET Core DataProtectionProvider.
The first thing you need to confirm is that the applications are hosted on same domain or subdomains of that domain . Otherwise consider using token/sso service like Identity Server 4/Azure AD to implement SSO .
ASP.NET 4.x apps that use Katana Cookie Authentication Middleware can be configured to generate authentication cookies that are compatible with the ASP.NET Core Cookie Authentication Middleware .Install the Microsoft.Owin.Security.Interop package into ASP.NET 4.x app to use DataProtectionProvider , which provides data protection services for the encryption and decryption of authentication cookie payload data.
You can now remove machineKey config , the Katana cookies middleware uses data protection , that doesn't rely on machine keys but on a key ring persisted ,you should ensure that it uses a persisted key storage, and that key storage is accessible and used by each application .
See below article for more details and code sample :
https://learn.microsoft.com/en-us/aspnet/core/security/cookie-sharing?view=aspnetcore-3.0
While this works, I'm not 100% sure it is the best / proper way of doing this but
here is the main class I created.
public class FormsAuthCookieTicketFormat : ISecureDataFormat<AuthenticationTicket>
{
private LegacyFormsAuthenticationTicketEncryptor _Encryptor;
private Sha1HashProvider _HashProvider;
public FormsAuthCookieTicketFormat(string decryptionKey, string validationKey)
{
byte[] decryptionKeyBytes = HexUtils.HexToBinary(decryptionKey);
byte[] validationKeyBytes = HexUtils.HexToBinary(validationKey);
_Encryptor = new LegacyFormsAuthenticationTicketEncryptor(decryptionKeyBytes, validationKeyBytes, ShaVersion.Sha1, CompatibilityMode.Framework20SP2);
_HashProvider = new Sha1HashProvider(validationKeyBytes);
}
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var ticket = _Encryptor.DecryptCookie(protectedText);
var identity = new ClaimsIdentity("MyCookie");
identity.AddClaim(new Claim(ClaimTypes.Name, ticket.Name));
identity.AddClaim(new Claim(ClaimTypes.IsPersistent, ticket.IsPersistent.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Expired, ticket.Expired.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Expiration, ticket.Expiration.ToString()));
identity.AddClaim(new Claim(ClaimTypes.CookiePath, ticket.CookiePath));
identity.AddClaim(new Claim(ClaimTypes.Version, ticket.Version.ToString()));
// Add some additional properties to the authentication ticket.
var props = new AuthenticationProperties();
props.ExpiresUtc = ticket.Expiration.ToUniversalTime();
props.IsPersistent = ticket.IsPersistent;
var principal = new ClaimsPrincipal(identity);
var authTicket = new AuthenticationTicket(principal, props, CookieAuthenticationDefaults.AuthenticationScheme);
return authTicket;
}
}
Here is the Startup.cs file
var formsCookieFormat = new FormsAuthCookieTicketFormat(decryptionKey, validationKey);
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = ".TESTCOOKIE";
options.Cookie.Domain = ".TESTDOMAIN";
options.Cookie.Path = "/";
options.Cookie.HttpOnly = false;
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.AccessDeniedPath = "/Login/";
options.LoginPath = "/Login/";
options.ReturnUrlParameter = "returnurl";
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.TicketDataFormat = formsCookieFormat;
options.SlidingExpiration = true;
});
I'm having problems in retrieving access token of an authenticated user. below is my configuration
ASP.NET MVC 5 Client:
OpenIdConnect
IdentityServer3 libraries
ResponseType = "code id_token"
ASP.NET Core Identity Server:
IdentityServer4 libraries
Client Config: AllowedGrantTypes =
GrantTypes.HybridAndClientCredentials,
I'm trying to get the access token in my client using this:
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(TokenEndpoint, "clientid", "secret");
var response = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
},
I used this reference for above implementation - https://github.com/IdentityServer/IdentityServer3/issues/2457
but the properties in the response has null values. I need the access token so that the user logged in the client can access the api. Below is another way that i'm trying to retrieve the access token:
public async Task<ActionResult> CallApiUsingUserAccessToken()
{
var user = User as ClaimsPrincipal;
var accessToken = user.FindFirst("access_token").Value;
var client = new HttpClient();
client.SetBearerToken(accessToken);
var content = await client.GetStringAsync("http://localhost:6001/api/values");
ViewBag.Json = JArray.Parse(content).ToString();
return View("json");
}
however, user.FindFirst("access_token").Value; is null. I'm thinking of migrating my MVC client to Core because I've tried the IdentityServer4 version in an asp.net core but that seems to be a big migration to my part. Thank you.
[updated]
It never occured to me that the endpoints in the IdentityServer3 differs from IDS4. I did have to change var tokenClient = new TokenClient(TokenEndpoint, "client", "secret"); to var tokenClient = new TokenClient("http://localhost:9000/connect/token", "client", "secret") since TokenEndpoint in IDS3 is http://localhost:9000/core/connect/token which the endpoint "core" does not exist in IDS4. I'm able to get the access token in this line var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri); but after authorization, i'm still getting nullreference exception to this var accessToken = user.FindFirst("access_token").Value; line of code.
Given the IdentityServer 4 documentation on
Switching to Hybrid Flow and adding API Access back
and an example client from IdentityServer3.Samples
MVC OWIN Client (Hybrid)
you should be able to setup a working environment.
To support debugging you should always do proper response handling as shown in example below and copied from example client. Add any response errors to your question.
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
Constants.TokenEndpoint,
"mvc.owin.hybrid",
"secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
Finally I recommend to add code for all important parts of an IdentityServer3/4 based setup - because the truth is usually burried in the details.
According to these posts, https://github.com/IdentityServer/IdentityServer3/issues/2457 & https://github.com/IdentityServer/IdentityServer3/issues/2015#issuecomment-172623173, it is a good practice to not include the access token in the claims. Hence, I followed his example, https://github.com/Mich-b/IdentityServerTMLClient/blob/master/IdentityServerTMLClient/Startup.cs, in which the access token is added in the Http Session storage.
I'm using
New browser only clients on the same domain
Identity 2.0
WebAPI 2.1
Owin 2.1
AngularJS front-end for registration, login and data display
In a WebAPI application with an AngularJS front-end.
I'm reading about token authentication but I am very confused now and I cannot find any good examples out there that use my combination. What I would like to know is should I be using cookies or tokens for the authentication. Should I be using a Userfactory or the CreatePerOwinContext?
Here's what I have in my Startup.Auth.cs
public partial class Startup {
public void ConfigureAuth(IAppBuilder app) {
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/"),
Provider = new CookieAuthenticationProvider {
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
}
Here's my WebAPI config:
public static class WebApiConfig
{
public static void CustomizeConfig(HttpConfiguration config)
{
config.Formatters.Remove(config.Formatters.XmlFormatter);
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
json.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-ddTHH:mmZ" });
}
I saw some examples using this code but I am not sure how I can call this:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
Could I just replace the cookie authentication with this?
Not an expert, but in my dabbling I've found that tokens work great for api and from javascript to api, and traditional cookies lean mostly for a ui. Either or both will work depending on what your trying to do.
You can follow something like this link that does cookie for the ui and token for the api http://blog.iteedee.com/2014/03/asp-net-identity-2-0-cookie-token-authentication/
app.CreatePerOwinContext(ApplicationSession.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Token Authentication
app.UseOAuthBearerAuthentication(new OAuthBearerOptions());
I think you can set the cookie authentication options authentication type to bearer if you want bearer for both, but you would have to play with it. The token would be in the owincontext under ".AspNet.ExternalBearer".
I also think if you register the Identity 2.0 middleware i think it also registers the oauth middleware stuff so you don't need to register the oauthserver middleware yourself. Thats the OAuthAuthorizationServerOptions code you posted. You dont need it.
if the ui and api are in separate then its a bit harder if you want to do some sort of single sign on from the ui pass to the api. I would recommend looking at opensource identity server or authorization server from thinktecture.
If your set on owin middleware and Identity 2.0 you would need to make sure the token can be read by both application and api and you probably would need to implement ISecureDataFormat. But remember, decryption doesn't mean you can 100% trust a token, it should be signed and verified. Depends on your needs.
Sorry, I guess thats a long ramble... Good luck.