Servicestack Authentication IsAuthenticated always false - redis

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)
}));

Related

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.

ServiceStack Authentication IsAuthenticated always false for users authenticated via facebook

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>>();
}

OAuth 2.0 , Azure AD and OpenId Connect

I am still trying to understand OAuth 2.0 flows with ASP.Net Core 2.0. The default code (see below) which was provided by Microsoft works well with Azure AD authentication using OAuth 2.0 and OpenId Connect.
I am actually testing the Authorization Code flow.
I am trying to change the code below so that it doesnt use OpenId Connect but instead use the plain OAuth. (You may want to ask why, the vendor I am working with is not supporting the OpenId Connect yet).
So I need to use plain OAuth to enable to Authorization Code flow using Azure AD.
services.AddAuthentication(auth =>
{
auth.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(opts =>
{
Configuration.GetSection("Authentication").Bind(opts);
opts.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async ctx =>
{
HttpRequest request = ctx.HttpContext.Request;
//We need to also specify the redirect URL used
string currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path);
//Credentials for app itself
var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret);
//Construct token cache
ITokenCacheFactory cacheFactory = ctx.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
TokenCache cache = cacheFactory.CreateForUser(ctx.Principal);
var authContext = new AuthenticationContext(ctx.Options.Authority, cache);
//Get token for Microsoft Graph API using the authorization code
string resource = "https://bupaau.onmicrosoft.com/4fa4b4a7-d34f-49af-8781-c8b39f0cf770";
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
ctx.ProtocolMessage.Code, new Uri(currentUri), credential, resource);
//Tell the OIDC middleware we got the tokens, it doesn't need to do anything
ctx.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
});
How do I turn the openId Connect off and enable the plain OAuth authentication for Authorization Code flow.
-Alan-
You specify this by using scope parameter values. For OpenID Connect, scope value is set to openid. This is what specification says about authorisation request.
But there are some Azure AD specifics you need to taken care of. This is highlighted in Azure AD documentation. For OpenID Connect,
When your web application needs to authenticate the user, it must direct the user to the /authorize endpoint. This request is similar to the first leg of the OAuth 2.0 Authorization Code Flow, with a few important distinctions:
The request must include the scope openid in the scope parameter.
The response_type parameter must include id_token.
The request must include the nonce parameter.
Also here is the link for OAuth 2.0 documentation. You may not simply remove OpenID Connect specific parameters to get OAuth 2.0 response as there could be implementation specific requirements.

Trouble getting ClaimsPrincipal populated when using EasyAuth to authenticate against AAD on Azure App Service in a Asp.Net Core web app

