ASP.NET CORE WEB API authentication via Social Network - asp.net-core

I have to build WEB api application that can authenticate user via Socail network like Facebook.
I understand how it's happening in asp.net core mvc, but I have no idea how it's happening in Web API. I find resource that build LinkedIn authentication in Startup.cs with code like this
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
LoginPath = new PathString("/login"),
LogoutPath = new PathString("/logout")
});
app.UseOAuthAuthentication(new OAuthOptions
{
// We need to specify an Authentication Scheme
AuthenticationScheme = "LinkedIn",
// Configure the LinkedIn Client ID and Client Secret
ClientId = Configuration["linkedin:clientId"],
ClientSecret = Configuration["linkedin:clientSecret"],
// Set the callback path, so LinkedIn will call back to http://APP_URL/signin-linkedin
// Also ensure that you have added the URL as an Authorized Redirect URL in your LinkedIn application
CallbackPath = new PathString("/signin-linkedin"),
// Configure the LinkedIn endpoints
AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization",
TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken",
UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address,picture-url)",
Scope = { "r_basicprofile", "r_emailaddress" },
Events = new OAuthEvents
{
// The OnCreatingTicket event is called after the user has been authenticated and the OAuth middleware has
// created an auth ticket. We need to manually call the UserInformationEndpoint to retrieve the user's information,
// parse the resulting JSON to extract the relevant information, and add the correct claims.
OnCreatingTicket = async context =>
{
// Retrieve user info
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
request.Headers.Add("x-li-format", "json"); // Tell LinkedIn we want the result in JSON, otherwise it will return XML
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
// Extract the user info object
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
// Add the Name Identifier claim
var userId = user.Value<string>("id");
if (!string.IsNullOrEmpty(userId))
{
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
// Add the Name claim
var formattedName = user.Value<string>("formattedName");
if (!string.IsNullOrEmpty(formattedName))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Name, formattedName, ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
// Add the email address claim
var email = user.Value<string>("emailAddress");
if (!string.IsNullOrEmpty(email))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String,
context.Options.ClaimsIssuer));
}
// Add the Profile Picture claim
var pictureUrl = user.Value<string>("pictureUrl");
if (!string.IsNullOrEmpty(email))
{
context.Identity.AddClaim(new Claim("profile-picture", pictureUrl, ClaimValueTypes.String,
context.Options.ClaimsIssuer));
}
}
}
});
app.Map("/logout", builder =>
{
builder.Run(async context =>
{
// Sign the user out of the authentication middleware (i.e. it will clear the Auth cookie)
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
// Redirect the user to the home page after signing out
context.Response.Redirect("/api/Account/LogOut");
});
});
app.Map("/login", builder =>
{
builder.Run(async context =>
{
// Return a challenge to invoke the LinkedIn authentication scheme
await context.Authentication.ChallengeAsync("LinkedIn", properties: new AuthenticationProperties() { RedirectUri = "/api/Account/Create" });
});
});
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
app.UseApplicationInsightsExceptionTelemetry();
//app.UseJwtBearerAuthentication();
app.UseMvc();
}
As you can see in the end of the file there are two map methods. They are managing login and logout. So can you recomend me how to build my app in right way, or how can create two controller methods, that can do the same things.
Thank you, and sorry for my English

Related

.NET Core Authentication is available only at the next request

I'm on .NET CORE5
In Startup ConfigureServices
// Authentication (use cookies)
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(40);
options.SlidingExpiration = true;
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});
// Need to register
services.AddScoped<CustomCookieAuthenticationEvents>();
In Startup Configure
app.UseAuthentication();
app.UseAuthorization();
My signin code routine
// Create user claims
List<Claim> claims = new List<Claim>() {
new Claim(ClaimTypes.NameIdentifier, this.UserId?.ToString() ?? string.Empty),
new Claim(ClaimTypes.Name, this.UserName ?? string.Empty),
};
// SignIn
ClaimsIdentity identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await this.context.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(identity),
new AuthenticationProperties()
{
IsPersistent = this.RememberMe,
ExpiresUtc = DateTimeOffset.Now.AddMinutes(this.ExpireMinutes),
});
// HERE the context.user.Identity is still not authenticated
// and no claims are registered
My custom events manager
public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
public override Task SignedIn(CookieSignedInContext context)
{
// HERE within the same SignInAsync request the context.user.Identity is still not authenticated
// and no claims are registered.
// BUT at the next request the identity will be registered
// also with the proper claims list.
return base.SignedIn(context);
}
}
As wrote in the comment it seems the identity registration will be available only at the next request.
I need to have the Identity registered just after the signin calling and not at the next request.

IdentityServer4 authenticating against an external api

