A web application where people can view their SSRS reports internally or externally.
I'm trying to authenticate users logging in against the active directory groups using custom authorize roles, so the reports can be secured down based on if they are in a particular AD Group or not.
Now I know this works out the box with windows auth/form auth, but I'm using a custom authentication due to other reasons but what I do have is a table which has the custom usernames that the users are logging with mapped against their windows credentials.
I've been following this blog to test out this method of authenticating against the active directory groups and customized it to pass in the windows credentials mapped to the custom user login but having no luck so far.
With regards to the custom authentication, when I go find the matching domain name out of my table and store that domain name into the session variable, which then is passed into this AD authentication process for checking if the user exists in the group or not, see code below.
Custom authorize attribute,
using Helpers;
using Models;
using System;
using System.Web;
using System.Web.Mvc;
namespace Application.Validators
{
public class AuthorizeADAttribute : AuthorizeAttribute
{
public string Group { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (string.IsNullOrEmpty(Group))
{
return true;
}
var logOnInfo = httpContext.Session["LogOnInfo"] as LogOnModel;
var username = logOnInfo.DomainName;
try
{
return LDAPHelper.UserIsMemberOfGroups(username, Group);
}
catch (Exception ex)
{
return false;
}
}
}
}
LDAP Helper,
using System;
using System.Configuration;
using System.DirectoryServices.AccountManagement;
using System.Web;
namespace Application.Helpers
{
public class LDAPHelper
{
public static string GetLDAPContainer()
{
Uri ldapUri;
ParseLDAPConnectionString(out ldapUri);
return HttpUtility.UrlDecode(ldapUri.PathAndQuery.TrimStart('/'));
}
public static bool ParseLDAPConnectionString(out Uri ldapUri)
{
string connString =
ConfigurationManager.ConnectionStrings["ADConnectionString"]
.ConnectionString;
return Uri.TryCreate(connString, UriKind.Relative, out ldapUri);
}
public static bool UserIsMemberOfGroups(string username, string Group)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(Group))
{
return false;
}
// Verify that the user is in the given AD group (if any)
using (var context = BuildPrincipalContext())
{
var userPrincipal = UserPrincipal.FindByIdentity(context,
IdentityType.SamAccountName,
username);
return userPrincipal.IsMemberOf(context, IdentityType.Name, Group);
}
}
public static PrincipalContext BuildPrincipalContext()
{
string container = GetLDAPContainer();
return new PrincipalContext(ContextType.Domain, null, container);
}
}
}
LDAP Connection string in the web.config (can confirm is correct),
<add name="ADConnectionString" connectionString="LDAP://CN=Managers;OU=Groups,OU=Users,DC=domain"/>
My issue I think is when I'm trying to return the container (GetLDAPHelper Method) from the LDAP conn string back to the PrincipalContext it justs returning null and throwing an error.
I'm looking to see if anyone has done anything remotely similar or is there a more suitable method for trying to achieve what i'm doing?
The issue is that the LDAP connection string is not a valid Uri, so when you attempt to make it one, ldapUri remains null. If you need to parse the connection string, for some reason, you'll need to do it another way. You can't use Uri.
Related
I'm using Identity Server 3 to authenticate and generate Access/Refresh tokens for my angular Client.
I'm currently setting the Refresh Token to expire in 48 hours for my Angular Client.
Some users who use my Angular application will need to be Signed On for 100 days straight without having to re-enter their credentials, is it possible to set the expiration of my Refresh Token for a specific user only instead of the entire client?
I have 100 users in my database, I want just one specific user to not need to re-authenticate in 100 days while the rest should authenticate every 48 hours.
Something along the lines of:
if (user == "Super Man") {
AbsoluteRefreshTokenLifetime = TimeSpan.FromDays(100.0).Seconds,
}
Is this possible to achieve? or am I restricted to only setting the Refresh Token Expiration for the Entire Client?
Thank You
I've never worked with IdentityServer3 and I didn't test the code below, but I think the concept may work.
When I take a look at the code of IdentityServer3 then I can see that in DefaultRefreshTokenService.CreateRefreshTokenAsync the lifetime is set:
int lifetime;
if (client.RefreshTokenExpiration == TokenExpiration.Absolute)
{
Logger.Debug("Setting an absolute lifetime: " + client.AbsoluteRefreshTokenLifetime);
lifetime = client.AbsoluteRefreshTokenLifetime;
}
else
{
Logger.Debug("Setting a sliding lifetime: " + client.SlidingRefreshTokenLifetime);
lifetime = client.SlidingRefreshTokenLifetime;
}
You wouldn't want to change the core code, but you should be able to override the IRefreshTokenService with your own implementation.
When I take the code from CustomUserService sample as example:
internal class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/core", coreApp =>
{
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
var refreshTokenService = new MyDefaultRefreshTokenService();
// note: for the sample this registration is a singletone (not what you want in production probably)
factory.RefreshTokenService = new Registration<IrefreshTokenService>(resolver => refreshTokenService);
Where MyDefaultRefreshTokenService is a copy of the DefaultRefreshTokenService.
In order to make it compile add a NuGet package of IdentityModel (v1.13.1) and add the following class:
using System;
namespace IdentityServer3.Core.Extensions
{
internal static class DateTimeOffsetHelper
{
internal static Func<DateTimeOffset> UtcNowFunc = () => DateTimeOffset.UtcNow;
internal static DateTimeOffset UtcNow
{
get
{
return UtcNowFunc();
}
}
internal static int GetLifetimeInSeconds(this DateTimeOffset creationTime)
{
return (int)(UtcNow - creationTime).TotalSeconds;
}
}
}
Now there are some compilation errors concerning the events. You can remove the events in order to test the code. If it works you can always choose to add them.
And now for the implementation of the RefreshTokenLifetime per user. In your version of the RefreshTokenService you can remove the client code and use your own logic to determine the lifetime per user.
The subject is available, though I don't know if it already contains enough information. But if it does then you can access the userManager to read the lifetime from the store. Or use an alternative to pass the lifetime information (perhaps you can use a claim containing the lifetime value).
Again, I didn't test this, but I think the concept should work.
Considerations
Consider sliding sessions for example. With sliding sessions, you would send a new short-lived token with every authenticated action made by the user. As long as the user is active he will stay authenticated (e.g. it requires user interaction before expiration interval, although it requires token management implementations). If the user sends an expired token, it means he has been inactive for a while.
Let's see how JWT works:
The JWT is mainly suitable for the following cases:
In case of building API services that need to support
server-to-server or client-to-server (like a mobile app or single page app (SPA)) communication, using JWTs as your API tokens is a
very smart idea (clients will be making requests frequently, with
limited scope, and usually authentication data can be persisted in a
stateless way without too much dependence on user data).
If you’re building any type of service where you need three or more
parties involved in a request, JWTs can also be useful.
if you’re using user federation (things like single sign-on and
OpenID Connect), JWTs become important because you need a way to
validate a user’s identity via a third party.
more clarification at stop using jwts as session tokens
So Stop using JWT for sessions, it’s a bad idea to use JWTs as session tokens for most of cases.
Possible Solution
For Refreshing JWT, the JWT refresh tokens and .NET Core may be useful to implement your own code And descriptions inside JWT (JSON Web Token) automatic prolongation of expiration guides you to design a working scenario. You need to inspect desired user before refreshing operation.
I found another implementation at Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token for you, maybe useful.
I'm not familiar with Microsoft's Identity Server (the "Identity Service" I refer to in the code below is a custom implementation), but you could consider writing an authentication handler to intercept the token in HTTP headers, examine a token prefix, then decide whether to process normally or allow an extended lifetime.
In my case, I intercept the token prior to JWT processing it. (I had to do this to get around a SharePoint workflow limitation. Oh, SharePoint.) Here's the AuthenticationHandler class:
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace CompanyName.Core2.Application.Middleware
{
[UsedImplicitly]
public class AuthenticationHandler : AuthenticationHandler<AuthenticationOptions>
{
public const string AuthenticationScheme = "CompanyName Token";
[UsedImplicitly] public const string HttpHeaderName = "Authorization";
[UsedImplicitly] public const string TokenPrefix = "CompanyName ";
public AuthenticationHandler(IOptionsMonitor<AuthenticationOptions> Options, ILoggerFactory Logger, UrlEncoder Encoder, ISystemClock Clock)
: base(Options, Logger, Encoder, Clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue(HttpHeaderName, out StringValues authorizationValues))
{
// Indicate failure.
return await Task.FromResult(AuthenticateResult.Fail($"{HttpHeaderName} header not found."));
}
string token = authorizationValues.ToString();
foreach (AuthenticationIdentity authenticationIdentity in Options.Identities)
{
if (token == $"{TokenPrefix}{authenticationIdentity.Token}")
{
// Authorization token is valid.
// Create claims identity, add roles, and add claims.
ClaimsIdentity claimsIdentity = new ClaimsIdentity(AuthenticationScheme);
claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, authenticationIdentity.Username));
foreach (string role in authenticationIdentity.Roles)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
}
foreach (string claimType in authenticationIdentity.Claims.Keys)
{
string claimValue = authenticationIdentity.Claims[claimType];
claimsIdentity.AddClaim(new Claim(claimType, claimValue));
}
// Create authentication ticket and indicate success.
AuthenticationTicket authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), Scheme.Name);
return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
}
// Indicate failure.
return await Task.FromResult(AuthenticateResult.Fail($"Invalid {HttpHeaderName} header."));
}
}
}
Then in the Startup class of your service, add code to decide which authentication handler to use. The key feature here is the ForwardDefaultSelector:
public void ConfigureServices(IServiceCollection Services)
{
// Require authentication token.
// Enable CompanyName token for SharePoint workflow client, which cannot pass HTTP headers > 255 characters (JWT tokens are > 255 characters).
// Enable JWT token for all other clients. The JWT token specifies the security algorithm used when it was signed (by Identity service).
Services.AddAuthentication(AuthenticationHandler.AuthenticationScheme).AddCompanyNameAuthentication(Options =>
{
Options.Identities = Program.AppSettings.AuthenticationIdentities;
Options.ForwardDefaultSelector = HttpContext =>
{
// Forward to JWT authentication if CompanyName token is not present.
string token = string.Empty;
if (HttpContext.Request.Headers.TryGetValue(AuthenticationHandler.HttpHeaderName, out StringValues authorizationValues))
{
token = authorizationValues.ToString();
}
return token.StartsWith(AuthenticationHandler.TokenPrefix)
? AuthenticationHandler.AuthenticationScheme
: JwtBearerDefaults.AuthenticationScheme;
};
})
.AddJwtBearer(Options =>
{
Options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Program.AppSettings.ServiceOptions.TokenSecret)),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(_clockSkewMinutes)
};
});
Add an extension method to the AuthenticationBuilder class:
public static AuthenticationBuilder AddCompanyNameAuthentication(this AuthenticationBuilder AuthenticationBuilder, Action<AuthenticationOptions> ConfigureOptions = null)
{
return AuthenticationBuilder.AddScheme<AuthenticationOptions, AuthenticationHandler>(AuthenticationHandler.AuthenticationScheme, ConfigureOptions);
}
And authentication options if you need them.
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
namespace CompanyName.Core2.Application.Middleware
{
public class AuthenticationOptions : AuthenticationSchemeOptions
{
[UsedImplicitly]
public AuthenticationIdentities Identities { get; [UsedImplicitly] set; }
public AuthenticationOptions()
{
Identities = new AuthenticationIdentities();
}
}
}
AuthenticationIdentities is just a class I define to associate a token with a username, roles, and claims (the token for the SharePoint workflow engine). It's populated from appsettings.json. Your options class most likely would contain a list of users who are authorized for an extended lifetime.
using System.Collections.Generic;
using JetBrains.Annotations;
namespace CompanyName.Core2.Application.Middleware
{
public class AuthenticationIdentity
{
public string Token { get; [UsedImplicitly] set; }
public string Username { get; [UsedImplicitly] set; }
[UsedImplicitly] public List<string> Roles { get; [UsedImplicitly] set; }
[UsedImplicitly] public Dictionary<string, string> Claims { get; [UsedImplicitly] set; }
public AuthenticationIdentity()
{
Roles = new List<string>();
Claims = new Dictionary<string, string>();
}
}
}
i am trying to build custom AuthorizAttribute and overriding AuthorizeCore
it's working fine every where
but when i went to access restrict url which is not permitted without specific role, it allow me to go there. like when i hit URL "http://localhost:8758/Classified/Attributes" it requires admin role but my code allowing to access it without admin role.
Am doing something wrong?
here is my code.
using System;
using System.Web;
using System.Web.Mvc;
using Classified.Web.Services;
namespace Classified.Web
{
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public IFormsAuthenticationService AuthenticationService { get; set; }
public string RequiredRole;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) throw new ArgumentNullException("httpContext");
AuthenticationService = new FormsAuthenticationService(new HttpContextWrapper(HttpContext.Current));
var user = AuthenticationService.GetAuthenticatedUser();
if (user == null)
return false;
foreach (var i in user.Roles)
{
if (i.RoleName == RequiredRole)
{
return true;
}
}
return false;
}
}
I got solution by my self...
There was a little mistake i just forgot to apply authorize before controller.
Something like that.
[Authorize]
public class AdminController : Controller
{
.
.
.
In my MVC-5 application I am adding/editing users in active directory through my application. Whenever I make changes in AD, each time I need to pass ldapUserName(Admin) and ldapPassword(pass#123) to connect to Active directory. Then only I can perform operation in AD. In place of passing credentials each time I would like to use Service account (domain\service_account) under which my application in running in order to connect to AD. How to achieve that ??
Simply creating the DirectoryEntry by new DirectoryEntry(ldabPath, null, null) will use credential of current user (service account in case of Windows service). The key is passing null to both username and password.
Is that what you want?
Just make sure the service account has enough permission to set password for all users involved.
You can choose one of 2 options.
Option 1. You run your application on a computer which is joined to a domain, where you add\remove users from. In this case the simplest solution is to run your application (application pool) under the domain account with sufficient permissions (in simple way can be an account that belongs to Domain Admins group. Will be also an admin on the host, where application runs)
Option 2. You run your application on a standalone computer, that is not joined to a domain. In this case you can choose one of the following:
A. Impersonate your thread to act as the domain account when performing all network connections. You need to use LogonUser function with LOGON32_LOGON_NEW_CREDENTIALS flag. The drawback of this method is that all network connections (e. g. you connect to a network share) will be made under domain account. For more details on implementation, see this post.
B. Create a connection manager which will create DirectoryEntry for you with required credentials. See the code below:
public interface IDirectoryEntryManager
{
DirectoryEntry GetDirectoryEntry(string domain, string baseDn);
}
public interface ICredentialProvider
{
Credential GetCredential(string domain);
}
public class Credential
{
public string UserName { get; set; }
public string Password { get; set; }
}
public class DirectoryEntryManager : IDirectoryEntryManager, IDisposable
{
private class DomainConnectionInfo
{
internal DomainConnectionInfo(string server, Credential credential)
{
Server = server;
Credential = credential;
}
internal string Server { get; private set; }
internal Credential Credential { get; private set; }
}
private bool disposed;
ICredentialProvider _credentialProvider;
Dictionary<string, DomainConnectionInfo> connectionsInfo = new Dictionary<string, DomainConnectionInfo>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, DirectoryEntry> connections = new Dictionary<string, DirectoryEntry>(StringComparer.OrdinalIgnoreCase);
public DirectoryEntryManager(ICredentialProvider credentialProvider)
{
_credentialProvider = credentialProvider;
}
public DirectoryEntry GetDirectoryEntry(string domain, string baseDn)
{
if (disposed)
{
throw new ObjectDisposedException(this.GetType().Name);
}
return GetOrCreateConnection(domain, baseDn);
}
public void Dispose()
{
if (!disposed)
{
foreach (var connection in connections)
{
connection.Value.Dispose();
}
connections.Clear();
disposed = true;
}
}
private DirectoryEntry GetOrCreateConnection(string domain, string baseDn)
{
DomainConnectionInfo info;
if (!connectionsInfo.TryGetValue(domain, out info))
{
var credential = _credentialProvider.GetCredential(domain);
var dc = DomainController.FindOne(new DirectoryContext(DirectoryContextType.Domain, credential.UserName, credential.Password));
info = new DomainConnectionInfo(dc.Name, credential);
// maintaining a connection to rootDse object to make all LDAP queries use this single connection under the hood. Increasing performance
var entry = new DirectoryEntry(string.Format("LDAP://{0}/RootDSE", dc.Name));
entry.RefreshCache();
connections.Add(domain, entry);
connectionsInfo.Add(domain, info);
}
return new DirectoryEntry(string.Format("LDAP://{0}/{1}", info.Server, baseDn), info.Credential.UserName, info.Credential.Password);
}
}
Didn't test the code. Use server bind instead of serverless bind (e. g. LDAP://domain.com) is better in case if you create a user in one part of program and try to access it in another part. Using serverless bind you can connect to different DCs, so the user you are trying to access may be not replicated to 2nd DC.
Be aware that the domain controller may become unavailable, so you need to implement logic, to search for another DC and refresh your connections cache if required.
You may store credential in a file\LSA possibly encrypted and make DirectoryEntryManager class singleton.
I have a project in VB.NET which is using asp.net membership to manage user authentication. Now I want to build android app for this project so I decided to learn WCF and I have got average hold on WCF webservices. Now the issue I am facing is that when the user login into the android app following things happen:
Request goes to the webapplication and user credentials are authenticated.
After that when the user tries submits any data or try to view data , request again goes to the web application but now the web application should authenticate the user based on the credentials he has provided in the first request for login from the membership authentication.
Now the issue I am facing how to authenticate user in asp.net membership for each WCF Request in Per-Session Service call mode from java(Android).
There's several ways to do what I think you're asking for, I've thought of (and written) a few different potential solutions, however, the one I'm sharing here is something that I think will "slot-in" to existing solutions using ASP.NET Membership/Roles Provider. Hopefully I've given you enough information to do what you need to do, but you can always comment and ask more questions if anything's still unclear.
In your problem you describe using an ASP.NET Web Application containing a WCF Service for existing clients, but you're looking to expand to using Android (java) requests? Given that the ASP.NET Membership provider uses alot of "behind the scenes" SOAP interchanges (for authentication, authorization and encryption) that seem to be built into the service reference frameworks it'd be a fairly big task to write a java implementation...
So, I've written you an example of something that will integrate to the same "backend" provider, but will also allow you to send SOAP requests from any client without needing the service reference (I tested it using SoapUI for example)... I've written my solution in C# (as it's what the WCF samples were written in), however, you can quite easily use a code-converter to switch it to VB.NET. I also haven't provided you with the method to encrypt and decrypt passwords, you'll have to research that bit yourself.
You'll need to implement a new .svc file into your existing solution and create new web.config entries accordingly (I assume you know how to create a basicHttpBinding and service endpoint already).
You'll also need to duplicate your method calls (or instead, create a new class with the method content and reference it from wherever you're implementing the ServiceContract methods) and remove the "[PrincipalPermission(SecurityAction" attributes, and add the example methods below into the new service.
e.g. (using methods from Microsoft's MembershipAndRoleProvider WCF Sample) -
// Allows all Users to call the Add method
[PrincipalPermission(SecurityAction.Demand, Role = "Users")]
public double Add(double n1, double n2)
{
double result = n1 + n2;
return result;
}
Would become:
// Allows all Users to call the Add method
public double Add(double n1, double n2, string username, string token)
{
string isAuthorized = IsAuthorized(username, "Users", token)
if (isAuthorized.Contains("Success")
double result = n1 + n2;
return result;
else
throw new Exception("Authorization Exception: " + isAuthorized);
}
Here's my implementation(s), integrated into the Microsoft WCF Sample MembershipAndRoleProvider (download from here):
IsolatedAuthService.svc
<%#ServiceHost Language="C#" Debug="true" Service="Microsoft.ServiceModel.Samples.IsolatedAuthService" CodeBehind="IsolatedAuthService.cs" %>
IIsolatedAuthService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace Microsoft.ServiceModel.Samples
{
// Define a service contract.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface IIsolatedAuthService
{
[OperationContract]
string IsAuthorized(string username, string roleName, string token);
[OperationContract]
string AuthenticateUser(string username, string encryptedPassword);
}
}
IsolatedAuthService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Web.Security;
using System.Web.Configuration;
using System.Configuration;
using System.IO;
using System.Security.Permissions;
using System.Security.Principal;
using System.ServiceModel.Activation;
using System.Threading;
namespace Microsoft.ServiceModel.Samples
{
public class IsolatedAuthService : IIsolatedAuthService
{
public string IsAuthorized(string username, string roleName, string token)
{
MembershipUser user = Membership.GetAllUsers()[username];
Configuration config = ConfigurationManager.OpenExeConfiguration(HostingEnvironment.MapPath("~") + "\\web.config");
SessionStateSection sessionStateConfig = (SessionStateSection)config.SectionGroups.Get("system.web").Sections.Get("sessionState");
InMemoryInstances instance = InMemoryInstances.Instance;
// Check for session state timeout (could use a constant here instead if you don't want to rely on the config).
if (user.LastLoginDate.AddMinutes(sessionStateConfig.Timeout.TotalMinutes) < DateTime.Now)
{
// Remove token from the singleton in this instance, effectively a logout.
instance.removeTokenUserPair(username);
return "User Unauthorized - login has expired!";
}
if (!instance.checkTokenUserPair(username, token))
return "User Unauthorized - not a valid token!";
// Check for role membership.
if (!Roles.GetUsersInRole(roleName).Contains(user.UserName))
return "User Unauthorized - Does not belong in that role!";
return "Success - User is Authorized!";
}
public string AuthenticateUser(string username, string encryptedPassword)
{
if (Membership.ValidateUser(username, Decrypt(encryptedPassword)))
{
// Not sure if this is actually needed, but reading some documentation I think it's a safe bet to do here anyway.
Membership.GetAllUsers()[username].LastLoginDate = DateTime.Now;
// Send back a token!
Guid token = Guid.NewGuid();
// Store a token for this username.
InMemoryInstances instance = InMemoryInstances.Instance;
instance.removeTokenUserPair(username); //Because we don't implement a "Logout" method.
instance.addTokenUserPair(username, token.ToString());
return token.ToString();
}
return "Error - User was not able to be validated!";
}
}
}
InMemoryInstances.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.ServiceModel.Samples
{
public class InMemoryInstances
{
private static volatile InMemoryInstances instance;
private static object syncRoot = new Object();
private Dictionary<string, string> usersAndTokens = null;
private InMemoryInstances()
{
usersAndTokens = new Dictionary<string, string>();
}
public static InMemoryInstances Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new InMemoryInstances();
}
}
return instance;
}
}
public void addTokenUserPair(string username, string token)
{
usersAndTokens.Add(username, token);
}
public bool checkTokenUserPair(string username, string token)
{
if (usersAndTokens.ContainsKey(username)) {
string value = usersAndTokens[username];
if (value.Equals(token))
return true;
}
return false;
}
public void removeTokenUserPair(string username)
{
usersAndTokens.Remove(username);
}
}
}
Bare in mind, this solution will not work if you're load-balancing your WCF service across multiple servers (due to the in-memory instance class), you could change the solution to use a database table instead of the in-memory instances if this is a requirement for you.
I have a couple of table on my database that specify witch users ( Depending on your AD Username) can actually use the current ASP.NET MVC 2 app I'm building.
My question is how ( or more likely where and where do I put it? On the master page?? ) do i write a method that gets the AD user out of the HTTP context and validates it against the database to see if you can actually use the app? If you can... the idea it's to write a couple of keys in the Session object with the information I need ( Role, Full Name, etc ).
I'm quite confused regarding how I should accomplish this and if it's actually the right way... Keep in mind that I have an admin section and non-admin section in my app.
Any thoughts?
Edit: Keep in mind that I do not care to authenticate the user through a form. All I want to check is if according to my database and your AD username you can use my app. If you can write to session in order to perish the information I need. Otherwise just throw an error page.
This is what I've implemented so far, is this the way to go?
What's the second method for? ( I'm sorry I'm kind of new to c#) What I want to do it's actually throw a view if yo're not authorized...
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
var canUse = this._userRepo.CanUserUseApp(httpContext.User.Identity.Name);
if (!canUse)
{
isAuthorized = false;
}
}
return isAuthorized;
}
You could activate and use Windows (NTLM) authentication and then write a custom [Authorize] attribute where you could fetch the currently connected AD user and perform the additional check of whether he is authorized or not to use the application against your data store. Then you would decorate controllers/actions that require authorization with this custom attribute.
UPDATE:
Here's an example of how such custom attribute might look like:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
// The user is authorized so far => check his credentials against
// the custom data store
return IsUserAllowedAccess(httpContext.User.Identity.Name);
}
return isAuthorized;
}
private bool IsUserAllowedAccess(string username)
{
throw new NotImplementedException();
}
}
and then:
[MyAuthorize]
public class FooController: Controller
{
public ActionResult Index()
{
...
}
}
Create a class called AdminAttribute with this code
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AdminsAttribute : AuthorizeAttribute
{
public AdminsAttribute()
{
this.Roles = "MSH\\GRP_Level1,MSH\\Grp_Level2";
}
}
public class HomeController : Controller
{
[Admins]
public ActionResult Level1()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}