ServiceStack Authentication IsAuthenticated always false for users authenticated via facebook - asp.net-core

Hi am trying to use OAuth authentication FacebookAuthProvider provided by servicestack
var AppSettings = appHost.AppSettings;
appHost.Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(AppSettings),
new FacebookAuthProvider(AppSettings),
new GoogleAuthProvider(AppSettings)
}));
I have implement custom AuthEvents and I have access to authorized user data
but after RedirectUrl happens in endpoint session is empty and IsAuthenticated property is false
In the same time from Controller I can see that created session successfully saved in Cache
This scenario occurred only if user doesn't login in facebook in the browser yet, but for user that already logged in facebook, authentication works fine
What am I doing wrong ?
Thanks in advance!
Updates:
Scenario for repsoducing:
ensure that you didn't loged in facebook in browser (open facebook.com and log out if need)
run web app (project which created with template mvcauth)
try to login via facebook
3.1. for first attempt you will be redirected to https://www.facebook.com/login.php? page for specify your facebook account credentials
after first attempt you will be returned to web app page /Account/Login#s=1 but User.Identity.IsAuthenticated = false in AccountController.Login and you still unauthorized.
after second attempt you will finally successfully logged in
At the same time after first attempt Session with user data will be created and saved in Cache, it looks like after redirecting from page 'https://www.facebook.com/login.php' а new session being created without auth data, but for second attempt we successfully login, maybe the core of issue is redirection from 'https://www.facebook.com/login.php'

In order to be able to use ServiceStack.Auth in MVC and vice-versa you'll need to register the NetCoreIdentityAuthProvider which provides integration between ServiceStack Authenticated Sessions and ASP.NET Core's Claims Principal, e.g:
public void Configure(IAppHost appHost)
{
var AppSettings = appHost.AppSettings;
appHost.Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new NetCoreIdentityAuthProvider(AppSettings), /* Use ServiceStack Auth in MVC */
new CredentialsAuthProvider(AppSettings), /* Sign In with Username / Password */
new FacebookAuthProvider(AppSettings),
new GoogleAuthProvider(AppSettings),
new MicrosoftGraphAuthProvider(AppSettings),
}));
appHost.Plugins.Add(new RegistrationFeature()); //Enable /register Service
//override the default registration validation with your own custom implementation
appHost.RegisterAs<CustomRegistrationValidator, IValidator<Register>>();
}

Related

Two claims principal in one request in ASP.NET Core

We can access ClaimsPrincipal for current request from HttpContext.User. As it seems to me, the AuthenticateResult.Success() in custom handler sets HttpContext.User to the created ClaimsPrincipal. And that's the problem.
I use cookie authentication scheme to authenticate users and custom authentication scheme to authenticate clients.
There are scenarios, where client redirects user to my app and then I have to authenticate client that redirected the user and then the user which is redirected.
So after successful client authentication, I create authentication ticket and ClaimsPrincipal is cached in HttpContext.User. In next step, I authenticate user. And what's now? Currently existing client's ClaimsPrincipal is overriden by user's ClaimsPrincipal and I lose info about authenticated client.
So how can I have multiple ClaimsPrinciapl for HttpContext? Or is there another, better solution?
You can using below code when login.
HttpContext.Authentication.SignInAsync("Cookie", userPrincipal1,
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
IsPersistent = false,
AllowRefresh = false
});
When you want add another ClaimsPrincipal, you can use the code below.
var userPrincipal2= new ClaimsPrincipal(new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "Jason"),
new Claim(ClaimTypes.Role, "User"),
}));
httpContext.User.AddIdentity(userPrincipal2.Identity);
ClaimsPrincipal objects with multiple identities are referred to as "complex identities". You can use the HasClaim method to check if a claim is present in any of the identities.

How to configure Azure AD authentication in Hybrid ASP.NET Core MVC (backend) and Vuejs SPA (frontend)?

