Spring security using LDAP and group membership - spring-mvc

I am using spring security to verify if the user has passed in valid username and password.
I also want to validate if the user is a part of a particular group.
Though, the credentials verification is working, the group membership verification is not.
Do I need to configure ldapAuthoritiesPopulator?

Though, the credentials verification is working, the group membership verification is not.
I am assuming group membership is combination of ldap base and userDn.
Here is a code to help you.
public class LDAPDetail{
private String url; //your LDAP url
private Long timeout; // some timeout to connect LDAP
private String domain; // domain of user
private String userContainer; // typically value for OU=**,dc=**,dc=**
// You should be getting value for _domain_ and _userContainer_ from user's LDAP detail
}
public void validateUserDetails(){
LdapDetail ldapDetail = //gets user's value which you want to validate.
LdapTemplate ldapTemplate = build(ldapDetail, "username", "password");
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("cn", userName));
ldapTemplate.authenticate(LdapUtils.emptyLdapName(), filter.toString(), "password")
}
public static LdapTemplate build(LdapDetail ldapDetail, String userName, String password) {
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setBase(ldapDetail.getUserContainer());
ldapContextSource.setUrl(ldapDetail.getUrl());
ldapContextSource.setAnonymousReadOnly(true);
ldapContextSource.setCacheEnvironmentProperties(false);
ldapContextSource.setUserDn(ldapDetail.getDomain());
ldapContextSource.setBaseEnvironmentProperties(buildContextFor(ldapDetail, userName, password));
LdapTemplate ldapTemplate = new LdapTemplate(ldapContextSource);
ldapTemplate.setContextSource(ldapContextSource);
return ldapTemplate;
}
public static Map<String, Object> buildContextFor(LdapDetail ldapDetail, String userName, String password) {
Map<String, Object> env = new HashMap<>();
env.put(Context.REFERRAL, "throw");
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PROTOCOL, "ssl");
env.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces:org.jboss.naming:org.jnp.interfaces");
env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(ldapDetail.getTimeout()));
env.put(Context.PROVIDER_URL, ldapDetail.getUrl());
env.put("ldap.domain", ldapDetail.getDomain());
env.put(Context.SECURITY_PRINCIPAL, userName);
env.put(Context.SECURITY_CREDENTIALS, password);
return env;
}

Related

id_token_hint parameter failed signature validation when Using B2C to generate the metadata endpoints

