Updating session cookie on first request to a site and add to custom header on additional requests - asp.net-core

I'm using ASP.NET Identity and OpenIddict for a custom authorization server in MVC / Razor Pages. I'm supporting most OpenID flows with OpenIddict and user and admin areas with ASP.NET Identity.
The site is accessed directly from desktop browsers, mobile apps and other projects, especially the endpoints for starting a login and a authroization flow.
Everything is working as expected.
Now I would like to add partial multi-tenancy by only switching CSS based on the tenant. The rest of the app will remain untouched. I was going to start by modifying the _Styles.cshtml file by adding the following:
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor accessor
#{
var ok = accessor.HttpContext.Request.Headers.TryGetValue("X-Tenant-Id", out var values);
var tenantId = values.FirstOrDefault();
}
Then depending on the tenant I will add the CSS file.
How, through the whole app, in views and in controller actions there redirects and links to other pages (eg. from login page to register page or forgot password). This is a problem because the custom header above is not persisted in further requests.
So I was thinking of added the header to the session state. Currently I'm not using sessions, but I would be doing something similar to what is described in this MS Article.
Any request that has the above header will update the session state. As this is an essential cookie but I still have to use cookie consent policy and data protection, is it possible to flag it as essential to avoid cookie consent?
Will the work or is there a better solution?

