I am using Microsoft.Owin to build Sign-On Application integrated with many other applications.
As summary, I generate an accessToken for every application try to login. the application verify accessToken and sign in successfully.
Code sample:
var identity = UserService.UserManager.CreateIdentity(user, DefaultAuthenticationTypes.ExternalBearer);
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.AllowRefresh = true;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
var dataProtectionProvider = new EFileDataProtectionProvider(client.ClientID, client.ClientSecret);
var accessTokenFormat = new TicketDataFormat(dataProtectionProvider);
Startup.OAuthBearerOptions.AccessTokenFormat = accessTokenFormat;
string accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
return accessToken;
The problem is:
How to force all other applications to sign out when a user signed out from one application ???
As far as I understood you are trying to logout all the other devices once one device is logged in thus making only one user on the account at all times.
What you need to do is update your user security stamp like so :
await UserManager.UpdateSecurityStampAsync(user.Id);
and then the asp.identity validation will take care of the rest
Please note to have the validation enabled with "OnValidateIdentity" like so:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromSeconds(60),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
This will check stamp validation every 60 seconds on every device.
Related
I have several MVC 4 apps in the same server that all utilize microsoft identity and share the same database. The machinekey in the web config is the same for all of them and they all have the same ConfigureAuth method with the same cookie name and domain. Thus if one is logged into one app they are authorized on the others. I would like to consolidate my login logic to one app and have all the other apps redirected to the same app for authentication. I know this can be done in Forms Authentication in the web.config file, but I cannot find how to do this using the Cookies Authentication. I have tried to change the pathstring in this line: LoginPath = new PathString("/Account/Login"), but it will always add the base as the directory of the app and will not redirect to another app in the server. Here is my code, its pretty much out of the box.
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
\\I have tried to change the pathstring but if I use something besides a '/' I get an error.
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
CookieDomain = ".mydomain.com",
CookieName = "Mycookie"
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
I finally figured it out. The calling app needs the following code to be added to the ConfigAuth
//.... as above
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),
**OnApplyRedirect= MainLogin**
},
//..... as above
private void MainLogin(CookieApplyRedirectContext obj)
{
obj.Response.Redirect("https://[AuthAppAddress]/Account/Login?ReturnURL=" + obj.Request.Uri.AbsoluteUri);
}
the Authorizing app needs the following code to the account controller in order to distinguish between calls from its own app or other apps:
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
if (returnUrl.StartsWith("https://"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
I've been messing with this for a few days now...
What I would like to do is Authenticate users with Azure AD, and when successful, automatically log them in using ASP.NET Identity for authorization. If they do not have an account I would like to create one automatically.
Essentially Azure AD is just confirming that they are a part of the organization, the ASP.NET Identity portion is it's own database where I can use the [Authorize] attribute to set up custom roles OUTSIDE of Azure AD.
This is my ConfigureAuth() method:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(IntranetApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = SettingsHelper.ClientId,
Authority = SettingsHelper.Authority,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey);
String signInUserId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, SettingsHelper.AADGraphResourceId);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
// This ensures that the address used for sign in and sign out is picked up dynamically from the request
// this allows you to deploy your app (to Azure Web Sites, for example)without having to change settings
// Remember that the base URL of the address used here must be provisioned in Azure AD beforehand.
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
// Suppress the exception if you don't want to see the error
context.HandleResponse();
return Task.FromResult(0);
}
}
});
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
});
}
Right now the ASP.NET Identity is taking over when I do a HttpContext.Request.IsAuthenticated which is okay, I just need a way to check if the OpenID portion is authenticated or not so I can put in my custom logic to automatically sign the user in.
Got it!
My biggest problem was attempting to use the OWIN middleware to do everything for me. The OpenID middleware is not needed for simple authentication to Azure AD. I essentially created a OpenIdAuth method in the Account controller which acts as my in-between to authenticate the user with Azure before they have access to the site.
[AllowAnonymous]
public ActionResult OpenIdAuth(string code)
{
string clientId = "00000000-0000-0000-0000-000000000000"; // Client ID found in the Azure AD Application
string appKey = "111111111112222222222223333333333AAABBBCCC="; // Key generated in the Azure AD Appliction
if (code != null)
{
string commonAuthority = "https://login.windows.net/<TENANT_URL>"; // Eg. https://login.windows.net/MyDevSite.onmicrosoft.com
var authContext = new AuthenticationContext(commonAuthority);
ClientCredential credential = new ClientCredential(clientId, appKey);
AuthenticationResult authenticationResult = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Request.Url.GetLeftPart(UriPartial.Path)), credential, "https://graph.windows.net");
var userManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
var signInManager = HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
var user = UserManager.FindByName(authenticationResult.UserInfo.UniqueId);
if (user != null)
{
signInManager.SignIn(user, false, false);
}
else
{
var newUser = new ApplicationUser { UserName = authenticationResult.UserInfo.UniqueId, Email = authenticationResult.UserInfo.DisplayableId };
var creationResult = UserManager.Create(newUser);
if (creationResult.Succeeded)
{
user = UserManager.FindByName(newUser.UserName);
signInManager.SignIn(user, false, false);
}
else
{
return new ViewResult { ViewName = "Error" };
}
}
return Redirect("/");
}
else
{
var url = new Uri($"https://login.microsoftonline.com/<TENANT_URL>/oauth2/authorize?client_id={clientId}&response_type=code&redirect_uri=https://localhost/Account/OpenIdAuth");
return Redirect(url.AbsoluteUri);
}
}
The awesome part is the code variable that will be passed by Microsoft when the user logs in successfully. (As documented Here) I used the same controller method and checked if it was null but technically two different controller methods can be used (Microsoft will redirect back to the url you specify for the redirect_uri parameter).
After I got the authorization code I can use the AuthorizationContext from the Microsoft.IdentityModel.Clients.ActiveDirectory nuget package to call: AcquireTokenByAuthorizationCode. The last parameter is the Resource URI. I'm using the Graph Resource but you can use any other resource that you've given your app access to in the Azure Management Portal.
Finally my ConfigureAuth Method is back to the plain ol' ASP.NET Identity version:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(IntranetApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
});
}
I am using mvc 5 with identity 2.0. I want use custom claim values over the application but I get null values. What am I doing wrong?
Updated code
Login Code in account controller
if (!string.IsNullOrEmpty(model.UserName) && !string.IsNullOrEmpty(model.Password))
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var result = SignInManager.PasswordSignIn(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
//Generate verification token
Dictionary<string, string> acceccToken = null;
if (SignInStatus.Success == 0)
{
var userDeatails = FindUser(model.UserName, model.Password).Result;
if (userDeatails != null)
acceccToken = GetTokenDictionary(model.UserName, model.Password, userDeatails.Id);
}
if (model.RememberMe)
{
HttpCookie userid = new HttpCookie("rembemberTrue", "1");
userid.Expires.AddDays(1);
Response.Cookies.Add(userid);
}
else
{
HttpCookie userid = new HttpCookie("rembemberTrue", "0");
userid.Expires.AddDays(1);
Response.Cookies.Add(userid);
}
#region custom claims
var claims = new Claim[]
{
new Claim("urn:Custom:MasterUniqueId", Convert.ToString(Guid.NewGuid()))
};
ClaimsIdentity identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
IAuthenticationManager authenticationManager = System.Web.HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignIn(identity);
Starup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromMinutes(60)
});
another controller
Here I am trying to fetch that claim values but it shows null
var identity = (ClaimsIdentity)User.Identity;
var res= identity.FindFirst("urn:Custom:MasterUniqueId");
res is null
You should add those claims on identity validation phase. Please check similar implementation here: Server side claims caching with Owin Authentication
In your account controller, to get a valid authenticationManager, you should use Request.GetOwinContext().Authentication. Im affraid System.Web.HttpContext.Current.GetOwinContext().Authentication gets you a fresh authenticationManager instead of the current Owin context one
First you need to convert identity to claims identity and then try to get claim using identity type
(HttpContext.Current?.User?.Identity as ClaimsIdentity)?.Claims?.FirstOrDefault(x => x.Type == "urn:Custom:MasterUniqueId")?.Value
I'm trying to make OAuth work for me for the first time. Using this as a basis I've created an out-of-the-box MVC app with the Authentication set to "Individual User Accounts". I've modified my Startup.Auth.cs file to look like this...
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
var googleOptions = new GoogleOAuth2AuthenticationOptions()
{
ClientId = "blah",
ClientSecret = "blah-blah",
CallbackPath = new PathString("/Account/ExternalLoginCallback"),
Provider = new GoogleOAuth2AuthenticationProvider
{
OnAuthenticated = async ctx =>
{
string accessToken = ctx.AccessToken;
string googleName = ctx.Name;
string googleEmailAddress = ctx.Email;
var serializedUser = ctx.User;
}
}
};
googleOptions.Scope.Add("https://www.googleapis.com/auth/drive.file");
app.UseGoogleAuthentication(googleOptions);
}
}
Obviously, in my real code I have real values for ClientId and ClientSecret.
When I run the app, the start page shows.
When I click on "Log In" I see an option to log in via Google.
Clicking that shows me the Consent screen.
But this is what I don't understand. When I click on Allow my app redirects to http://localhost/WebApplication2/Account/ExternalLoginCallback?error=access_denied#
I'm clearly doing something wrong, I just can't figure out what. Can anyone offer any clues?
I have a MVC application using Identity 2 for authentication. After I log in, if I close the browser and then open the application again, there are 3 problems occurring.
The user isn't redirected to the login page
The session still contains some of the user details in the claim
The session is missing other custom information from the claim that is not part of the identity framework
I am using IIS to run the application on a Windows Server, but I can reproduce the issue on my local dev environment
Both the session in the cookie and on the server are set to expire after 1 minute while I am debugging the issue
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString(url.Action("LogIn","Auth")),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, User>(
validateInterval: TimeSpan.FromMinutes(1),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
CookieName = "MyApplication"
});
The issue was that I never set the cookie to expire, adding the following 2 lines fixed the issue I was having
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromMinutes(30)
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString(url.Action("LogIn","Auth")),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, User>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
CookieName = "MyApplication",
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromMinutes(30)
});