I am attempting to set up a Magic link like system using Azure B2C. Using the following samples:
Primary:
https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-in-with-magic-link
For sing B2C to generate the metadata endpoints:
https://github.com/azure-ad-b2c/samples/tree/master/policies/invite#using-b2c-to-generate-the-metadata-endpoints
As a note I believe I had it working at one point but after a clean up I have been getting the error:
The provided id_token_hint parameter failed signature validation. Please provide another token and try again.
The steps I took to set up is as follows:
Create a cert via powershell and get thumbprint to use in local code
Use certmng via MMC to export cert
All Task / Export / Next / Yes, Export the private key
Personal Information Exchange - PKCS (Include all cert in cert path)(Enable cert privacy)
Security (Password) Randomly Generated Pass 25 character password.
Name: id_token_hint_cert.pfx
Browse Azure / B2C / Identity Experience Framework / Policy keys
Add / Option: Upload / Name: IdTokenHintCert / File Upload id_token_hint_cert.pfx / Password: Password from setup 3
This is where I have tried 2 different set ups. The first was to setup a set of custom policies so that I could update the following claims provider to have issuer_secret set to B2C_1A_IdTokenHintCert
<ClaimsProvider>
<DisplayName>Token Issuer</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="JwtIssuer">
<DisplayName>JWT Issuer</DisplayName>
<Protocol Name="None" />
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="client_id">{service:te}</Item>
<Item Key="issuer_refresh_token_user_identity_claim_type">objectId</Item>
<Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_IdTokenHintCert" />
<Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
</CryptographicKeys>
<InputClaims />
<OutputClaims />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
This is set of policies grabbed from https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/tree/master/LocalAccounts and updated to my tenant but left mostly alone.
I also tried changing out the issuer_secret in my main custom policies with the same error being output.
Heading into my code:
This is the important part of my startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
OpenIdConnectDefaults.AuthenticationScheme
).AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C"),"OpenIdConnect", "Cookies",true);
services.AddControllersWithViews();
services.AddRazorPages()
.AddMicrosoftIdentityUI();
services.AddTransient<IClaimsTransformation, AppClaimsTransformations>();
}
And here is my Home Controller where I submit a form, create the token and link and then just redirect to that link using it as a run now endpoint. (I know this wont end of working but need to get the signature validate before I can move on.)
public class HomeController : Controller
{
private static Lazy<X509SigningCredentials> SigningCredentials;
private readonly AppSettingsModel _appSettings;
private readonly IWebHostEnvironment HostingEnvironment;
private readonly ILogger<HomeController> _logger;
// Sample: Inject an instance of an AppSettingsModel class into the constructor of the consuming class,
// and let dependency injection handle the rest
public HomeController(ILogger<HomeController> logger, IOptions<AppSettingsModel> appSettings, IWebHostEnvironment hostingEnvironment)
{
_appSettings = appSettings.Value;
this.HostingEnvironment = hostingEnvironment;
this._logger = logger;
// Sample: Load the certificate with a private key (must be pfx file)
SigningCredentials = new Lazy<X509SigningCredentials>(() =>
{
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
"***************************************",
false);
// Get the first cert with the thumb-print
if (certCollection.Count > 0)
{
return new X509SigningCredentials(certCollection[0]);
}
throw new Exception("Certificate not found");
});
}
[HttpGet]
public ActionResult Index(string Name, string email, string phone)
{
if (string.IsNullOrEmpty(email))
{
ViewData["Message"] = "";
return View();
}
string token = BuildIdToken(Name, email, phone);
string link = BuildUrl(token);
return Redirect(link);
}
private string BuildIdToken(string Name, string email, string phone)
{
string issuer = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase.Value}/";
// All parameters send to Azure AD B2C needs to be sent as claims
IList<System.Security.Claims.Claim> claims = new List<System.Security.Claims.Claim>();
claims.Add(new System.Security.Claims.Claim("name", Name, System.Security.Claims.ClaimValueTypes.String, issuer));
claims.Add(new System.Security.Claims.Claim("email", email, System.Security.Claims.ClaimValueTypes.String, issuer));
if (!string.IsNullOrEmpty(phone))
{
claims.Add(new System.Security.Claims.Claim("phone", phone, System.Security.Claims.ClaimValueTypes.String, issuer));
}
// Create the token
JwtSecurityToken token = new JwtSecurityToken(
issuer,
"******************************************",
claims,
DateTime.Now,
DateTime.Now.AddDays(7),
HomeController.SigningCredentials.Value);
// Get the representation of the signed token
JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();
return jwtHandler.WriteToken(token);
}
private string BuildUrl(string token)
{
string nonce = Guid.NewGuid().ToString("n");
return string.Format("https://{0}.b2clogin.com/{0}.onmicrosoft.com/{1}/oauth2/v2.0/authorize?client_id={2}&nonce={4}&redirect_uri={3}&scope=openid&response_type=id_token",
"myTenant",
"B2C_1A_SIGNIN_WITH_EMAIL",
"************************************",
Uri.EscapeDataString("https://jwt.ms"),
nonce)
+ "&id_token_hint=" + token;
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
Location Location Location.
I was adjusting the base profile which I learned I should not be doing. When I applied my change to the extension file instead everything starting working properly.

Mock Custom User in Spring Security Test

We are using Spring 4.3.9.RELEASE and Spring Security 4.2.3.RELEASE, so these are some of the latest versions we have seen. We have a RESTful (spring-mvc) backend where we are using Spring Web Security for roles-based access to the API's.
We have a controller that looks like this:
#RequestMapping(value = "/create", method = RequestMethod.POST, produces = "application/json", headers = "content-type=application/json")
public #ResponseBody MyObjectEntity createMyObject(#RequestBody MyObjectEntity myObj) throws MyObjectException
{
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
String email = user.getEmail();
MyObjectEntity myObj = MyObjectService.createMyObject(myObj, email);
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
return myObj;
}
We know a user has logged in from the web-site with a username and password. We know the UI has a token, and they pass it along in the header. Our security uses the SiteMinder example, which means we have a UserDetailsService that goes to a third-party, passes along the token, and we now have the username, password, and the roles the user has. This is normally working well.
We did create a CustomUserDetailsService as follows:
public class CustomUserDetailsService implements UserDetailsService
{
#Override
public UserDetails loadUserByUsername(String accessToken) throws
UsernameNotFoundException,
PreAuthenticatedCredentialsNotFoundException
{
// goto to third-party service to verify token
// get the Custom User and the user roles
// also get some extra data, so a custom user
}
}
So, once we established the token is valid, and we have gotten additional user information from that third-party, and we have the valid role that is authorized for this API ... then we can execute the controller itself. And we see this code is traditional for getting an existing user out of the Spring Security Context.
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
Actually, from what we have read, this is the way to do it when you have a custom user and CustomUserDetails. With this code, we want to get the email of this user. And this all works when we actually test the API with Advanced REST Client. Our QA has to authenticate against the web-site, and they get tokens passed back to the UI, they get those access tokens, and put those in the headers of the Advanced REST Client (or Postman) and this all works.
We even have code to invalidate the security context when the API is over.
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
Against, the real API, with the real progress, this works great.
Now, when it comes to testing, some of the tests work against our secured controllers and some do not. So, here we have a controller to test:
#RequestMapping(value = "/{productId}", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody ProductEntity getProductById(#PathVariable("productId") long productId)
{
logger.debug("ProductController: getProductById: productId=" + productId);
CustomUser user = authenticate();
ProductEntity productEntity = service.getById(productId);
logger.debug("ProductController: getProductById: productEntity=" + productEntity);
invalidateUser();
return productEntity;
}
And here is the test:
#Test
public void testMockGetProductByProductId() throws Exception
{
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user("testuser").roles("REGULAR_USER"));
this.mockMvc.perform(requestBuilder).andDo(print()).andExpect(status().isOk());
}
This works because even when we get to the controller, we don't need the CustomerUser set, so it works. If the role is the correct role ("REGULAR_USER"), then it works, if the role is not correct, we get a 403 error which are expecting.
But if you look at the Controller I first posted at the top, we NEED the CustomUser to be set, and if it isn't set, then when we try to get that email, we fail. So, we have been looking at multiple ways of setting up a mock user in authentication, so when we get to the Controller we can get that CustomUser already in security context.
I've actually done this before, but that was when we were using the standard spring security user, and not a custom user.
We can definitely establish a CustomUser in the security context, but when it gets to the controller, and this code is run ....
// THIS WORKS
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
// This IF fails because;
// userDetails is of instance User (Spring Security User)
// and not CustomUser.
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
Let me add the code we have for our CustomUser:
public class CustomUser implements UserDetails
{
private static final long serialVersionUID = -6650061185298405641L;
private String userName;
private ArrayList<GrantedAuthority> authorities;
private String firstName;
private String middleName;
private String lastName;
private String email;
private String phone;
private String externalUserId;
// getters/setters
// toString
}
I hope I put enough information here that someone can answer my question. I have spent a day or two scouring the internet for someone who can answer this question to no avail. Some of the answers were a little older from Spring 3 and older Spring Security 3.x. if any more information is needed, please let me know. Thanks!
I wonder ... if I need a CustomUserDetails which implments UserDetails?
Thanks again!
This is probably much easier than what you think.
CustomUser userDetails = new CustomUser();
/* TODO: set username, authorities etc */
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user(userDetails));
This is allowed as long as your CustomUser implements UserDetails interface.