We have a requirement to authenticate users in IdentityServer4 against an external API. The scenario works like this:
User visits a Javascript client application and clicks the login button to redirect to IdentityServer login page (exact same client as provided in the docs here
User enters their username (email) and password
IdentityServer4 connects to an external API to verify credentials
User is redirected back to the JavaScript application
The above process works perfect when using the TestUsers provided in the QuickStarts. However, when an API is used, the login page resets and does not redirect the user back to the JavaScript client. The only change is the below code and a custom implementation of IProfileService.
Below is the custom code in the login action (showing only the relevant part):
var apiClient = _httpClientFactory.CreateClient("API");
var request = new HttpRequestMessage(HttpMethod.Post, "/api/auth");
var loginModel = new LoginModel
{
Email = model.Email,
Password = model.Password
};
var content = new StringContent(JsonConvert.SerializeObject(loginModel),
Encoding.UTF8, "application/json");
request.Content = content;
HttpResponseMessage result = await apiClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
var loginStatus = JsonConvert.DeserializeObject<ApiLoginStatus>(
await result.Content.ReadAsStringAsync());
if (loginStatus.LoginSuccess)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(model.Email, model.Email, loginStatus.Name, clientId: context?.ClientId));
AuthenticationProperties props = null;
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
var user = new IdentityServerUser(loginStatus.SubjectId)
{
DisplayName = loginStatus.Name
};
await HttpContext.SignInAsync(user, props);
if (context != null)
{
if (await _clientStore.IsPkceClientAsync(context.ClientId))
{
return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
}
return Redirect(model.ReturnUrl);
}
The code actually hits the return View() path, but for some reason it resets and the login page is shown again.
Code in Startup.cs:
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddProfileService<ProfileService>()
.AddDeveloperSigningCredential();
Code in ProfileService.cs:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var profile = await GetUserProfile(context.Subject.GetSubjectId());
var claims = new List<Claim>
{
new Claim(ClaimTypes.Email, profile.Email),
new Claim(ClaimTypes.Name, profile.Name)
};
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
var profile = await GetUserProfile(context.Subject.GetSubjectId());
context.IsActive = (profile != null);
}
There are multiple sources online that show how to user a custom store for authentication, but they all seem to use ResourceOwnerPasswordValidator. If someone could point out what is missing here, it would help greatly. Thanks.
So the issue turned out to be very simple. We had missed removing the builder.AddTestUsers(TestUsers.Users) line when setting up IdentityServer in Startup.cs.
Looking at the code here, it turned out that this line was overriding our profile service with the test users profile service. Removing that line solved the problem.

how to redirect from /signin-oidc back to my controller/action?

the callback url is https://localhost:44338/signin-oidc
lets say i am in controller/action , decorated with [Authorize]
how do i redirect from https://localhost:44338/signin-oidc back to my controller/action ?
Note : I am following the wiki :
Quickstart: Add sign-in with Microsoft to an ASP.NET Core web app
You can store the url on server side . For example ,base on code sample :
Quickstart: Add sign-in with Microsoft to an ASP.NET Core web app
modify your OIDC configurations like :
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = async n =>
{
//save url to state
n.ProtocolMessage.State = n.HttpContext.Request.Path.Value.ToString();
},
OnTokenValidated = ctx =>
{
var url = ctx.ProtocolMessage.GetParameter("state");
var claims = new List<Claim>
{
new Claim("myurl", url)
};
var appIdentity = new ClaimsIdentity(claims);
//add url to claims
ctx.Principal.AddIdentity(appIdentity);
return Task.CompletedTask;
},
OnTicketReceived = ctx =>
{
var url = ctx.Principal.FindFirst("myurl").Value;
ctx.ReturnUri = url;
return Task.CompletedTask;
}
};
// Per the code below, this application signs in users in any Work and School
// accounts and any Microsoft Personal Accounts.
// If you want to direct Azure AD to restrict the users that can sign-in, change
// the tenant value of the appsettings.json file in the following way:
// - only Work and School accounts => 'organizations'
// - only Microsoft Personal accounts => 'consumers'
// - Work and School and Personal accounts => 'common'
// If you want to restrict the users that can sign-in to only one tenant
// set the tenant value in the appsettings.json file to the tenant ID of this
// organization, and set ValidateIssuer below to true.
// If you want to restrict the users that can sign-in to several organizations
// Set the tenant value in the appsettings.json file to 'organizations', set
// ValidateIssuer, above to 'true', and add the issuers you want to accept to the
// options.TokenValidationParameters.ValidIssuers collection
options.TokenValidationParameters.ValidateIssuer = false;
});

Where to store JWT Token in .net core web api?