Is it possible to flag it as essential to avoid cookie consent
If you marked the session cookie as essential ,you would reveive a cookie name of .AspNetCore.Session
builder.Services.AddSession(options =>
{
options.Cookie.IsEssential = true;
})
and if you configured cookieConsent as below
builder.Services.Configure<CookiePolicyOptions>(op =>
{         op.CheckConsentNeeded = _ => true;     
});
you won't receive the cookies which are not marked as essential
I tried as below:
public IActionResult Index()
{
HttpContext.Response.Cookies.Append("SomeKey", "SomeValue" ,new CookieOptions() { IsEssential=false});
HttpContext.Session.SetString("SomeKey", "SomeVal");
return View();
}
public IActionResult Privacy()
{
string? cookieval;
HttpContext.Request.Cookies.TryGetValue("SomeKey", out cookieval);
var sessionval=HttpContext.Session.GetString("SomeKey");
return View();
The Result:

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.

ASP.NET Core : Return Json response on Unauthorized in a filter at the controller/action level

I am not using Identity.
I have this ASP.NET Core configuration enabling two authentication schemes, cookies and basic auth:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "_auth";
options.Cookie.HttpOnly = true;
options.LoginPath = new PathString("/Account/Login");
options.LogoutPath = new PathString("/Account/LogOff");
options.AccessDeniedPath = new PathString("/Account/Login");
options.ExpireTimeSpan = TimeSpan.FromHours(4);
options.SlidingExpiration = true;
})
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
BasicAuthenticationHandler is a custom class inheriting from AuthenticationHandler and overriding HandleAuthenticateAsync to check the request headers for basic authentication challenge, and returns either AuthenticateResult.Fail() or AuthenticateResult.Success() with a ticket and the user claims.
It works fine as is:
Controllers/Actions with the [Authorize] tag will check the cookies and redirect to the login page is not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication")] tag will check the header and reply a 401 Unauthorized HTTP code if not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication,Cookies")] tag will allow both methods to access the page, but somehow use the Cookies redirection mechanism when failing both checks.
My goal is to have most of my project to use Cookies (hence why it is set as default), but have some API type of controllers to accept both methods. It should also be possible to tag the Controllers/Actions to return a specific Json body when desired (as opposed to the login redirect or base 401 response), but only for certain controllers.
I've spent the last 2 days reading different similar questions and answers here on StackOverflow, nothing seems to accommodate my need.
Here's a few methods I found:
The options under AddCookie allow you to set certain events, like OnRedirectToAccessDenied and change the response from there. This does not work because it applies to the whole project.
Under my BasicAuthenticationHandler class, the AuthenticationHandler class allow to override HandleChallengeAsync to change the response from there instead of replying 401. Unfortunately, again it applies globally to everywhere you use the scheme, not on a controller/action level. Not sure if it's applied when mixing multiple schemes either.
Many answers point to adding a Middleware to the solution, again, it impacts the whole project.
Many answers point to Policies, but it seems to be to control whether or not an user have access to the resource based on claims, not controlling the response when he do not.
Many answers suggest creating a class inheriting from AuthorizeAttribute, IAuthorizationFilter. Again, this allow to override the OnAuthorization method to decide if the user have the right or not to access the resource, but not to control the response AFTER the normal authentication scheme failed.
I'm thinking either there's a filter type I'm missing, or maybe I need to create a third authentication type that will mix the previous two and control the response from there. Finding a way to add a custom error message in the options would also be nice.
I managed to do it via a IAuthorizationMiddlewareResultHandler. Not my favorite approach because there can be only one per project and it intercepts all calls, but by checking if a specific (empty) attribute is set, I can control the flow:
public class JsonAuthorizationAttribute : Attribute
{
public string Message { get; set; }
}
public class MyAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler DefaultHandler = new AuthorizationMiddlewareResultHandler();
public async Task HandleAsync(RequestDelegate requestDelegate, HttpContext httpContext, AuthorizationPolicy authorizationPolicy, PolicyAuthorizationResult policyAuthorizationResult)
{
// if the authorization was forbidden and the resource had specific attribute, respond as json
if (policyAuthorizationResult.Forbidden)
{
var endpoint = httpContext.GetEndpoint();
var jsonHeader = endpoint?.Metadata?.GetMetadata<JsonAuthorizationAttribute>();
if (jsonHeader != null)
{
var message = "Invalid User Credentials";
if (!string.IsNullOrEmpty(jsonHeader.Message))
message = jsonHeader.Message;
httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = "application/json";
var jsonResponse = JsonSerializer.Serialize(new
{
error = message
});
await httpContext.Response.WriteAsync(jsonResponse);
return;
}
}
// Fallback to the default implementation.
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policyAuthorizationResult);
}
}
I was typing this on comment... but it's doesn't fit... so here is something we probably need to make clear before choosing a solution:
Authorization process happen at the upper middleware above controller
Yes, AuthorizationMiddleware was registered when we use app.UseAuthorization();, that quite far above controller layer, so it was returned long before the request can reach controller, so, any type of filter cannot be applied here.
Not specify an authentication scheme or policy would easily lead to un-stable behavior.
Imagine, Authentication process return an instance of User that stick with the request, but what would happen if the permission on cookie and basicAuth was difference, like cookie have myclaim, while basicAuth doens't ? Related process on both type of scheme was difference (like challenge on cookie would lead to /Account/Login and basicAuth to /Login ?). And various logic case that we could implement on each page.
I Know, this is not possible, but it would become a mess, not for the author of these code, but for those maintainers to come.
Json response for some specific process on client ?
This might sound detailed at first glance, but it would rather become burden soon, if some more authentication requirement raise after that (like Jwt). Covering each of these case on client would make user experience quite awkward (like, half-authentication and authorization).
And if It's un-avoidable in the project. Might I suggest create a default authentication scheme with ForwardDefaultSelector that would elected which authentication scheme to use for each request. And maintain a stable routing HashSet that would use to detect on which endpoint to set Json Response as wished on some upper level than AuthorizationMiddleware, by using middleware, ofcourse. Then, we narrow down to 2 centralize places to checkout the authorization.
Chaos came when we tried to make one thing to do somethings. At least in this case, I think we would breath easier when coming to debug phase.

Restrict account registration to only Admin users with asp.net identity authentication

