Asp.net core API authenticate with AzureAD Token - authentication

I have a xamarin mobile app that users Azure ad to authenticate it's users.
I would like to use the the token that is stored on the app to access data on an api. I have this working on an old api but I have created an asp.net core api and i would like to use the same token as i slowly migrate the data access from one api to the other.
I have set up access in the asp.net core startup class as follows
services.AddAuthentication(sharedoptions =>
{
sharedoptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
sharedoptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://login.microsoftonline.com/{ad name}.onmicrosoft.com";
options.Audience = "api://{app service guid}";
options.TokenValidationParameters = new `enter code here`Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudiences = new List<String> { "{app service guid}" },
ValidIssuers = new List<string> { "[https://login.microsoftonline.com/{Azure AD guid}/v2.0/token"}
};
});
I test with post man and get unauthorised status on the asp.net core api, but an OK (authorised) status on the old api so i know the token is working.
Does my set up appear to be correct or am i missing some configuration?

You can change your this piece of code like below in case you are using B2C:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer("ADB2C", jwtOptions =>
{
jwtOptions.Authority = $"{appConfiguration.AppSettings.Adb2cInstance}/{appConfiguration.AppSettings.Adb2cDomain}/{appConfiguration.AppSettings.Adb2cPolicy}/v2.0/";
jwtOptions.Audience = appConfiguration.AppSettings.Adb2cClientId;
jwtOptions.RequireHttpsMetadata = bool.Parse(configuration["AppSettings:RequireHttpsMetadata"]); //// NOTE:: This is set only for dev purposes. Remove for higher environments.
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = arg =>
{
// invoked if authentication fails
return Task.FromResult(0);
}
};
});
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().AddAuthenticationSchemes("ADB2C").Build();
});

Related

TokenValidationParameters validation for multi-tenant ASP.NET Core Web API application

This code is working for a single tenant application. How does it need to be changed to work with multi-tenant application (Web API)? Is setting ValidateIssuer = false the right way?
sample
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
var azureAdOptions = new AzureADOptions();
Configuration.Bind("AzureAd", azureAdOptions);
options.Authority = $"{azureAdOptions.Instance}{azureAdOptions.TenantId}/v2.0";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = SSOAuthHelper.GetValidAudiences(Configuration),
ValidIssuers = SSOAuthHelper.GetValidIssuers(Configuration),
AudienceValidator = SSOAuthHelper.AudienceValidator
};
});

How do I get more recent userinfo with AddJwtBearer

We use IdentityServer to handle SSO authentication across our apps.
My application is an Aspnet core 3.0 website that passes the users Token to javascript. The javascript then calls a separate aspnet 2.2 API.
Problem: Logging a user out and back in does not update the ClaimsPrincipal on the API with new claims.
I have confirmed that the Web application has the new claims.
If I login Incognito or clear my cookies the new claim shows up in the API.
I am not sure where the responsibility for getting the claims should be and how to fix it. I assume the claims are part of the encrypted access_token, therefore I assume the Web app is sending a stale access_token to the API. So is the Web App what I need to fix? And what would be the proper fix?
Api Startup Code
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Bearer";
options.DefaultChallengeScheme = "Bearer";
})
.AddJwtBearer(options =>
{
options.Authority = oidcSettings.Authority;
options.Audience = oidcSettings.ApplicationName;
options.RequireHttpsMetadata = true;
});
Web App Startup Code
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.OnAppendCookie = cookieContext => { cookieContext.CookieOptions.SameSite = SameSiteMode.None; };
options.OnDeleteCookie = cookieContext =>
{
cookieContext.CookieOptions.SameSite = SameSiteMode.None; // this doesn't appear to get called.
};
});
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.SlidingExpiration = false;
options.ExpireTimeSpan = TimeSpan.FromHours(8);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = oidcSettings.Authority;
options.RequireHttpsMetadata = true;
options.ClientId = oidcSettings.ClientId;
options.ClientSecret = oidcSettings.ClientKey;
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("offline_access");
options.Scope.Add(oidcSettings.ApplicationName);
options.ClaimActions.MapJsonKey("role", "role"); // claims I am looking for are mapped here
options.Events.OnUserInformationReceived = async (context) =>
{
await Task.CompletedTask; // confirmed that after new sign in I can see updated info here.
};
});
TLDR: Javascript from Web app calls Api using access_token. When user logs out and logs back in, the API does not receive updated claims. I am not sure if the issue is the API needs to call out to identity server for user info or the Web App is not signing out properly and needs to send a fresh access_token?