I am using web api for accessing data and I want to authenticate and authorize web api.For that I am using JWT token authentication. But I have no idea where should I store access tokens?
What I want to do?
1)After login store the token
2)if user want to access any method of web api, check the token is valid for this user,if valid then give access.
I know two ways
1)using cookies
2)sql server database
which one is the better way to store tokens from above?
Alternatively, if you just wanted to authenticate using JWT the implementation would be slightly different
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var user = context.Principal.Identity.Name;
//Grab the http context user and validate the things you need to
//if you are not satisfied with the validation fail the request using the below commented code
//context.Fail("Unauthorized");
//otherwise succeed the request
return Task.CompletedTask;
}
};
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("MyVeryStrongKeyHiddenFromAnyone"),
ValidateIssuer = false,
ValidateAudience = false
};
});
still applying use authentication before use MVC.
[Please note these are very simplified examples and you may need to tighten your security more and implement best practices such as using strong keys, loading configs perhaps from the environment etc]
Then the actual authentication action, say perhaps in AuthenticationController would be something like
[Route("api/[controller]")]
[Authorize]
public class AuthenticationController : Controller
{
[HttpPost("authenticate")]
[AllowAnonymous]
public async Task<IActionResult> AuthenticateAsync([FromBody]LoginRequest loginRequest)
{
//LoginRequest may have any number of fields expected .i.e. username and password
//validate user credentials and if they fail return
//return Unauthorized();
var claimsIdentity = new ClaimsIdentity(new Claim[]
{
//add relevant user claims if any
}, "Cookies");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
return Ok();
}
}
in this instance I'm using cookies so I'm returning an HTTP result with Set Cookie. If I was using JWT, I'd return something like
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]LoginRequest loginRequest)
{
//validate user credentials and if they validation failed return a similar response to below
//return NotFound();
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("MySecurelyInjectedAsymKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
//add my users claims etc
}),
Expires = DateTime.UtcNow.AddDays(1),//configure your token lifespan and needed
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey("MyVerySecureSecreteKey"), SecurityAlgorithms.HmacSha256Signature),
Issuer = "YourOrganizationOrUniqueKey",
IssuedAt = DateTime.UtcNow
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(4);//you can set this to a suitable timeframe for your situation
cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", tokenString, cookieOptions);
return Ok();
}
I'm not familiar with storing your users tokens on your back end app, I'll quickly check how does that work however if you are using dotnet core to authenticate with either cookies or with jwt, from my understanding and experience you need not store anything on your side.
If you are using cookies then you just need to to configure middleware to validate the validity of a cookie if it comes present in the users / consumer's headers and if not available or has expired or can't resolve it, you simply reject the request and the user won't even hit any of your protected Controllers and actions. Here's a very simplified approach with cookies.(I'm still in Development with it and haven't tested in production but it works perfectly fine locally for now using JS client and Postman)
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "yourCookieName";
options.Cookie.SameSite = SameSiteMode.None;//its recommended but you can set it to any of the other 3 depending on your reqirements
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
{
OnRedirectToLogin = redirectContext =>//this will be called if an unauthorized connection comes and you can do something similar to this or more
{
redirectContext.HttpContext.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnValidatePrincipal = context => //if a call comes with a valid cookie, you can use this to do validations. in there you have access to the request and http context so you should have enough to work with
{
var userPrincipal = context.Principal;//I'm not doing anything with this right now but I could for instance validate if the user has the right privileges like claims etc
return Task.CompletedTask;
}
};
});
Obviously this would be placed or called in the ConfigureServices method of your startup to register authentication
and then in your Configure method of your Startup, you'd hookup Authentication like
app.UseAuthentication();
before
app.UseMvc()

How to use authentication from my ASP.NET Core site to authenticate angular 2 web app?

I have ASP.NET Core app with angular 2 front-end. I use cookie auth.
But I want to split my app into 2 separate sites - one front-end site on angular2 and one back-end site on asp.net core.
How do I use auth from ASP.NET Core site to authenticate front-end app?
There's a login page in my back-end site. How do I identify in front-end app that I'm not authenticated, then redirect to back-end app and then get auth cookies? I'm not sure I understand mechanic of this process.
I used token based authentication. I choosed this solution: https://stormpath.com/blog/token-authentication-asp-net-core & https://github.com/nbarbettini/SimpleTokenProvider
For Authentication I prefer to use cookies.
Use cookie authentication without Identity
Login Code
[HttpPost("login")]
[AllowAnonymous]
public async Task<HttpBaseResult> Login([FromBody]LoginDto dto)
{
var user = db.Users.Include(u=>u.UserRoles).SingleOrDefault();
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName)
};
var roles = user.UserRoles.Select(u => u.Role);
foreach (var item in roles)
{
claims.Add(new Claim(ClaimTypes.Role, item.Name));
}
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = dto.RememberMe });
// ...
}
Cross Domain
ConfigureServices
{
options.SlidingExpiration = true;
options.Cookie.HttpOnly = false;
// Dynamically set the domain name of the prod env and dev env
options.Cookie.Domain = Configuration["CookieDomain"];
});
Configure
app.UseCors(builder => builder.WithOrigins("http://localhost:4200", "http://www.example.com","http://example.com")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
Angular Code
public login(userName: string, password: string, rememberMe: boolean): Observable<HttpBaseResult> {
const url: string = `${this.url}/login`;
var data = {
UserName: userName,
Password: password,
RememberMe: rememberMe
};
return this.client.post<HttpBaseResult>(url, data, { withCredentials: true });
}