I am creating a Blazor server app that requires authenticated users in order to prevent external access, and I would like to limit the ability to register new accounts to be only available to Administrator users to prevent unwanted accounts from being created.
I'm using Identity user accounts, scaffolded out for Blazor. Solutions like this at least disable the registration, but from there I need to be able to enable it again for administrative users. I attempted to recreate the register page as a Blazor component, however, using the generated RegisterModel did not seem to work for me.
Upon a large amount of searching - the answer ended up being relatively simple. Muhammad Hammad Maroof's solution although technically correct, confused me and was mostly unhelpful for working with the register page specifically.
As I am using Role-Based Authentication scaffolded out from Blazor - in a seperate razor page I use this code to set up roles:
#code {
protected override async Task OnParametersSetAsync()
{
await SetUpAuth();
}
private async Task SetUpAuth()
{
const string Manager = "Manager";
string[] roles = { Manager };
foreach (var role in roles)
{
var roleExist = await roleManager.RoleExistsAsync(role);
if (!roleExist)
{
await roleManager.CreateAsync(new IdentityRole(role));
}
}
var user = await userManager.FindByEmailAsync(config.GetValue<string>("AdminUser"));
if (user != null)
{
await userManager.AddToRoleAsync(user, Manager);
}
}
}
Allowing the appropriate user to be marked as an administrator. This page has the [AllowAnonymous] tag on it in order to allow the administrative user as dictated by "AdminUser": "SomeEmail#test.com", in the appsettings.json page to be able to access the site on initial setup.
Preventing access to the Blazor site itself from anonymous users was as simple as adding this line to ConfigureServices in the startup class (Code taken from Microsoft Docs)
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
From this, allowing access to the register page was significantly easier than I had initially thought (likely due to my lack of .net experience). To do so, all you have to do is locate the Register.cshtml.cs page (I couldn't initially find the controller method Muhammad had mentioned) which I did by using visual studio to right click on the Register Model and then go to definition. This should take you to the Register.cshtml.cs page with the RegisterModel class. In order to restrict access to this page for only a specific role of users, all you have to do is change the [AllowAnonymous] tag above the class to look similar to this:
[Authorize(Roles ="Manager")]
public class RegisterModel : PageModel
It's important to note that the same technique used to secure the register page could be used to secure any of the of the other scaffolded Identity pages. For applications where you may have more than a few roles, the method provided by Muhammad of using policy based authorization may be the way to go, and this link he provided is a great tutorial for setting up and using that form of authentication.
//FORCE autentication for all RAZOR PAGES except [AllowAnonymous]
services.AddControllers(config => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Only adding this code to my startup.cs solved my problem.
Here's how I am doing it in asp.net core mvc app
C# Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy(ADMIN_ACCESS, policy => policy.RequireRole($"{UserType.Admin}"));
});
}
[Authorize("AdminAccess")]
public class AdminController : Controller
{
//Some action methods here
}

.NET CORE API Making Facebook Login Work With Openiddict/Identity