Return a JWT after authenticating via Open Id

I'm developing an ASP.NET Core Web API where the user logins via Steam.
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = SteamAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddSteam(options =>
{
options.Events.OnAuthenticated = ctx => // Create user
});
// ...
}
For now I'm using a cookie and both the authentication and authorization are working fine. But I'd like to use JWTs. If I simply replace AddCookie by AddJwtBearer I get the following exception: The authentication handler registered for scheme 'Bearer' is 'JwtBearerHandler' which cannot be used for SignInAsync.
In this github issue, it says that I would need a OpenID Connect server but I don't understand why because if I wanted to write the JWT logic by myself, I could generate the token in the open id callback and return it to the user. Or am I missing something ?
See #KévinChalet's comment about the security issue with the below code.
Call HandleResponse in SteamAuthenticationOptions.Events.OnTicketReceived so it doesn't call SignInAsync and to be able to do the redirect yourself to join the jwt.
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => // ...)
.AddSteam(options =>
{
options.Events.OnAuthenticated = ctx =>
{
var res = ctx.User[SteamAuthenticationConstants.Parameters.Response];
var players = res[SteamAuthenticationConstants.Parameters.Players];
var player = players.First.ToObject<SteamPlayer>();
// Create user and generate jwt, then
ctx.Request.HttpContext.Items["jwt"] = jwt;
});
options.Events.OnTicketReceived = ctx =>
{
ctx.HandleResponse();
var jwt = ctx.Request.HttpContext.Items["jwt"] as string;
ctx.Response.Redirect(QueryHelpers.AddQueryString(ctx.ReturnUri, "token", jwt));
return Task.CompletedTask;
};
});
// ...
}
When the authentication succeeds after challenging Steam, a jwt is generated and the user is redirected to {ReturnUri}?token={jwt}.

SecurityTokenException when trying to authenticate ASPNET Core MVC app with Ws-Fed

I have an ASPNet Core 2.2 app which I'm trying to configure to use WsFed Auth for authenticating with our STS. I've added the following to startup.
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Configuration = new WsFederationConfiguration
{
TokenEndpoint = Configuration["Sts:Issuer"],
Issuer = "FP",
KeyInfos = { new KeyInfo(GetStsIssuerCert(Configuration["Sts:Thumbprint"])) },
};
options.CallbackPath = new PathString("/Home/CallBack");
options.ClaimsIssuer = Configuration["Sts:Issuer"];
options.Wtrealm = Configuration["Sts:Realm"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = Configuration["Sts:Realm"]
};
})
.AddCookie();
This works as expected when trying to hit main controller action which has "Authorize" attribute, however the User.Identity data is not populated. If I then add the following in startup.
app.UseAuthentication();
I get the following error.
SecurityTokenException: No token validator was found for the given token.
Microsoft.AspNetCore.Authentication.WsFederation.WsFederationHandler.HandleRemoteAuthenticateAsync()
Do I need to use UseAuthentication()?
Any help on this would be greatly appreciated.

_signInManager.GetExternalLoginInfoAsync() always returns null with open id to azure ad