ASP.NET OAuth Authorization - Difference between using ClientId and Secret and Username and Password

I'm trying to implement a simple OAuthAuthorizationServerProvider in ASP.NET WebAPI 2. My main purpose is to learn how to have a token for a mobile app. I would like users to login with username & password, and then receive a token (and a refresh token so they won't have to re-enter credentials once token expires). Later on, I would like to have the chance to open the API for external use by other applications (like one uses Facebook api and such...).
Here is how I've set-up my AuthorizationServer:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
Provider = new SimpleAuthorizationServerProvider(new SimpleAuthorizationServerProviderOptions()
{
ValidateUserCredentialsFunction = ValidateUser
}),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
});
This is my SimpleAuthorizationServerProviderOptions implementation:
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public delegate Task<bool> ClientCredentialsValidationFunction(string clientid, string secret);
public delegate Task<IEnumerable<Claim>> UserCredentialValidationFunction(string username, string password);
public SimpleAuthorizationServerProviderOptions Options { get; private set; }
public SimpleAuthorizationServerProvider(SimpleAuthorizationServerProviderOptions options)
{
if (options.ValidateUserCredentialsFunction == null)
{
throw new NullReferenceException("ValidateUserCredentialsFunction cannot be null");
}
Options = options;
}
public SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction)
{
Options = new SimpleAuthorizationServerProviderOptions()
{
ValidateUserCredentialsFunction = userCredentialValidationFunction
};
}
public SimpleAuthorizationServerProvider(UserCredentialValidationFunction userCredentialValidationFunction, ClientCredentialsValidationFunction clientCredentialsValidationFunction)
{
Options = new SimpleAuthorizationServerProviderOptions()
{
ValidateUserCredentialsFunction = userCredentialValidationFunction,
ValidateClientCredentialsFunction = clientCredentialsValidationFunction
};
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
if (Options.ValidateClientCredentialsFunction != null)
{
string clientId, clientSecret;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
var clientValidated = await Options.ValidateClientCredentialsFunction(clientId, clientSecret);
if (!clientValidated)
{
context.Rejected();
return;
}
}
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (Options.ValidateUserCredentialsFunction == null)
{
throw new NullReferenceException("ValidateUserCredentialsFunction cannot be null");
}
var claims = await Options.ValidateUserCredentialsFunction(context.UserName, context.Password);
if (claims == null)
{
context.Rejected();
return;
}
// create identity
var identity = new ClaimsIdentity(claims, context.Options.AuthenticationType);
// create metadata to pass to refresh token provider
var props = new AuthenticationProperties(new Dictionary<string, string>()
{
{ "as:client_id", context.UserName }
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
var currentClient = context.ClientId;
// enforce client binding of refresh token
if (originalClient != currentClient)
{
context.Rejected();
return;
}
// chance to change authentication ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
newIdentity.AddClaim(new Claim("newClaim", "refreshToken"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
}
}
And my SimpleRefreshTokenProvider implementation:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens =
new ConcurrentDictionary<string, AuthenticationTicket>();
public void Create(AuthenticationTokenCreateContext context)
{
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
};
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
_refreshTokens.TryAdd(guid, refreshTokenTicket);
context.SetToken(guid);
}
public void Receive(AuthenticationTokenReceiveContext context)
{
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
}
}
What I don't fully understand is the use of ClientId and Secret vs Username and Password. The code I pasted generates a token by username and password and I can work with that token (until it expires), but when I try to get a refresh token, I must have the ClientId.
Also, if a token expires, the correct way is to send the refresh token and get a new token? What if the refresh token gets stolen? isn't it the same as a username & password getting stolen?
What I don't fully understand is the use of ClientId and Secret vs Username and Password. The code I pasted generates a token by username and password and I can work with that token (until it expires), but when I try to get a refresh token, I must have the ClientId.
Also, if a token expires, the correct way is to send the refresh token and get a new token? What if the refresh token gets stolen? isn't it the same as a username & password getting stolen?
In OAuth2 is essential to authenticate both the user and the client in any authorization flow defined by the protocol. The client authentication (as you may guess) enforces the use of your API only by known clients. The serialized access token, once generated, is not bound to a specific client directly. Please note that the ClientSecret must be treated as a confidential information, and can be used only by clients that can store this information in some secure way (e.g. external services clients, but not javascript clients).
The refresh token is simply an alternative "grant type" for OAuth2, and, as you stated correctly, will substitute the username and password pair for a User. This token must be treated as confidential data (even more confidential than the access token), but gives advantages over storing the username & password on the client:
it can be revoked by the user if compromised;
it has a limited lifetime (usually days or weeks);
it does not expose user credentials (an attacker can only get access tokens for the "scope" the refresh token was issued).
I suggest you to read more about the different grant types defined in OAuth 2 checking in the official draft. I also recommend you this resource I found very useful when firstly implemented OAuth2 in Web API myself.
Sample requests
Here are two request examples using fiddler, for Resource Owner Password Credentials Grant:
and for Refresh Token Grant:

Request email from OAuthWebSecurity.RegisterMicrosoftClient

I am trying to integrate oauth authentication from an MVC 4 project to Microsoft (Live service). The followings lines in AuthConfig.cs have been uncommented:
OAuthWebSecurity.RegisterMicrosoftClient(
clientId: "XXX",
clientSecret: "XXX");
And, the app has been set up at https://manage.dev.live.com
Later, when OAuthWebSecurity.VerifyAuthentication is called, I get back the success status, but the email field is not in the returned data.
How can I request an email to be returned from VerifyAuthentication call issued again Microsoft account?
Thanks.
First, you should implement a 'MicrosoftScopedClient' class which implements 'IAuthenticationClient ' interface, and that should implement two methods of interface which is;
public class MicrosoftScopedClient : IAuthenticationClient
{
//Define following three keys in Web.Config file and use it in code, it will maintain code consistency.
private string clientId;
private string clientSecret;
private string scope;
private const string baseUrl = "https://login.live.com/oauth20_authorize.srf";
private const string tokenUrl = "https://login.live.com/oauth20_token.srf";
public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
{
//Getting values of clientId, clientSecret and scope from Web.Config file
clientId=System.Configuration.ConfigurationManager.AppSettings["msClientId"].ToString();
clientSecret=System.Configuration.ConfigurationManager.AppSettings["msClientSecret"].ToString();
scope=System.Configuration.ConfigurationManager.AppSettings["msScope"].ToString();
string url = baseUrl + "?client_id=" + clientId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=" + HttpUtility.UrlEncode(scope) + "&response_type=code";
//this will authenticate the user and register(only if user visited first time).
context.Response.Redirect(url);
}
public AuthenticationResult VerifyAuthentication(HttpContextBase context)
{
string code = context.Request.QueryString["code"];
string rawUrl = context.Request.Url.ToString();
//removing code portion
rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");
IDictionary<string, string> userData = GetUserData(code, rawUrl);
if (userData == null)
return new AuthenticationResult(false, ProviderName, null, null, null);
string id = userData["id"];
string username = userData["email"]; // here you'll get email id of user
userData.Remove("id");
userData.Remove("email");
AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
return result;
}
}
////// Finally you need to register all stuffs in AuthConfig.cs and interact with Microsoft through our application.
OAuthWebSecurity.RegisterClient(new MicrosoftScopedClient(System.Configuration.ConfigurationManager.AppSettings["msClientId"].ToString(),
System.Configuration.ConfigurationManager.AppSettings["msClientSecret"].ToString(),
"wl.basic wl.emails"
)
, "Microsoft", null);

ASP.NET how to store user login data in the APP

Just wondering what the best way to store user login data on successful login in my application. I.e. when logged in at the moment I do something like this in my login script
Session("loggedIn") = "Yes"
Session("userName") = reader_login("useremail").ToString()
Session("userId") = reader_login("user_ID").ToString()
Session("firstName") = reader_login("firstName").ToString()
Session("lastName") = reader_login("lastName").ToString()
And then I use these session variables on my scripts, but just noticed that every time I want to use some of these session variables I do need to check if they are nut null before calling them, seems a bit clumsy to repeat these for a lot of my .aspx pages. Is there a better way to do this ?
EDIT :
Also I am wondering why do I need to add the IS NUll check for the session on each script I use session variables I put the check in the master page but noticed I still get null exception in my usercontrol which is referenced in my master page but does not have the IS null check
Session is not the way to check whether user is authenticated or not. Session may be cleared on demand by administrator when clearing app pool, or by the low memory on server. You won't wish to log out user in such cases. The builtin and reccommended way for doing this in ASP.NET is storing data in authentication cookie. Once the user is logged in, you issue the cookie that contains all the data, including user id, name, etc. And then, you don't have to check every property in session for null, more simple - you just check if the user is authenticated - then you've got the data, else -not. And the other benefit, if you substitute builtin principal with custom one, you can define strongly typed object that holds user data, no more casting from objects extracted from session. Here're the examples for defining custom principal with forms authentication
First, let's define custom MyIdentity and MyPrincipal
public class MyIdentity : IIdentity
{
private FormsAuthenticationTicket _Ticket;
private int _userId = 0;
public FormsAuthenticationTicket Ticket
{
get { return _Ticket; }
}
public string Name
{
get { return _Ticket.Name; }
}
public int UserId
{
get
{
if (_userId == 0)
_userId = Convert.ToInt32(_Ticket.UserData.Split("|".ToCharArray())[0]);
return _userId;
}
}
public Identity(FormsAuthenticationTicket ticket)
{
this._Ticket = ticket;
}
public string AuthenticationType
{
get { return "Custom"; }
}
public bool IsAuthenticated
{
get { return UserId > 0; }
}
}
Then the MyPrincipal that holds MyIdentity
public class MyPrincipal : IPrincipal
{
private MyIdentity _Identity;
public Principal(MyIdentity identity)
{
_Identity = identity;
}
public IIdentity Identity
{
get { return _Identity; }
}
public bool IsInRole(string role)
{
return false;
}
}
Then substitute original forms user with the custom one. In Global.asax
private void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
IPrincipal usr = HttpContext.Current.User;
// If we are dealing with an authenticated forms authentication request
if (usr.Identity.IsAuthenticated && usr.Identity.AuthenticationType == "Forms")
{
FormsIdentity formsIdentity = usr.Identity as FormsIdentity;
// Create a CustomIdentity based on the FormsAuthenticationTicket
IIdentity identity = new MyIdentity(formsIdentity.Ticket);
IPrincipal principal = new MyPrincipal(identity);
// Attach the CustomPrincipal to HttpContext.User and Thread.CurrentPrincipal
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
}
Define method for issuing forms authentication ticket. Later, the custom MyIdentity class will extract userId and other methods from userData.
public static HttpCookie GetAuthCookie(string userName, string userData, bool createPersistentCookie, HttpSessionStateBase session)
{
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(userName, createPersistentCookie);
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, userData, session.SessionID);
authCookie.Value = FormsAuthentication.Encrypt(newTicket);
return authCookie;
}
When the user is checked and is authenticated, return them authentication cookie
Response.Cookies.Add(AuthenticationCookie.GetAuthCookie(model.UserName, GetUserInfo(model.UserName, passwordHash), model.RememberMe, Session));
//GetUserInfo returns | separated string of user datas. "userId|userName|firstName|lastName" for example.
And at last, using all of the above in code
if(User.Identity.IsAuthenticated)
{
int userId = ((MyIdentity)User.Identity).UserId;
}
This may seem the larger code, but in runtime it'll give much more benefits than storing all the data in session. The main of them are null checking and casting every time.
You could load this through a single object which you put in the Session. This will remove all your strings as you can just set properties. Also you can check if the object is available in the session, if it's not the user is not logged in?
public class CurrentUserObject
{
public string UserName { get; set; }
public string UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public CurrentUserObject(string userName, string userID, string firstName, string lastName)
{
UserName = userName;
UserID = userID;
FirstName = firstName;
LastName = lastName;
}
}
You can instantiate this object and store it in Session("CurrentUser") or something. If you request this session variable and it turns out to be null, your user is not logged in. I would advise you to do this in a master page or something by the way to avoid duplication of this code.
you don't have to store "loggedIn" in session.
you can use Session["userName"] to check, if it is null, not logged in; not null, logged in.
try to use one session item to track user login status, such username or userid.
also you can encapsule the logic into a method such as
static bool CheckLogin(HttpSession sessionState, out username, out userId, out firstName, out LastName);
FYI
may be you need to use caching in your application because you are going to check if null or not every time i think for save use data caching will be better and here are some links :
http://msdn.microsoft.com/en-us/library/xsbfdd8c(v=vs.71).aspx
http://msdn.microsoft.com/en-us/library/ms972379.aspx
http://www.exforsys.com/tutorials/asp.net/caching-in-asp.net.html
http://www.codeproject.com/KB/web-cache/cachingaspnet.aspx
Hope it helps mark as answered if it helps :)

Resources