I have one project (Project A) which is a .NET CORE API project using Openiddict with an endpoint of /connect/token to issue JWT tokens using Identity to handle the security etc. This project works great as is.
I have another project (Project B), which is just a very simple project with some HTML that makes requests to the API to get an access token, and get data from the API. This project also works great.
Now the part I cannot wrap my brain around, how do I use Facebook login between these two totally separate projects? I know how to use it if everything is under one roof, and it's really easy, but this scenario has me totally confused since everything is separated. So for starters, who handles the 'ExternalLogin', 'ExternalLoginCallBack' logic (from .NET web template using individual accounts), the API? The HTML project? When connecting with Facebook, what redirect uri should I use (API/HTML project)? Then who should have the below code in their 'Startup.cs' file?
app.UseFacebookAuthentication(new FacebookOptions
{
AppId = "xxxxxxx",
AppSecret = "xxxxxxxxx",
Scope = { "email", "user_friends" },
Fields = { "name", "email" },
SaveTokens = true,
});
And finally if this helps here is how I have Project A currently setup:
STARTUP.CS (API)
public void ConfigureServices function: (API)
// add entity framework using the config connection string
services.AddEntityFrameworkSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// add identity
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// add OpenIddict
services.AddOpenIddict<ApplicationUser, ApplicationRole, ApplicationDbContext>()
.DisableHttpsRequirement()
.EnableTokenEndpoint("/connect/token")
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.UseJsonWebTokens()
.AddEphemeralSigningKey();
services.AddCors();
public void Configure function: (API)
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
RequireHttpsMetadata = false,
Audience = "http://localhost:54418/",
Authority = "http://localhost:54418/"
});
Authorization Controller (API)
public class AuthorizationController : Controller
{
private OpenIddictUserManager<ApplicationUser> _userManager;
public AuthorizationController(OpenIddictUserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpPost("~/connect/token")]
[Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIdConnectRequest();
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null)
{
return BadRequest(new OpenIdConnectResponse
{
ErrorDescription = "The username or password provided is incorrect"
});
}
var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());
// Add a custom claim that will be persisted
// in both the access and the identity tokens.
if (user.Avatar != null)
{
identity.AddClaim("user_avatar", user.Avatar,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
if (user.InSiteUserName != null)
{
identity.AddClaim("insite_username", user.InSiteUserName,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
identity.AddClaim("hasLoggedIn", user.HasLoggedIn.ToString(),
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetResources(request.GetResources());
ticket.SetScopes(request.GetScopes());
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
}
}
I don't know if it's including anything from Project B since it's pretty basic/bare and relies on the API for everything.
I know this is a loaded and complicated question, and I'm sure I'm not presenting it as fluidly as possible so I apologize in advance for that, like I said before, I'm confused. Thank you!
Now the part I cannot wrap my brain around, how do I use Facebook login between these two totally separate projects? I know how to use it if everything is under one roof, and it's really easy, but this scenario has me totally confused since everything is separated. So for starters, who handles the 'ExternalLogin', 'ExternalLoginCallBack' logic (from .NET web template using individual accounts), the API? The HTML project?
In the recommended case (i.e when using an interactive flow like the authorization code flow or the implicit flow), the authorization server project itself is responsible of handling the external authentication dance, using the social providers you've configured in your ASP.NET Core pipeline.
In theory, the final client application (i.e the JS app) doesn't even know that you've decided to use external authentication at the authorization server level, since it's not directly linked to Facebook or Google.
In this case, the redirect_uri configured in the Facebook options must correspond to an endpoint owned by the authorization server application (in your case, it's provided by the Facebook authentication middleware).
If you don't like this approach, there's also a different flow named "assertion grant", that basically reverses how things are handled: the final client app (the JS app in your case) is directly linked to Facebook - so the redirect_uri must correspond to the JS app - and uses OpenIddict's token endpoint to "exchange" Facebook tokens with tokens issued by your own server, that can be used with your own APIs.
For more information about this flow, please read Exchanging a google idToken for local openId token c#.

Alternative to cookie based session/authentication

Is there an alternative to the session feature plugin in servicestack? In some scenarios I cannot use cookies to match the authorized session in my service implementation. Is there a possibility to resolve the session using a token in http header of the request? What is the preferred solution for that in case the browser is blocking cookies?
I'm using ServiceStack without the built-in auth and session providers.
I use a attribute as request filter to collect the user information (id and token), either from a cookie, request header or string parameter.
You can provide this information after the user takes login. You append a new cookie to the response and inject the id and token info on clientside when rendering the view, so you can use for http headers and query parameters for links.
public class AuthenticationAttribute : Attribute, IHasRequestFilter
{
public void RequestFilter(IHttpRequest request, IHttpResponse response, object dto)
{
var userAuth = new UserAuth { };
if(!string.IsNullOrWhiteSpace(request.GetCookieValue("auth"))
{
userAuth = (UserAuth)request.GetCookieValue("auth");
}
else if (!string.IsNullOrEmpty(request.Headers.Get("auth-key")) &&
!string.IsNullOrEmpty(request.Headers.Get("auth-id")))
{
userAuth.Id = request.Headers.Get("id");
userAuth.Token = request.Headers.Get("token");
}
authenticationService.Authenticate(userAuth.Id, userAuth.token);
}
public IHasRequestFilter Copy()
{
return new AuthenticationAttribute();
}
public int Priority { get { return -3; } } // negative are executed before global requests
}
If the user isn't authorized, i redirect him at this point.
My project supports SPA. If the user consumes the API with xmlhttprequests, the authentication stuff is done with headers. I inject that information on AngularJS when the page is loaded, and reuse it on all request (partial views, api consuming, etc). ServiceStack is powerful for this type of stuff, you can easily configure your AngularJS app and ServiceStack view engine to work side by side, validating every requests, globalizing your app, etc.
In case you don't have cookies and the requests aren't called by javascript, you can support the authentication without cookies if you always generate the links passing the id and token as query parameters, and pass them through hidden input on forms, for example.
#Guilherme Cardoso: In my current solution I am using a PreRequestFilters and the built-in session feature.
My workflow/workaround is the following:
When the user gets authorized I took the cookie and send it to the client by using an http header. Now the client can call services if the cookie is set in a http-header (Authorization) of the request.
To achieve this I redirect the faked authorization header to the cookie of the request using a PreRequestFilter. Now I am able to use the session feature. Feels like a hack but works for the moment ;-)
public class CookieRestoreFromAuthorizationHeaderPlugin : IPlugin
{
public void Register(IAppHost appHost)
{
appHost.PreRequestFilters.Add((req, res) =>
{
var cookieValue = req.GetCookieValue("ss-id");
if(!string.IsNullOrEmpty(cookieValue))
return;
var authorizationHeader = req.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.ToLower().StartsWith("basictoken "))
{
var cookie = Encoding.UTF8.GetString(Convert.FromBase64String(authorizationHeader.Split(' ').Last()));
req.Cookies.Add("ss-id",new Cookie("ss-id",cookie));
req.Items.Add("ss-id",cookie);
}
});
}
}