My application is a hybrid approach where use ASP.NET Core MVC as my backend. I have various controllers which my front end uses to pull data from our database and also to do API calls on MS Graph. I am using the following program.cs file to get the authentication initiated when a user first logs on to the site:
//authentication pipline
builder.Services.AddHttpContextAccessor();
var initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events = new OpenIdConnectEvents
{
//Tap into this event to add a UserID Claim to a new HttpContext identity
OnTokenValidated = context =>
{
//This query returns the UserID from the DB by sending the email address in the claim from Azure AD
string query = "select dbo.A2F_0013_ReturnUserIDForEmail(#Email) as UserID";
string connectionString = builder.Configuration.GetValue<string>("ConnectionStrings:DBContext");
string signInEmailAddress = context.Principal.FindFirstValue("preferred_username");
using (var connection = new SqlConnection(connectionString))
{
var queryResult = connection.QueryFirst(query, new { Email = signInEmailAddress });
var claims = new List<Claim>
{
new Claim("UserID", queryResult.UserID.ToString())
};
var appIdentity = new ClaimsIdentity(claims);
context.Principal.AddIdentity(appIdentity);
}
return Task.CompletedTask;
},
};
}).EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
//Add Transient Services
builder.Services.AddTransient<IOneDrive, OneDrive>();
builder.Services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
builder.Services.AddRazorPages().AddRazorPagesOptions(options =>
{
options.Conventions.AllowAnonymousToFolder("/Login");
options.Conventions.AuthorizeFolder("/");
options.Conventions.AuthorizeFolder("/files");
}).AddMicrosoftIdentityUI();
// Add the UI support to handle claims challenges
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddRequiredScopeAuthorization();
In the Azure AD portal my application is registered as a web app. So when a user initially goes to the site they are redirected to https://login.microsoftonline.com/blahblah to get the login process started. This is automated by the Azure AD identity platform. Then once the login occurs they are redirected to localhost where the VueJS spa is loaded (localhost:43862). My spa uses various axios requests to the controllers and they pull data and vue router loads components. However, my issue is say the user needs to relog in because the cookie is expired or they logged out in another tab. The next axios request made by the expired session does not redirect the user to Azure login screen but instead results in an CORS error. So I need to get my axios requests to force the page redirect to Azure AD login screen (which probably is the worst idea since CORS policy is resulting in error) or have it return a redirect to localhost/login which is my own custom login screen with a button to Azure AD login and shouldnt impact CORS. So how do I intercept this Azure AD redirect to Azure AD login and replace with my own?
I have also tried to return a 401 error code so I could check for that in my axios request but to no avail it does nothing. If I put a breakpoint there it does hit this code but it does not change the status code of the response and I still get 302. My code for that was to try and add to the event :
OnRedirectToIdentityProvider = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
}
My other ideas was maybe I should set my CORS policy to allow redirects from login.microsoft.com? Or would this be bad practice?
I can answer part of your question... First, for our API application which is protected by Azure AD, what the API should do is validating the request whether it contained a correct access token in the request header, if yes, give the response, if no, then give error like 401 or 403. A normal API application shouldn't have a UI to let users sign in. Anyway, if you want to expose an API in an MVC project, it's OK, but for API itself, it shouldn't have a UI.
Let's see sample below, I had a .net 6 web api project, and here's my program.cs:
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
And it requires configurations in appsetting.json.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "azure_ad_client_id",
"ClientSecret": "client_secret",
"Domain": "tenant_id",
"TenantId": "tenant_id",
//"Audience": "api://azure_ad_client_id_which exposed_api" // here I used the same azure ad app to expose API, so I can comment this property
},
And this is the Controller:
[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
[RequiredScope("Tiny.Read")]
[HttpGet]
public string Get()
{
return "world";
}
}
I had an Azure AD app, and I exposed an API like this:
I also add this API for the same Azure AD app.
Then let's do a test. When I call this API directly, I will get 401 error:
If I used an expired token within the request, I will also get 401 error:
But if I used a correct token(go to https://jwt.io to decode the token, we should see it containing correct scope, for me its "scp": "Tiny.Read",), I will get response:
And till now, the API part had finished. Let's see the client SPA. For SPA, you should integrate MSAL so that you can make your users to sign in via Azure AD, and generate the access token for calling MS graph API or your own API. The code for generating access token should be the same but you should set different scope for different API. In my scenario, my API required a scope Tiny.Read, then I should set in my client App.
Here's an screenshot for generating access token in react. You need to set the scope in your code.
Now you have the method to generate access token, you already know the API url. Then you can send request to call api, using AJAX, using fetch, or something else, sending an http request is ok. And in the calling api part, you also need to handle the response. If the response code is 401, then you need to do some logic, maybe redirect to the sign in page. And you said you had trouble here, you met CORS issue. I can't answer this part. I think it depends on how you redirect to Azure AD sign in page. I'm afraid you can take a look at this sample to learn how to sign in users and call graph api.

Why is ServiceStack JwtAuthProvider being invoked when service is specified to authenticate with GithubAuthProvider?

Exploring the ServiceStack authentication providers for the first time. Have gradually built up a test project by adding BasicAuthProvider and when that worked, added GithubAuthProvider. The last one added was JwtAuthProvider and that works as well. When I retested authentication with any of the previous authentication providers such as Github, for example, I find that the JwtAuthProvider lambda function for CreatePayloadFilter is still being invoked. This was not expected.
The AuthFeature plugin is as follows:
Plugins.Add(new AuthFeature(
() => new PartnerWebSession(),
new IAuthProvider[] {
new JwtAuthProvider(appSettings) { ... },
new BasicAuthProvider(), //Sign-in with HTTP Basic Auth
new GithubAuthProvider(appSettings)
}));
I have created BasicHello, GithubHello and JwtHello service models with different end points for use with each of the authentication providers with routes:
/basic/{Name}
/github/{Name}
/jwt/{Name}
The AuthenticateAttribute has been attached to the Service classes designed to work with these models and they each specify the Provider to use, e.g.:
[Authenticate(Provider = "basic")]
public class BasicAuthentication : Service
{
public object Any(BasicHello request)
{
return new BasicHelloResponse { Result = $"{nameof(BasicAuthentication)} says Hello, {request.Name}!" };
}
}
Testing using Visual Studio debug and Chrome browser. Have ensured that no previous cookies JWT are hanging around from previous tests by clearing cookies for all time. I enter the URL:
http://localhost:52070/api/github?name=stephen
This takes me to the ServiceStack rendered authentication screen with login dialogue and "Sign In with Github" button. Click the "Sign In with Github" button which takes me to the "Sign in to GitHub" page provided by GitHub. Successfully authenticate, and then code execution hits a breakpoint I've set in CreatePayloadFilter lambda of the JwtAuthProvider.
I did not expect this. Is this an error or am I doing something wrong?
If you have the JWT AuthProvider registered it populates the successful Auth Response with a populated JWT Token that encapsulates the partial authenticated session.
The stateless client JWT Token (i.e. instead of server session cookies) can then be used to make authenticated requests after a successful OAuth Sign In.

Servicestack Authentication IsAuthenticated always false

Hi am trying to use OAuth authentication provided by servicestack
plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
new BasicAuthProvider(), new LinkedInOAuth2Provider(new AppSettings()),
new GoogleOAuth2Provider(new AppSettings()) }));
//Use Redis Repo
var userRepository = new RedisAuthRepository(redisClientsManager);
container.Register<IUserAuthRepository>(userRepository);
//Register Users with redis
Plugins.Add(new RegistrationFeature());
After Successful authentication by Google/LinkedIn the redis AuthRepo contains this, We can see that isAuthenticated is False even after Successful authentication.
Could anyone let me know more about OAuth, Because there are many Secrets of it when comes to Mobile.
Eg: What Redirect Uri should i give in LinkedIn Console..! if i use OAuth for Mobiles ..? And how can i refresh session on each App StartUp.
Your screenshot does not show an authenticated User Session, which would have IsAuthenticated = true and include OAuth details returned by each OAuth provider in ProviderOAuthAccess collection.
ServiceStack OAuth Live Demos
See the httpbenchmarks.servicestack.net for an example of a working Live Demo that uses Google and LinkedIn OAuth2. The HttpBenchmarks Github Repo includes a step-by-step Guide explaining how to Configure OAuth in ServiceStack, including an example of a App Settings configuration and how to configure Glimpse for inspecting DotNetOpenAuth errors.
The mvc.servicestack.net Live Demo is another example that contains a working configuration using a number of Auth Providers.
Use AppHost AppSettings
When registering the OAuth Provider you should use the AppHost's AppSettings instead of injecting a new AppSettings() that way all Auth Providers will use the same configured AppSettings for your AppHost, e.g:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new BasicAuthProvider(),
new LinkedInOAuth2Provider(base.AppSettings),
new GoogleOAuth2Provider(base.AppSettings)
}));

Asp.net mvc web use both token base authentication and form authentication

I have an asp.net mvc project that contains some web API controllers.my mvc area and pages are authenticated via form authentication.
API controllers should be consumed from native android client I need to register deice and authenticate them for some API's. I searched and seen some web api example used token authentication but here how can i merged both token and form authentication for different request?
how can i customize my security configuration to generate token and authenticate api requests?
here is Startup.Auth class:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager> (ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
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);
}