For beta testing, I would like to use Basic Authentication (or Digest) but I am unable to combine the Cookie Authentication provided by default in MVC with Basic Authentication.
Following Dead simple ASP.NET MVC 5 password protection? (and older posts such as link1 and link2), I set up an action filter in FilterConfig.cs:
GlobalFilters.Filters.Add(new BasicAuthenticationAttribute("myUsername", "myPassword"));
However, the result is an infinite login loop:
http://localhost:52200/account/login?ReturnUrl=%2Faccount%2Flogin%3FReturnUrl%3D%252Faccount%252Flogin%253FReturnUrl%253D%25252Faccount%25252Flogin%25253FReturnUrl%25253D%2525252Faccount%2525252Flogin%2525253FReturnUrl%2525253D%252525252Faccount%252525252Flogin%252525253FReturnUrl%252525253D%25252525252Faccount%25252525252Flogin%25252525253FReturnUrl%25252525253D%2525252525252Faccount%2525252525252Flogin%2525252525253FReturnUrl%2525252525253D%252525252525252Faccount%252525252525252Flogin%252525252525253FReturnUrl%252525252525253D%25252525252525252Faccount%25252525252525252Flogin%25252525252525253FReturnUrl%25252525252525253D%2525252525252525252F
The project has Anonymous Authentication Enabled and Windows Authentication Disabled. The BasicAuthentication filter is as follows:
using System;
using System.Web;
using System.Web.Mvc;
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
Username = username;
Password = password;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"];
if (!string.IsNullOrEmpty(auth))
{
var cred = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password)
{
return;
}
else
{
throw new HttpException(403, "Forbidden"); // For Elmah
}
}
var res = filterContext.HttpContext.Response; // The 4 lines below cause the login redirect
res.StatusCode = 401;
res.AddHeader("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", BasicRealm ?? "MyProject"));
res.End();
}
}
My understanding is that upon triggering the http 401, UseCookieAuthentication in Startup.Auth.cs redirects to login, which in turns calls BasicAuthentication, thus starting the loop until the browser throws an error. This is the default UseCookieAuthentication:
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))
}
});
Many posts indicate that web.config needs to modified (Owin, formsAuthentication, etc.) I won't cite them here because there seems to be no commonly accepted answer.
Would a temporary password-protected index be wiser / preferable for beta testing?
Per OWIN Authentication with IIS Basic Authentication, the following line in Startup.Auth.cs must be deleted to avoid starting an infinite login loop:
LoginPath = new PathString("/Account/Login"),
It is the only way for Cookie Authentication and Basic Authentication to work simultaneously in MVC - no further setup required in IIS. Once a user has been authenticated via Basic Authentication, users who have logged in remain successfully logged in across the site.
Related
I have read and followed this article to setup my site using our AAD (Azure Active Directory) to get SSO (Single Sign On.) I have gotten it to work in a brand new website both with localhost as well as when I publish it to Azure.
Here are the settings for the working version's App Registration:
Branding:
Home page URL: https://<worksgood>.azurewebsites.net
Authentication:
Redirect URIs:
https://localhost:44390/
https://<worksgood>.azurewebsites.net/.auth/login/aad/callback
Implicit grant:
ID Tokens: Checked
Supported account types
Accounts in this organizational directory only (My Company - Single tenant)
Treat application as a public client
No
And when I run the application here is the callback request.
As you can see the Response Header | Location looks good (to me)
Here are the App Registration settings for the site I am attempting to integrate this same logic into:
Branding:
Home page URL: https://<notsogood>.azurewebsites.net
Authentication:
Redirect URIs:
https://localhost:54449/
https://<notsogood>.azurewebsites.net/.auth/login/aad/callback
Implicit grant:
ID Tokens: Checked
Supported account types
Accounts in this organizational directory only (My Company - Single tenant)
Treat application as a public client
No
And when I run the application here is the callback request.
When I run it, I do get the AD login screen where I enter my AD user and creds. However, it does not successfully log me in.
As you can see, the Location in the response gets altered. I do know that this non-working version has the authentication and authorization sections within the web.config and if I change the loginUrl attribute from /login to /loginklg it will change the location to /loginklg?ReturnUrl=%2f.auth%2flogin%2faad%2fcallback but if I remove that section the site will not work.
You should also notice that there is a loop where it attempts to log me in and then for some reason can not and then tries again.
Initially, the not working site had the following startup code for authentication:
public void ConfigureAuth(IAppBuilder app) {
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/login"),
Provider = new CookieAuthenticationProvider {
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(15),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)
)
}
});
}
I have kept this in as well as taken it out and it makes no difference in my result.
The only real difference is that the working version is MVC and the SignIn method is called.
public void SignIn() {
if (!Request.IsAuthenticated) {
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
And with the not working version it is a WebForm/Page and the Page_Load method is called:
Please Note
This application was not created by me nor my company, so I am trying to integrate it with simply some separate classes and config settings with the least code change as possible. The _avantiaSSOEnabled just reads an appSettings in the web.config which I added. The _openIdEnabled already existed.
_openIdEnabled = false
_avantiaSSOEnabled = true
Even if I enable _openIdEnabled the Location is still bad.
protected void Page_Load(object sender, EventArgs e) {
if (_avantiaSSOEnabled) {
if (!Request.IsAuthenticated) {
Request.GetOwinContext().Authentication.Challenge(
new Microsoft.Owin.Security.AuthenticationProperties { RedirectUri = "/klg" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
if (_openIdEnabled)
openIdBackgroundSignIn.OnOpenIdSSOLoggedIn += OnOpenIdSSOLoggedIn;
if (!IsPostBack) {
if (SystemHub.Maintenance.IsActive)
HandleInfoPopup(MaintenenceException.Text, true);
else if (Request["error"] != null)
HandleError(Request["error"].ToString());
else if (Request["auto"] == "true")
HandleInfoPopup(AutoLogout.Text, true);
else if (_openIdEnabled) {
openIdBackgroundSignIn.ClearData();
if (Request["oidc_error"] != null) //This is usually when auto-login fails, so we pass it to client side which will handle it
openIdBackgroundSignIn.AddData(OpenIdBackgroundSignIn.OPENID_KEY_ERROR, Request["oidc_error"].ToString());
else if (Request["oidc_login"] == "true")
openIdBackgroundSignIn.AddData(OpenIdBackgroundSignIn.OPENID_KEY_LOGIN_SUCCESS, true);
else if (User.Identity.IsAuthenticated)
Response.RedirectToUrl(Request.QueryString["ReturnUrl"]);
else if (Request["lo"] == null) //lo is set when coming from logout, so don't try to autologin
openIdBackgroundSignIn.AddData(OpenIdBackgroundSignIn.OPENID_KEY_ATTEMPT_LOGIN_AUTO, true);
}
else if (User.Identity.IsAuthenticated) {
Response.RedirectToUrl(Request.QueryString["ReturnUrl"]);
}
}
}
The only code change I made (that wasn't in the above linked article) was in a attempt to fix it, reading many other articles and there is a known issue with default cookie manager. Here is the result:
app.UseCookieAuthentication(new CookieAuthenticationOptions {
CookieManager = new SystemWebChunkingCookieManager() // Originally SystemWebCookieManager
});
I know I am close. Clearly something is intercepting the request and tweaking it. I am just not sure where to look. I have been coding in C# since the start, but I am not that used to the security/SSO side of it, so any help is appreciated. If you need me to add more information, I can, just let me know what.
UPDATE - 07/31/2020
I was able to fix the Location /login?ReturnUrl... after following this article as you can see below.
As you can see in the image below, from the AAD Sign-in Log, I am successfully logging in. So it seems as if the code is unable to remember or store the token once logged in and then just tries again and must have some threshold of tries or time before it fails.
Oddly enough, when it stops looping I get the following message which has the email of the account I am attempting to log in as and says "Signed in"
The looping issues was fixed by removing the following line:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
The looping was happening because the authentication type (set in the above line) would return Cookies. However, the response from AAD was setting the type as ApplicationCookie.
The full code in the ConfigAuth is now:
public void ConfigAuth(IAppBuilder app) {
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
CookieManager = new SystemWebChunkingCookieManager(),
Provider = new CookieAuthenticationProvider {
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: AuthenticationHelper.OpenIdEnabled
? TimeSpan.FromSeconds(30)
: TimeSpan.FromMinutes(15),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
ClientId = AvantiaSSOHelper.ClientId,
Authority = AvantiaSSOHelper.Authority,
PostLogoutRedirectUri = AvantiaSSOHelper.PostLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications {
AuthenticationFailed = (context) => {
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) => {
Debug.WriteLine($"Authorization code received: {context.Code}");
return Task.FromResult(0);
},
MessageReceived = (context) => {
Debug.WriteLine($"Message received: {context.Response.StatusCode}");
return Task.FromResult(0);
},
SecurityTokenReceived = (context) => {
Debug.WriteLine($"Security token received: {context.ProtocolMessage.IdToken}");
string test = context.ProtocolMessage.AccessToken;
return Task.FromResult(0);
},
SecurityTokenValidated = (context) => {
Debug.WriteLine($"Security token validated: {context.Response.StatusCode}");
var nameClaim = context.AuthenticationTicket.Identity.Claims
.Where(x => x.Type == AvantiaSSOHelper.ClaimTypeWithEmail)
.FirstOrDefault();
if (nameClaim != null)
context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypes.Name, nameClaim.Value));
return Task.FromResult(0);
},
TokenResponseReceived = (context) => {
string test = context.ProtocolMessage.AccessToken;
return Task.FromResult(0);
}
}
}
);
// This makes any middleware defined above this line run before the Authorization rule is applied in web.config
app.UseStageMarker(PipelineStage.Authenticate);
}
That made a single (non-looping) call and then the system attempted to continue in an Authenticated mode. However, there was still one more step I needed to do. This last step was to alter the SecurityTokenValidated event by adding the appropriate response claim into the authentication ticket. Our system is using Micrososft Identity and is thus based on an email address. So I need to add a Claim of type ClaimTypes.Name to the authentication ticket from the extracted email claims value as follows:
context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypes.Name, nameClaim.Value));
The AvantiaSSOHelper.ClaimTypeWithEmail is simply a value I am reading out of the Web.config file in case other implementations have a different claim I would need to extsract.
I have a requirement where I want to fetch the Authorization Code for Azure login for each request in web API. As of now once the user signs in to Azure, after that I am not getting the authorization code as the user is already signed in.
How can I force the user to sign in again? This is the code I have been using as of now in the owin_startup file in web API?
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieSecure = (CookieSecureOption)Convert.ToInt32(cookieSecure), // CookieSecureOption.NeverAlways
CookieManager = new SystemWebCookieManager(),
CookieHttpOnly = false,
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
RedirectUri = RedirectUri,
});
According to the code and the cases you post before, I think it it not about Azure ad b2c, so here I will give a reply for azure ad.
When you request an authorization code, there is a prompt=login property which is indicate the user should be prompted to reauthenticate.
Also here is an article about Forcing reauthentication with Azure AD which suggest use Token Max Age to achieve it.
You can append max_age= to the authorization URL (or just put 0 to force password authentication at all times). So once user gets redirected to the URL, they will be presented with an information to login again.
public class RequireReauthenticationAttribute : Attribute, IAsyncResourceFilter
{
private int _timeElapsedSinceLast;
public RequireReauthenticationAttribute(int timeElapsedSinceLast)
{
_timeElapsedSinceLast = timeElapsedSinceLast;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var foundAuthTime = int.TryParse(context.HttpContext.User.FindFirst(AppClaimTypes.AuthTime)?.Value, out int authTime);
if (foundAuthTime && DateTime.UtcNow.ToUnixTimestamp() - authTime < _timeElapsedSinceLast)
{
await next();
}
else
{
var state = new Dictionary<string, string> { { "reauthenticate", "true" } };
await context.HttpContext.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties(state)
{
RedirectUri = context.HttpContext.Request.Path
}, ChallengeBehavior.Unauthorized);
}
}
}
It appears that the reason you wish to re-authenticate the user is to get/refresh the token in memory even when the auth cookie is present. Simplest way to achieve this would be decorate your controller with the AuthorizeForScopes attribute. You can check the sample project on Github here
I am extremely confused about how the actual authentication works so that [Authorize] does not redirect me to the login page.
Here's my Configuration:
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(() => new MyANTon.DataContext.AntContext());
app.CreatePerOwinContext<UserManager>(UserManager.Create);
app.CreatePerOwinContext<RoleManager<AppRole>>((options, context) =>
new RoleManager<AppRole>(
new RoleStore<AppRole>(context.Get<MyANTon.DataContext.AntContext>())));
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Login"),
});
}
}
In the controller, I want to call an Authenticate(string Email, String Password) method, authenticate the user and return a bool. However, I have no idea how the actual authentication works.
In FormsAuthentication I would create a ticket, what do I do for Identity?
Here's what I have:
public static async System.Threading.Tasks.Task<bool> AuthUserAsync(string Email, string Password)
{
using (var db = new AntContext())
{
string hashedPW = GetHash(Password);
bool userValid = db.Users.Any(user => user.Email == Email && user.Password == hashedPW);
if (userValid)
{
var actUser = db.Users.FirstOrDefault(u => u.Email == Email && u.Password == hashedPW);
if (actUser != null && !actUser.IsLocked)
{
/** What do I do here? **/
}
else if (actUser.IsLocked)
{
LoggingServices.AuthLog(actUser.Email, "Hat versucht auf ein gesperrtes Konto zuzugreifen.");
}
}
return false;
}
}
You are heading in the right direction, what you are doing is sort of using OAuth to faciliate the mapping of your tokens and letting OWin handle the browser information. So, by using the [Authorize] attribute, like you are doing, how are you handling the signing of the identity? Like mentioned above with Forms authentication, you still have to create the Identity/Claim token. In my projects, I do something like this,
protected void IdentitySignin(IUserModel userModel, string providerKey = null, bool isPersistent = true)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userModel.Id.ToString()),
new Claim(ClaimTypes.Name, userModel.UserName),
new Claim("UserContext", userModel.ToString())
};
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties
{
IsPersistent = isPersistent,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
}, identity);
}
This forces OWIN/OAuth to login the user, and in my web.config I have the following:
<system.webserver>
<authentication mode="None" />
</system.webserver>
And to sign my user out, and force the browser to get a new token:
protected void IdentitySignout()
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie,
DefaultAuthenticationTypes.ExternalCookie);
}
My AuthenticationManager is defined as such:
private IAuthenticationManager AuthenticationManager
{
get { return HttpContext.GetOwinContext().Authentication; }
}
Which is part of Microsoft.OWin.IOwinContext, you will have to add the reference if it does not exist.
You can handle an unauthorized user via the web.config file, or a base-controller, I opted for the base-controller option like so:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (UserContext == null || UserContext.Id.Equals(Guid.Empty))
{
if (filterContext.Controller.GetType() == typeof(AccountController) || filterContext.Controller.GetType() == typeof(HomeController)) return;
filterContext.Result = new RedirectResult("/Home/Index");
return;
}
}
But you can also do it via the AuthorizeAttribute if you wanted. This link will go into great detail on handling Oauth and Asp.Net MVC, may seem daunting at first, but it gives a great layout on using other providers if you decided to incorporate them into a release version.
https://www.codeproject.com/Articles/577384/Introduction-to-OAuth-in-ASP-NET-MVC
When you want Login in to your web-site you send your token to client and you can work with that to request and response in many, in other word, you have to login with your server side.
But when you want to logout from web-site, your client have to know that you want to logout this is not work just for server, the client should do this issue for log out.
I want to suggest you Token-JWT
if you want know about JWT click here
If you want I'll create an Example for you.
I have implemented asp.net mvc with asp.net identity authentication.
I have used cookie based authentication. After restart the IIS/stop and start the IIS for the my site, when i open my website, the user is automatically login to the system.
The user cookie is not cleared and still valid for the user. How to force the user to log out after restart the iis?
I have used default sample from the website.
http://www.nuget.org/packages/Microsoft.AspNet.Identity.Samples
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))
}
});
Cookies are not meant to be invalidated when IIS is restarted - this is not how HTTP protocol works. Cookies being invalidated on IIS restart can lead to some strange behaviour in production - IIS can be plunged any time, or there could be load-balancer that uses a few IIS servers to serve the requests - what would happen if one of the servers restarts?
Anyway, you can kill all the cookies for all the users by mass-updating ApplicationUser.SecurityStamp in the database. And in Startup.Auth.cs set validateInterval: TimeSpan.FromMinutes(2) - this will invalidate all the cookies within 2 minutes of the SecurityStamp update. Value lower than this is not recommended - this will cause performance issues.
For this, I have done a trick.
We are using session to store the dynamic variables and asp.net identity for authentication in ASP.NET MVC.
Each request I have interrupted.
I have checked like whether asp.net identity is valid and session is invalid.
If session is invalid, then make the cookies invalid and navigate the user to specific page.
public class SessionHandler : ActionFilterAttribute
{
private ApplicationUserManager _userManager;
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.Current.GetOwinContext().Authentication;
}
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
public IIdentity UserIdentity
{
get { return System.Web.HttpContext.Current.User.Identity; }
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!string.IsNullOrWhiteSpace(UserIdentity.GetUserId()))
{
if (System.Web.HttpContext.Current.Session["Username"] == null)
{
AuthenticationManager.SignOut();
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "Index" },
{ "controller", "Home" }
});
}
}
}
}
In Global.asax file
Add the following code
GlobalFilters.Filters.Add(new SessionHandler());
Following blog series by #Taiseer Joudeh (http://bitoftech.net/2015/01/21/asp-net-identity-2-with-asp-net-web-api-2-accounts-management/) I've build simple WebApi with JWT authentication. I'm able to login and query my endpoint using token.
I'm trying to change how my login mechanism works.
Right now user must send his login and password to /oauth/token to get access token that will be send with every request to webapi.
I'd like to be able to login user with password that will be send via SMS to phone number assigned to his account.
It should require two requests to token endpoint path. First one with only user id (login or some unique identifier), then endpoint should check if user exists, if yes then it should send password via SMS.
Then user would input that password in my form and second request would be done to token endpoint path with both user id and password.
I've read about two-factor authentication (http://bitoftech.net/2014/10/15/two-factor-authentication-asp-net-web-api-angularjs-google-authenticator/ and other sources), but it requires user to login using username and password and then to submit second password from SMS.
In my case I want user to pass only username and SMS password.
EDIT:
Below is code I've tried to build, it is starting point, but I don't know how to finish implementing it. I've overriden GrantCustomExtension in OAuthAuthorizationServerProvider
public override async Task GrantCustomExtension(OAuthGrantCustomExtensionContext context)
{
const string allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
//custom grant_type
if (context.GrantType != "sms")
{
context.SetError("invalid_grant", "unsupported grant_type");
return;
}
var userName = context.Parameters.Get("username");
if (userName == null)
{
context.SetError("invalid_grant", "username is required");
return;
}
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindByNameAsync(userName);
if (user == null)
{
context.SetError("invalid_grant", "user not found");
return;
}
//generate one-time password
//store it in database
//send it to user phone number
//return ok message
context.Validated();
//return base.GrantCustomExtension(context);
}
And here is part of Startup.cs that is responsible for setting up oAuthAuthorizationServer:
public void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
var issuer = ConfigurationManager.AppSettings["Issuer"];
OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
#if DEBUG
AllowInsecureHttp = true,
#endif
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(issuer)
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(oAuthServerOptions);
}
I'm able to send custom grant_type and find user in database, but the hardest part is still missing. I know I can store that one-time password in User table, but maybe ASP.Net Identity 2 has build in mechanism, I dont want to reinvent the wheel.
You can do it by changing the user password every time.
dont know if is accepted in your situation
In case you are interested in this solution, here is how you can do it:
User Manager
ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
// get your user here
var currentUser = UserManager.Users...
if (currentUser != null)
{
var resultRemove = UserManager.RemovePassword(currentUser.Id);
var resultAdd = UserManager.AddPassword(currentUser.Id, model.NewPassword);
if (resultAdd.Succeeded && resultRemove.Succeeded)
//Send SMS here
}