I have made asp.net mvc application that have custom forms authentication.
Beside that it needs to authenticate user from sharepoint (in other words I need to pass user from sharepoint to asp mvc application). SP and asp mvc app are in the same domain and SP is using AD to authenticate user. I have searched google/so and so far I haven`t got any good solution.
Note: I need secure way of passing user from sp to asp mvc application ... I saw few examples that pass user thought URL parameter and I think that this is not secure thing to do.
Why not to use url paramenter?
public class SecureToken {
public Int32 UserId {get;set;}
public DateTime DateCreated {get;set;}
public DateTime ValidTill {get;set;}
public SecureToken (Int32 userId) {
this.UserId = userId;
this.DateCreated = DateTime.Now;
this.ValidTill = this.DateCreated.AddMinutes(0.5);
}
public String ToEncryptedToken() {
// Do serialization,
// Then encrypt with, for example TrippleDES
// Escape for url
// return the string arguement for url
}
public static SecureToken Decrypt(String input) {
// If the DateCreated == ValidTill - 30 seconds
// If validTill > Now
// If decryptable
// Return deserialized token
// else throw Authentication error.
}
}
The point here is that the token while in URL is viable only for 30 seconds.
As an additional parameter you can use HMAC-SHA 256 during serialization and check weather this is really your token.
You could configure SP for a custom forms auth provider which in turn validates to the domain - then you are sharing forms auth tokens between apps which is fairly easy:
http://msdn.microsoft.com/en-us/library/ie/eb0zx8fc.aspx
Related
Microsoft recommend against using HttpContext in Blazor Server (here). To work around the issue of how to pass user tokens to a Blazor Server app, Microsoft recommend storing the tokens in a Scoped service (here). Jon McGuire’s blog suggests a similar approach that stores the tokens in Cache (here).
Microsoft’s approach above works just fine as long as the user stays within the same Blazor Server connection. However if the access_token is refreshed and the user then reloads the page either by pressing F5 or by pasting a URL into the address bar, then an attempt is made to retrieve the tokens from the cookie. By this time, the access_token and refresh_token in the cookie are no longer valid. Jon McGuire mentions this problem at the end of his blog post and refers to it as Stale Cookies (here). He gives hints about a possible solution, but is very light on implementation instructions. There are many comments at the bottom of that post from people unable to implement a solution, with no apparent working solution suggested. I spent a lot of time searching for a solution and all I found were people asking for one and not receiving any answers that worked.
Having found a solution that seems to work well, and also seems fairly principled, I thought it might be worth sharing my solution here. I would welcome any constructive criticism or suggestions for any significant improvements.
Edit 20220715: After some feedback on our approach from Dominic Baier we removed our Scoped UserSubProvider service in favour of using AuthenticationStateProvider instead. This has simplified our approach. I have edited the following answer to reflect this change.
This approach combines advice from Microsoft on how to pass tokens to a Blazor Server app (here), with server side storage of tokens in a Singleton service for all users (inspired by Dominick Baier’s Blazor Server sample project on GitHub here).
Instead of capturing the tokens in the _Host.cshtml file and storing them in a Scoped service (like Microsoft do in their example), we use the OnTokenValidated event in a similar way to Dominick Baier’s sample, storing the tokens in a Singleton service that holds tokens for all Users, we call this service ServerSideTokenStore.
When we use our HttpClient to call an API and it needs an access_token (or refresh_token), then it retrieves the User’s sub from an injected AuthenticationStateProvider, uses it to call ServerSideTokenStore.GetTokensAsync(), which returns a UserTokenProvider (similar to Microsoft’s TokenProvider) containing the tokens. If the HttpClient needs to refresh the tokens then it populates a UserTokenProvider and saves it by calling ServerSideTokenStore.SetTokensAsync().
Another issue we had was if a separate instance of the web browser is open while the app restarts (and therefore loses the data held in ServerSideTokenStore) the user would still be authenticated using the cookie, but we’ve lost the access_token and refresh_token. This could happen in production if the application is restarted, but happens a lot more frequently in a dev environment. We work around this by handling OnValidatePrincipal and calling RejectPrincipal() if we cannot get a suitable access_token. This forces a round trip to IdentityServer which provides a new access_token and refresh_token. This approach came from this stack overflow thread.
(For clarity/focus, some of the code that follows excludes some standard error handling, logging, etc.)
Getting the User sub claim from AuthenticationStateProvider
Our HttpClient gets the user's sub claim from an injected AuthenticationStateProvider. It uses the userSub string when calling ServerSideTokenStore.GetTokensAsync() and ServerSideTokenStore.SetTokensAsync().
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
string userSub = state.User.FindFirst("sub")?.Value;
UserTokenProvider
public class UserTokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTimeOffset Expiration { get; set; }
}
ServerSideTokenStore
public class ServerSideTokenStore
{
private readonly ConcurrentDictionary<string, UserTokenProvider> UserTokenProviders = new();
public Task ClearTokensAsync(string userSub)
{
UserTokenProviders.TryRemove(userSub, out _);
return Task.CompletedTask;
}
public Task<UserTokenProvider> GetTokensAsync(string userSub)
{
UserTokenProviders.TryGetValue(userSub, out var value);
return Task.FromResult(value);
}
public Task StoreTokensAsync(string userSub, UserTokenProvider userTokenProvider)
{
UserTokenProviders[userSub] = userTokenProvider;
Return Task.CompletedTask;
}
}
Startup.cs ConfigureServices (or equivalent location if using .NET 6 / whatever)
public void ConfigureServices(IServiceCollection services)
{
// …
services.AddAuthentication(…)
.AddCookie(“Cookies”, options =>
{
// …
options.Events.OnValidatePrincipal = async context =>
{
if (context.Principal.Identity.IsAuthenticated)
{
// get user sub
var userSub = context.Principal.FindFirst(“sub”).Value;
// get user's tokens from server side token store
var tokenStore =
context.HttpContext.RequestServices.GetRequiredService<IServerSideTokenStore>();
var tokens = await tokenStore.GetTokenAsync(userSub);
if (tokens?.AccessToken == null
|| tokens?.Expiration == null
|| tokens?.RefreshToken == null)
{
// if we lack either an access or refresh token,
// then reject the Principal (forcing a round trip to the id server)
context.RejectPrincipal();
return;
}
// if the access token has expired, attempt to refresh it
if (tokens.Expiration < DateTimeOffset.UtcNow)
{
// we have a custom API client that takes care of refreshing our tokens
// and storing them in ServerSideTokenStore, we call that here
// …
// check the tokens have been updated
var newTokens = await tokenStore.GetTokenAsync(userSubProvider.UserSub);
if (newTokens?.AccessToken == null
|| newTokens?.Expiration == null
|| newTokens.Expiration < DateTimeOffset.UtcNow)
{
// if we lack an access token or it was not successfully renewed,
// then reject the Principal (forcing a round trip to the id server)
context.RejectPrincipal();
return;
}
}
}
}
}
.AddOpenIdConnect(“oidc”, options =>
{
// …
options.Events.OnTokenValidated = async n =>
{
var svc = n.HttpContext.RequestServices.GetRequiredService<IServerSideTokenStore>();
var culture = new CultureInfo(“EN”) ;
var exp = DateTimeOffset
.UtcNow
.AddSeconds(double.Parse(n.TokenEndpointResponse !.ExpiresIn, culture));
var userTokenProvider = new UserTokenProvider()
{
AcessToken = n.TokenEndpointResponse.AccessToken,
Expiration = exp,
RefreshToken = n.TokenEndpointResponse.RefreshToken
}
await svc.StoreTokensAsync(n.Principal.FindFirst(“sub”).Value, userTokenProvider);
};
// …
});
// …
}
I am using Identity Server 4 for authentication for both a website and WPF application. On the website, I want users to have the ability to check a Remember Me box when signing in, but I don't want that for the WPF application. I have the logic to disable that checkbox on the front end, but am having trouble in my controller. I have this function
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
LoginViewModel _vm;
_vm = await BuildLoginViewModelAsync(returnUrl);
//if(Client_id == "wpf") <- this is what I need help with
//{
// _vm.AllowRememberMe = false;
//}
return View(_vm);
}
This controller contains
private readonly IIdentityServerInteractionService mInteraction;
private readonly IClientStore mClientStore;
private readonly IAuthenticationSchemeProvider mSchemeProvider;
private readonly IEventService mEvents;
Any help would be appreciated
You can get the client id from the AuthorizationRequest returned from the IIdentityServerInteractionService as follows using your code snipet:
var context = await mInteraction.GetAuthorizationContextAsync(returnUrl);
_vm.AllowedRememberMe = context.ClientId != "wpf";
However, you would be better off placing this logic in your BuildLoginViewModelAsync method where the view model is constructed rather than setting the property after construction.
I don't believe the client_id is directly available from any IS4 constructs in the Login method. However, depending on your OIDC flow, it's likely that your client_id was passed as part of the "returnUrl" parameter. Look at your return URL and see if it's there.
For example, I have a spa website connecting to IS4 that shows a returnURL of:
https://localhost:8080/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3DspaClient%26redirect_uri%3Dhttps...(long url continues)
You can see that it contains the "client_id" parameter with a value of "spaClient". Simply parse the returnUrl using your code of choice (e.g. RegEx) and extract the client_id from there. I don't have any WPF experience, so it may behave differently and not pass this parameter.
Once a WebAPI access token is generated, how does WebAPI validate that token for the next request? I wonder if I can use an [Authorize] attribute, it must compare the token sent by the client with the token at the server side, if stored somewhere. Does it just check if token is present and not its value?
Bearer token
First of all, your Identity Provider or Token Provider which issues the authorization tokens needs to have the same machine key settings as the Web Api application for encryption/decryption:
<machineKey
decryptionKey="B7EFF1C5839A624ED0268917EDE82F408D2ECBFAC817"
validation="SHA1"
validationKey="C2B8DF31AB9624D8066DFDA1A479542825F3B48865C4E47AF6A026F22D853DEC2B3248DF268599BF89EF78B9E86CA05AC73577E0D5A14C45E0267588850B"
/> </system.web>
Because under the hood Bearertoken uses MachineKey encryption.
In other words if you dont have the same settings, your web api won't be able to decrypt the token (validate it).
This is done automatically by:
Microsoft.Owin.Security.OAuth.dll
using middleware.
You can use the Authorize Attribute on your web api controllers/actions if you want simple authorization with Usernames or roles like this:
[Authorize(Roles="Administrators,Managers",Users ="Mike,Laura")]
If you want custom authorization, then you have to implement a custom authorization attribute which will handle the custom authorization in your web api. If the user is not allowed to pass you will return a 401 UnAuthorized Response:
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate","Bearer location='http://localhost:8323/account/login'");
For example:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class CustomAuthorizeAttribute : System.Web.Http.Filters.AuthorizationFilterAttribute
{
public RulesExampleEnum[] Rules { get; set; }
public string Id { get; set; }
.....
// Summary:
// Calls when a process requests authorization.
//
// Parameters:
// actionContext:
// The action context, which encapsulates information for using System.Web.Http.Filters.AuthorizationFilterAttribute.
public virtual void OnAuthorization(HttpActionContext actionContext);
public virtual Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken);
and register it in your webApiConfig.cs
config.Filters.Add(new CustomAuthorizeAttribute());
and apply it on Web Api controller or action:
[CustomAuthorize(Id = "AnyId", Rules = new RulesExampleEnum[] { RulesExampleEnum.Rule1, RulesExampleEnum.Rule3 })]
public IEnumerable<object> Get()
{...
Once access token is generated, client must include the access token inside Header for each request.
Client may set the access token inside Authorization HTTP Header.
On the server side, you should create class to handle the Authorization, which is a derived class from System.Web.Http.AuthorizeAttribute, something like below :
public class AuthorizationHandlerAttribute : AuthorizeAttribute
{
string AccessTokenFromRequest = "";
if (actionContext.Request.Headers.Authorization != null)
{
// get the access token
AccessTokenFromRequest = actionContext.Request.Headers.Authorization.Parameter;
}
string AccessTokenStored = "";
// write some code to get stored access token, probably from database
// then assign the value to a variable for later use
// compare access token
if (AccessTokenFromRequest != AccessTokenStored)
{
// if the token is not valid then return 401 Http Stasus
// or simply call base method
base.HandleUnauthorizedRequest(actionContext);
}
}
Then you use the newly created class and attach it on controller or action you wished to protect from unauthorized access.
public class UsersController : ApiController
{
[AuthorizationHandler]
public User Get(int id)
{
// only request with valid access token will reach this
}
}
The secret key is transmitted in the header of the request from the client to the server, and the contents are validated at the server at each request where the [Authorize] attribute is used.
You can use a tool like Fiddler from Telerik (free) to see the data being transported, but not the content (since its encrypted). Inspecting raw web traffic is invaluable when working with MVC / WebAPI, so I highly recommend it. Here's a link to Fiddler, although other similar tools exist as well.
http://www.telerik.com/fiddler
To answer the second part of your question, the server absolutely checks the contents of the secret key before allowing the request to proceed as authorized.
I am developing a website using ASP.NET MVC with an API using ServiceStack.
Very soon I want to add authentication. The website will have at least two types of users 'service providers' and 'service consumers', although a user could have multiple roles.
I am open to using new MVC Identity, but I want whatever I use to work nicely for both the servicestack API and MVC 'pages' that don't necessarily use the API but should show different content based on login. I do not want to require javascript for login/logout.
I would like the solution to use tokens as I have not used session state anywhere else, but I am open to other options providing they would scale horizontally on a cloud provider (users next request may go to a different instance of back-end).
Anyone have example of an ideal solution?
(N.B: I am not interested in an externally hosted service).
ServiceStack's Authentication can also be used by external ASP.NET Web Frameworks, the ServiceStack and MVC Integration docs shows how you can accept Login credentials from a MVC Controller and register them with ServiceStack:
public ActionResult Login(string userName, string password, string redirect=null)
{
if (ModelState.IsValid)
{
try
{
using (var authService = ResolveService<AuthenticateService>())
{
var response = authService.Authenticate(new Authenticate {
provider = CredentialsAuthProvider.Name,
UserName = userName,
Password = password,
RememberMe = true,
});
// add ASP.NET auth cookie
FormsAuthentication.SetAuthCookie(userName, true);
return Redirect(string.IsNullOrEmpty(redirect) ? "/" : redirect);
}
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
}
}
return View("Index", GetViewModel());
}
The http://mvc.servicestack.net Live Demo shows an example of calling this controller using a standard MVC HTML Form.
Your MVC Controllers can then inherit ServiceStackController to access the Authenticated Users Session and different ServiceStack providers, here are the API's relating to Session and Authentication:
public class ServiceStackController : Controller
{
//...
ISession SessionBag { get; set; }
bool IsAuthenticated { get; set; }
IAuthSession GetSession(bool reload = true);
TUserSession SessionAs<TUserSession>();
void ClearSession();
}
Enable OAuth Providers
Should you need to you can also enable ServiceStack's different OAuth providers which can optionally callback either directly to a ServiceStack Service or
Further to mythz answer I also needed to know if a user was authenticated in a view and the normal Request.IsAuthenticated does not work when your doing above. So I created a CustomWebViewPage (to use this you will have to change *pageBaseType="Your.NameSpace.CustomWebViewPage" in the View folder's Web.config).
public abstract class CustomWebViewPage : WebViewPage
{
private IServiceStackProvider _serviceStackProvider;
public virtual IServiceStackProvider ServiceStackProvider
{
get
{
return _serviceStackProvider ?? (_serviceStackProvider =
new ServiceStackProvider(new AspNetRequest(base.Context, GetType().Name)));
}
}
public virtual bool IsAuthenticated
{
get { return ServiceStackProvider.IsAuthenticated; }
}
}
public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel>
{
//EXACTLY the same as above method...
I'm using ASP.NET MVC and Forms Authentication on my application. Basically I use FormsAuthentication.SetAuthCookie to login and FormsAuthentication.SignOut to logout.
In the HttpContext.Current.User.Identity I have stored the user name but I need more info about the logged user. I don't want to store my entire User obj in the Session because it might be big and with much more infomation than I need.
Do you think it's a good idea to create like a class called LoggedUserInfo with only the attributes I need and then add it to the Session variable? Is this a good approach?
Or do you have better ideas?
I use this solution:
ASP.NET 2.0 Forms authentication - Keeping it customized yet simple
To summarize: I created my own IPrincipal implementation. It is stored in HttpContext.Current.Cache. If it is somehow lost, I have username from client side authorization cookie and can rebuild it. This solution doesn't rely on Session, which can be easily lost.
EDIT
If you want to use your principal in your controller and make it testable, you can do this:
private MyPrincipal _myPrincipal;
MyPrincipal MyPrincipal
{
get
{
if (_myPrincipal == null)
return (MyPrincipal)User;
return _myPrincipal;
}
set
{
_myPrincipal = value;
}
}
In your test, you will set object prepared for testing. Otherwise it will be taken from HttpContext. And now I started thinking, why do I use Ninject to do it?
Store it server side in the session.
Eg.
// Make this as light as possible and store only what you need
public class UserCedentials
{
public string Username { get; set; }
public string SomeOtherInfo { get; set; }
// etc...
}
Then when they sign in just do the following to save the users info:
// Should make typesafe accessors for your session objects but you will
// get the point from this example
Session["UserCredentials"] = new UserCredentials()
{ Username = "SomeUserName", SomeOtherInfo = "SomeMoreData" };
Then whenever you need it fetch it:
UserCredentials user = (UserCredentials)(Session["UserCredentials"]);
I have written a couple of question/answers regarding doing custom authorization in MVC:
How to implement authorization checks in ASP.NET MVC based on Session data?
How does the Authorize tag work? - Asp.net Mvc
I actually like to use a CustomPrincipal and CustomIdentity which I set in the logon action method like
if (!String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password) && _authService.IsValidLogin(username, password))
{
User objUser = _userService.GetUserByName(username);
if (objUser != null)
{
//** Construct the userdata string
string userData = objUser.RoleName + "|" + objUser.DistrictID + "|" + objUser.DistrictName + "|" + objUser.ID + "|" + objUser.DisplayName;
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(username, rememberMe.GetValueOrDefault());
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, userData);
authCookie.Value = FormsAuthentication.Encrypt(newTicket);
Response.Cookies.Add(authCookie);
return RedirectToAction("Index", "Absence");
}
else
{
return RedirectToAction("LogOn", "Account");
}
}
else
{
return RedirectToAction("LogOn", "Account");
}
Then in the custom principal you can have methods that access specific information you passed in to the constructor like
((CustomIdentity)((CustomPrincipal)HttpContext.Current.User).Identity).DisplayName;
where the DisplayName property is declared in the CustomIdentity class.
Well you will have to store these somewhere. Two main possible places though:
The server
You can either put them into Session. I suggest you do create a separate class that will hold only data that you actually need to avoid of wasting too much memory. Or you can also store into Cache that can end up in having many DB calls when there are huge amounts of concurrent users.
The client
In this case if you can limit the amount of data with a separate class, to that and use whatever way to serialize it and send it to the client. Either in a cookie or in URI (if length permits and cookies are disabled)...
Outcome of these thoughts:
the main thing here would be to create a separate class if you gain much memory resources this way. So that's the first thing you should do.