Why does the signinmanager getexternallogininfoasync method always returning null?
I am using VS2015 with the default asp.net core (not framework) project for MVC with individual user accounts (this is a requirement). The purpose of using third party login is to allow users to self register. Role based authorization will be handled by asp.net identity using the identity provided from registering through Azure AD.
Correct me if the following interpretation of the signin in manager is incorrect. This method should provide details on the external login and return a claimsprincipal object which contains the claims provided by the user by the identity provider.
I have used the following guide for setting up OpenIdConnectAuthentication in my Startup.cs (class section below)
https://azure.microsoft.com/en-us/documentation/samples/active-directory-dotnet-webapp-openidconnect/
When I launch the external login provider, it directs me to the organization login page and succeeds.
However the variable info which should be populated by the signinmanager method is null
if I put a breakpoint into the callback class the User is populated and the IsAuthenticated variable is true.
I could drive the functionality of allowing the user to register in the app myself, however, this is my first attempt at implementing third party login and I would like to understand what I am doing wrong as this point.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add Authentication services.
services.AddAuthentication(sharedOptions => {
sharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
//services.AddDistributedMemoryCache();
//services.AddSession();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseApplicationInsightsExceptionTelemetry();
app.UseStaticFiles();
app.UseIdentity();
// Configure the OWIN pipeline to use cookie auth.
app.UseCookieAuthentication( new CookieAuthenticationOptions());
//Add external authentication middleware below.To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
CallbackPath = "/signin-oidc",
ClientId = Configuration["AzureAD:ClientId"],
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAd:Tenant"]),
ResponseType = OpenIdConnectResponseType.IdToken,
PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
Events = new OpenIdConnectEvents
{
//OnRemoteFailure = OnAuthenticationFailed,
}
});
//app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
External Login
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
There have been multiple reported issues with this method producing a null value in the past. This does not happen with any of the supported authentication methods that are supplied out of the box. This is a problem at least with using OAuth with azure AD and following the supplied method in the post. However, there is a workaround that still allows for the use of the default project type. Simply replace the method that produces the ExternalLoginInfo variable (info) with a roll your own ExternalLoginInfo class constructed using the User principle.
ExternalLoginInfo info = new ExternalLoginInfo(User,
"Microsoft",
User.Claims.Where(x=>x.Type== "http://schemas.microsoft.com/identity/claims/objectidentifier").FirstOrDefault().Value.ToString(),
"Microsoft" );
ASP.NET MVC 5 (VS2013 final): Facebook login with OWIN fails (loginInfo is null)
MVC 5 Owin Facebook Auth results in Null Reference Exception
http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx
I had a similar problem using the OpenIdConnect middleware - I finally fixed it by changing ResponseType to OpenIdConnectResponseType.CodeIdToken (it was being set to 'Code').
Here's a link to my Startup.Auth.vb source:
OWIN OpenID provider - GetExternalLoginInfo() returns null
In my case, I needed to explicitly pass null for cookieScheme when adding it on startup, as discussed in this github issue: https://github.com/AzureAD/microsoft-identity-web/issues/133
services.AddAuthentication(idp.LoginProvider).AddMicrosoftIdentityWebApp(
o => {
o.Instance = config.Instance;
o.Domain = config.Domain;
o.ClientId = config.ClientId;
o.TenantId = config.TenantId;
o.CallbackPath = config.CallbackPath;
o.SignInScheme = IdentityConstants.ExternalScheme;
o.SignOutScheme = IdentityConstants.ExternalScheme;
},
openIdConnectScheme: idp.LoginProvider,
cookieScheme: null // YAR
);
Try after removing SignInScheme
In my case I'm using Github's OAuth to log in. I had to add two things in order for the _signinManager.GetExternalLoginInfoAsync() to not return null and for the [Authorize] attribute to work for users who log in using Github.
I had to add this for Authentication:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// options.DefaultChallengeScheme = "oidc";
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.IsEssential = true;
});
And then inside the section where I'm actually adding the Github oauth, I had to add the following:
options.SignInScheme = "Identity.External";
So the whole section for Github oauth for me looks like this:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// options.DefaultChallengeScheme = "oidc";
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.IsEssential = true;
});
services.AddAuthentication().AddOAuth("GitHub", options =>
{
var key = _env.EnvironmentName == Production ? "GitHubProd" : "GitHub";
options.ClientId = Configuration[$"{key}:ClientId"];
options.ClientSecret = Configuration[$"{key}:ClientSecret"];
options.CallbackPath = new PathString("/github-oauth");
options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
options.TokenEndpoint = "https://github.com/login/oauth/access_token";
options.UserInformationEndpoint = "https://api.github.com/user";
options.SaveTokens = true;
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
options.ClaimActions.MapJsonKey("urn:github:login", "login");
options.ClaimActions.MapJsonKey("urn:github:url", "html_url");
options.ClaimActions.MapJsonKey("urn:github:avatar", "avatar_url");
options.Scope.Add("repo");
options.Scope.Add("repo_deployment");
options.Scope.Add("repo:status");
options.Scope.Add("write:repo_hook");
options.Scope.Add("read:repo_hook");
options.Scope.Add("notifications");
options.Scope.Add("read:repo_hook");
options.Scope.Add("user");
// NEW LINE HERE
options.SignInScheme = "Identity.External";
// save oauth token to cookie
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(json.RootElement);
}
};
});
why aren't you using the built in microsoftaccountauthentication. There is a tutorial on that here.
https://www.benday.com/2016/05/14/walkthrough-asp-net-core-1-0-mvc-identity-with-microsoft-account-authentication/
This is similar to the google and Facebook authorization classes.