We have a web app built on Asp.Net core. It doesn't contain any authentication middleware configured in it.
We are hosting on Azure App Service and using the Authentication/Authorization option (EasyAuth) to authenticate against Azure AD.
The authentication works well - we get the requisite headers inserted and we can see the authenticated identity at /.auth/me. But the HttpContext.User property doesn't get populated.
Is this a compatibility issue for Asp.Net core? Or am I doing something wrong?
I've created a custom middleware that populates the User property until this gets solved by the Azure Team.
It reads the headers from the App Service Authentication and create a a user that will be recognized by the [Authorize] and has a claim on name.
// Azure app service will send the x-ms-client-principal-id when authenticated
app.Use(async (context, next) =>
{
// Create a user on current thread from provided header
if (context.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-ID"))
{
// Read headers from Azure
var azureAppServicePrincipalIdHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"][0];
var azureAppServicePrincipalNameHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"][0];
// Create claims id
var claims = new Claim[] {
new System.Security.Claims.Claim("http://schemas.microsoft.com/identity/claims/objectidentifier", azureAppServicePrincipalIdHeader),
new System.Security.Claims.Claim("name", azureAppServicePrincipalNameHeader)
};
// Set user in current context as claims principal
var identity = new GenericIdentity(azureAppServicePrincipalIdHeader);
identity.AddClaims(claims);
// Set current thread user to identity
context.User = new GenericPrincipal(identity, null);
};
await next.Invoke();
});
Yes, this is a compatibility issue. ASP.NET Core does not support flowing identity info from an IIS module (like Easy Auth) to the app code, unfortunately. This means HttpContext.User and similar code won't work like it does with regular ASP.NET.
The workaround for now is to invoke your web app's /.auth/me endpoint from your server code to get the user claims. You can then cache this data as appropriate using the x-ms-client-principal-id request header value as the cache key. The /.auth/me call will need to be properly authenticated in the same way that calls to your web app need to be authenticated (auth cookie or request header token).
I wrote a small basic middleware to do this. It will create an identity based off of the .auth/me endpoint. The identity is created in the authentication pipeline so that [authorize] attributes and policies work with the identity.
You can find it here:
https://github.com/lpunderscore/azureappservice-authentication-middleware
or on nuget:
https://www.nuget.org/packages/AzureAppserviceAuthenticationMiddleware/
Once added, just add this line to your startup:
app.UseAzureAppServiceAuthentication();
The following code decrypts the AAD token from the Azure App Service HTTP header and populates HttpContext.User with the claims. It's rough as you'd want to cache the configuration rather than look it up on every request:
OpenIdConnectConfigurationRetriever r = new OpenIdConnectConfigurationRetriever();
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.Endpoint, r);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = config.SigningKeys.ToList(),
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateAudience = true,
ValidAudience = options.Audience,
ValidateLifetime = true,
ClockSkew = new TimeSpan(0, 0, 10)
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
string token = context.Request.Headers["X-MS-TOKEN-AAD-ID-TOKEN"];
if (!String.IsNullOrWhiteSpace(token))
{
principal = handler.ValidateToken(token, tokenValidationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null) { throw new ArgumentException("Invalid JWT"); }
if (principal != null)
{
context.User.AddIdentities(principal.Identities);
}
}
It only works for Azure AD. To support other ID providers (Facebook, Twitter, etc) you'd have to detect the relevant headers and figure out how to parse each provider's token. However, it should just be variations on the above theme.
You can give this library a try. I faced a similar problem and created this to simplify the use.
https://github.com/dasiths/NEasyAuthMiddleware
Azure App Service Authentication (EasyAuth) middleware for ASP.NET
CORE with fully customizable components with support for local
debugging
It hydrates the HttpContext.User by registering a custom authentication handler. To make things easier when running locally, it even has the ability to use a json file to load mocked claims.

How to configure Swagger in Web API 2 to POST OAuth2 authentication request?

I have recently started a Web API 2 project in Visual Studio 2012 using OWIN middleware to authenticate users with OAuth2. I incorporated token based authentication as outlined on this tutorial (Token Based Authentication). The authentication part works great. I have added some testing API methods and I wanted to hook up Swagger for my API documentation. I got that part working too, with the exception that the API calls from Swagger fail on authorization.
After research, I found Erik Dahl's post about how to hook up Swagger to OWIN middleware. After I configured my Swagger according to the post, I now see the authenticate buttons on the Swagger UI next to each API method. However, when trying to authenticate, the authentication within Swagger is done using a GET request. The authentication on the web API though requires it to be POST request. Is it possible to configure Swagger make the authentication request a POST? If not, should I allow my API to accept GET requests for token authentication? What would be the best approach to make this work?
Note: The request still hits my authentication logic, but the client_id and client_secret are not passed in a GET request, only in a POST request.
Here's my Swagger config:
httpConfig
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "Sample API");
c.ApiKey("token")
.Description("API Key Authentication")
.Name("Bearer")
.In("header");
c.OAuth2("oauth2")
.AuthorizationUrl("/oauth/token")
.Flow("implicit")
.Description("OAuth2 authentication")
.Scopes(scopes =>
{
scopes.Add("sampleapi", "Sample API");
});
c.OperationFilter<AssignOAuth2SecurityRequirements>();
})
.EnableSwaggerUi(c =>
{
c.EnableOAuth2Support(
clientId: "Sample_App",
clientSecret: "xxxxx",
realm: "test-realm",
appName: "Swagger UI");
});
And here's my OAuth config:
app.CreatePerOwinContext<ApiClientRepo>(ApiClientRepo.Create);
app.CreatePerOwinContext<MeetingRegistrantRepo>(MeetingRegistrantRepo.Create);
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost:51071"),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
No, I would not change the authentication method from POST to GET just to satisfy Swagger.
I found another article which should help you do what you want to do here : http://danielwertheim.se/use-identityserver-in-swaggerui-to-consume-a-secured-asp-net-webapi/
It wold be worth to try it that way. Don't forget that changing from POST to GET means you can no longer pass the parameters in the body of the request and you will instead have to do it in the URL of the request and that makes the whole thing insecure.
Yes, the ClientID and ClientSecret will still be part of the Authorization Header, but still do not open yourself up to stuff like this. Swagger should not dictate the architecture of your API so don't go there.