How do I use Windows Authentication in WEB API for internal users who will also be on the public network? The REST API will be public facing and will need to authenticate intranet users as well as internet users. Basically, anybody not on Active Directory won't be able to access it and one more AD groups will be authorized.
The REST service at the moment has a security filter to validate token using attribute filter.
public class RestAuthorizeAttribute : AuthorizeAttribute
{
private const string SecurityToken = "token";
public override void OnAuthorization(HttpActionContext actionContext)
{
if (Authorize(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
private bool Authorize(HttpActionContext actionContext)
{
try
{
HttpRequestMessage request = actionContext.Request;
//Extract Token from the Request. This will work for all.
// E.g \api\Facilitiles\Token\298374u23lknndsjlkfds==
// \api\Ward\123\Token\298374u23lknndsjlkfds==
string path = request.RequestUri.LocalPath;
int indexOfToken = path.IndexOf(SecurityToken) + SecurityToken.Length + 1;
string token = path.Substring(indexOfToken);
bool isValid = SecurityManager.IsTokenValid(token, IpResolver.GetIp(request),request.Headers.UserAgent.ToString());
return isValid;
}
catch (Exception ex)
{
string av = ex.Message;
return false;
}
}
}
This is then applied to specific controllers like this:
[RestAuthorize]
[RoutePrefix("api/patient")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class PatientDetailsController : ApiController
{
PatientDetailsRetriever _patientDetailsRetriever;
// GET: api/patient/meds/personId/{personId}/token/{token}
[Route("meds/personId/{personId}/token/{token}")]
[HttpGet]
public HttpResponseMessage GetMeds(Int64 personId, string token)
{
List<Medication> meds;
.....
The client generates the token which includes username, password and domain and among other things.
Enabling Windows Authentication in IIS (web.config) will be enough to validate local users. But how does this work when the user is outside the network and sends in the credentials?
I have found the answer on this SO post.
//create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}
Related
I defined this GatewayFilter:
EDIT More context information:
What I would like to achieve is to avoid the client providing its credentials to get an access token from an authorization server.
The client sends a POST request with user's credentials (username/password) and the gateway adds all complementary information like scope, client_id, grant_type etc... before forwarding the request to the authorization server.
#Component
public class OAuth2CredentialsAppenderGatewayFilterFactory extends AbstractGatewayFilterFactory<OAuth2CredentialsAppenderGatewayFilterFactory.Config> {
public OAuth2CredentialsAppenderGatewayFilterFactory() {
super(Config.class);
}
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();
if ("x-www-form-urlencoded".equals(request.getHeaders().getContentType().getSubtype())) {
//This code is not executed, the call of formData.put does not do anything, even a breakpoint is not reached!
if (request.getMethod().equals(HttpMethod.POST)) {
exchange.getFormData().map(formData -> {
formData.put("key1", List.of("value1"));
formData.put("key2", List.of("value2"));
formData.put("key3", List.of("value3"));
return formData;
});
}
//This part of code works well, the header is added to the forwarded request
requestBuilder.header(HttpHeaders.AUTHORIZATION,
"Basic " + Base64Utils.encodeToString((this.uiClientId + ":" + this.uiClientSecret).getBytes()));
}
return chain.filter(exchange.mutate().request(requestBuilder.build()).build());
};
}
}
I use the filter like this:
- id: keycloak_token_route
uri: http://localhost:8180
predicates:
- Path=/kc/token
filters:
- OAuth2CredentialsAppender
- SetPath=/auth/realms/main/protocol/openid-connect/token
- name: RequestRateLimiter
args:
key-resolver: "#{#userIpKeyResolver}"
redis-rate-limiter.replenishRate: 20
redis-rate-limiter.burstCapacity: 30
denyEmptyKey: false
The filter is well invoked but altering the incoming request body does not work.
I am new to the reactive world so I am a bit confused, any help will be appreciated.
For those who would like to do the same thing, this is how I solved my problem. Again I am not an expert of Reactive programming, I am still learning it so it might be a better answer.
#Component
public class OAuth2CredentialsAppenderGatewayFilterFactory extends AbstractGatewayFilterFactory<OAuth2CredentialsAppenderGatewayFilterFactory.Config> {
#Value("${uiservice.clientId}")
private String uiClientId;
#Value("${uiservice.clientSecret}")
private String uiClientSecret;
public OAuth2CredentialsAppenderGatewayFilterFactory() {
super(Config.class);
}
#Override
public GatewayFilter apply(Config config) {
return (ServerWebExchange exchange, GatewayFilterChain chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();
if (nonNull(request.getHeaders().getContentType()) && request.getHeaders().getContentType().equals(MediaType.APPLICATION_FORM_URLENCODED)) {
if (requireNonNull(request.getMethod()).equals(HttpMethod.POST)) {
//Use this filter to modify the request body
ModifyRequestBodyGatewayFilterFactory.Config requestConf = new ModifyRequestBodyGatewayFilterFactory.Config()
.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.setRewriteFunction(String.class, String.class, this.completeRequestBody());
requestBuilder.header(HttpHeaders.AUTHORIZATION, base64Encoding(this.uiClientId, this.uiClientSecret));
return new ModifyRequestBodyGatewayFilterFactory().apply(requestConf).filter(exchange.mutate().request(requestBuilder.build()).build(), chain);
}
}
return chain.filter(exchange.mutate().request(requestBuilder.build()).build());
};
}
/** Add some config params if needed */
public static class Config {
}
/** Complete request by adding required information to get the access token. Here we can get 2 type of token: client_credentials or password. If the param client_only=true we should get a client_credentials token */
private RewriteFunction<String, String> completeRequestBody() {
return (ServerWebExchange ex, String requestBody) -> {
requireNonNull(requestBody, "Body is required");
//if body contains only this, we should get a client_credentials token
var idForClientCredentialsOnly = "client=ui&client_only=true";
String finalRequestBody;
var joiner = new StringJoiner("");
if (idForClientCredentialsOnly.equalsIgnoreCase(requestBody)) {
joiner.add("grant_type=").add("client_credentials");
}
else {
joiner.add(requestBody);
if (!containsIgnoreCase(requestBody, "grant_type")) {
joiner.add("&grant_type=").add("password");
}
}
if (!containsIgnoreCase(requestBody, "scope")) {
joiner.add("&scope=").add("uiclient");//I use Keycloak so I specify the scope to get some extra information
}
finalRequestBody = joiner.toString();
return Mono.just(isBlank(finalRequestBody) ? requestBody : finalRequestBody);
};
}
}
I have a question regarding JWT authentication in ASP.NET Core and Claims, because I don't know if I get everything correctly.
When I create a JWT token in ASP.NET I add some Claims, some of which can be custom. What happens when the request with JWT token is sent from the client to API. How is User.Claims filled ? Does it use the claims that are read from JWT?
I would like to create a custom Identity provider ( don't want to use this provided by ASP.NET), with my own tables for user data, roles etc. I don't want store all important data required to fulfill the policy in JWT token (the amount of information stored in token matters, as well as security matters). Is it possible to store only basic claims (like user id, name etc) in JWT token, and then re-fetch other required data DB/ Cache? Along with that, I would like to use the standard mechanism for [Authorize] and the Policy mechanism.
How to make this all work: Custom User Identity + JWT + Standard ASP.NET policy-based authorization + claims fetched from DB/Cache on every request? How to achieve this?
Asp Net Core
First step is write the method that configure Jwt authentication:
// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
{
// Enable the use of an [Authorize(AuthenticationSchemes =
// JwtBearerDefaults.AuthenticationScheme)]
// attribute on methods and classes to protect.
services.AddAuthentication().AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = JwtController.SecurityKey,
ValidAudience = JwtController.Audience,
ValidIssuer = JwtController.Issuer,
// When receiving a token, check that we've signed it.
ValidateIssuerSigningKey = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew when validating
// the lifetime. As we're creating the tokens locally and validating
// them on the same machines which should have synchronised time,
// this can be set to zero.
ClockSkew = TimeSpan.FromMinutes(0)
};
});
}
Now inside the ConfigureServices() method of the Startup.cs, we can call ConfigureJwtAuthService() method to configure the Jwt authentication.
This is the complete Startup.cs:
using System;
using Autofac;
using ExpertCodeBlogWebApp.Controllers;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace ExpertCodeBlogWebApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add
// services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Configure jwt autenticazione
ConfigureJwtAuthService(services);
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
// Create the Autofac container builder for dependency injection
var builder = new ContainerBuilder();
// Add any Autofac modules or registrations.
builder.RegisterModule(new AutofacModule());
// Return ServiceProvider
var serviceProvider = services.BuildServiceProvider();
return serviceProvider;
}
// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
{
// Enable the use of an [Authorize(AuthenticationSchemes =
// JwtBearerDefaults.AuthenticationScheme)]
// attribute on methods and classes to protect.
services.AddAuthentication().AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = JwtController.SecurityKey,
ValidAudience = JwtController.Audience,
ValidIssuer = JwtController.Issuer,
// When receiving a token, check that we've signed it.
ValidateIssuerSigningKey = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew when validating
// the lifetime.
// As we're creating the tokens locally and validating them on the
// same machines which should have synchronised time, this can be
// set to zero.
ClockSkew = TimeSpan.FromMinutes(0)
};
});
}
// This method gets called by the runtime. Use this method to configure
// the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
// For dependency injection.
public class AutofacModule : Module
{
// Dependency Injection with Autofact
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<UserRepository>().As<IUserRepository>()
.SingleInstance();
}
}
}
The JwtController.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using ExpertCodeBlogWebApp.Domain.Models;
using ExpertCodeBlogWebApp.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
namespace ExpertCodeBlogWebApp.Controllers
{
[Route("api/[controller]")]
public class JwtController : Controller
{
#region Private Members
// JWT-related members
private TimeSpan TokenExpiration;
private SigningCredentials SigningCredentials;
// EF and Identity members, available through DI
private MyDbContext DbContext;
private IUserRepository _userRepository;
private readonly ILogger _logger;
#endregion Private Members
#region Static Members
private static readonly string PrivateKey = "my_PrivateKey";
public static readonly SymmetricSecurityKey SecurityKey =
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey));
public static readonly string Issuer = "my_Issuer";
public static readonly string Audience = "my_Audience";
#endregion Static Members
#region Constructor
// I have used Autofac in the Startup.cs for dependency injection)
public JwtController(
MyDbContext dbContext,
IUserRepository userRepository,
ILogger<JwtController> logger)
{
_logger = logger;
_userRepository = userRepository;
// Instantiate JWT-related members
TokenExpiration = TimeSpan.FromMinutes(10);
SigningCredentials = new SigningCredentials(SecurityKey,
SecurityAlgorithms.HmacSha256);
// Instantiate through Dependency Injection with Autofact
DbContext = dbContext;
}
#endregion Constructor
#region Public Methods
// Manages the request for a new authentication or the refresh of an
// already established one
[HttpPost("token")]
public async Task<IActionResult>
Authentication([FromBody]JwtRequestViewModel jwt)
{
if (ModelState.IsValid)
{
string grantType = jwt.GrantType;
if (grantType == "password")
{
string userName = jwt.UserName;
string password = jwt.Password;
// Password check required
var user = await
_userRepository.GetUserInfoWithCheckPwd(userName, password);
// Check if user is expired (check the ExpireDate property)
if (UserExpired(user))
return BadRequest($"Account of {user.Name} expired!");
if (UserEnabled(user))
return await GenerateToken(user);
else
return BadRequest("User name or password invalid.");
}
}
else if (grantType == "refresh_token")
{
string userName = jwt.UserName;
// Refresh token (no password check required)
var user = await _userRepository.GetUserInfoByName(userName);
// Check if user is expired (check the ExpireDate property)
if (UserExpired(user))
return BadRequest($"Account of {user.Name} expired!");
string token = jwt.Token;
if (token == user.Token)
{
// Generate token and send it via a json-formatted string
return await GenerateToken(user);
}
else
{
return BadRequest("User token invalid.");
}
}
else
return BadRequest("Authentication type invalid.");
}
else
return BadRequest("Request invalid.");
}
#endregion Public Methods
#region Private Methods
private bool UserExpired(Users utente)
{
if (utente != null)
return utente.ExpireDate.CompareTo(DateTime.Now) < 0;
return true;
}
private bool UserEnabled(Users utente)
{
if (utente != null)
return utente.Enabled == true;
return false;
}
private JsonSerializerSettings DefaultJsonSettings
{
get
{
return new JsonSerializerSettings()
{
Formatting = Formatting.Indented
};
}
}
private async Task<IActionResult> GenerateToken(Users user)
{
try
{
if (user != null)
{
var handler = new JwtSecurityTokenHandler();
DateTime newTokenExpiration = DateTime.Now.Add(TokenExpiration);
ClaimsIdentity identity = new ClaimsIdentity(
new GenericIdentity(user.Name, "TokenAuth"),
new[] { new Claim("ID", user.Id.ToString())}
);
var securityToken = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = JwtController.Issuer,
Audience = JwtController.Audience,
SigningCredentials = SigningCredentials,
Subject = identity,
Expires = newTokenExpiration
});
string encodedToken = handler.WriteToken(securityToken);
// Update token data on database
await _userRepository.UpdateTokenData(user.Name, encodedToken,
newTokenExpiration);
// Build the json response
// (I use Automapper to maps an object into another object)
var jwtResponse = Mapper.Map<JwtResponseViewModel>(user);
jwtResponse.AccessToken = encodedToken;
jwtResponse.Expiration = (int)TokenExpiration.TotalSeconds;
return Ok(jwtResponse);
}
return NotFound();
}
catch(Exception e)
{
return BadRequest(e.Message);
}
}
#endregion
}
}
On my project I use Angular. For call JwtController method by Angular:
login(userName: string, password: string)
{
return this.getLoginEndpoint(userName, password)
.map((response: Response) => this.processLoginResponse(response));
}
getLoginEndpoint(userName: string, password: string): Observable<Response>
{
// Body
// JwtRequest is a model class that I use to send info to the controller
let jwt = new JwtRequest();
jwt.GrantType = "password";
jwt.UserName = userName;
jwt.Password = password;
jwt.ClientId = "my_Issuer";
// Post requiest (I use getAuthHeader that attach to the header the
// authentication token, but it can also be omitted because it is ignored
// by the JwtController
return this.http.post(this.loginUrl, JSON.stringify(jwt),
this.getAuthHeader(true))
}
protected getAuthHeader(includeJsonContentType?: boolean): RequestOptions
{
// Hera I use this.authService.accessToken that is a my service where
// I have store the token received from the server
let headers = new Headers({
'Authorization': 'Bearer ' + this.authService.accessToken });
if (includeJsonContentType)
headers.append("Content-Type", "application/json");
headers.append("Accept", `application/vnd.iman.v01+json,
application/json, text/plain, */*`);
headers.append("App-Version", "01");
return new RequestOptions({ headers: headers });
}
private processLoginResponse(response: Response)
{
// process the response..
}
On the controllers classes (or methods) that you want to be accessible only by authenticated users (not on your JwtController because its method must be accessible by all users) you can set:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
To call from Angular the controller method that require authentication, you need to attach the token into the header with the getAuthHeader() method.
I hope this post can help you.
yes it uses the claim stored in jwt token
look at the httpcontext object for claims that are stored in token when you created the token
this link can also be helpfull https://joonasw.net/view/adding-custom-claims-aspnet-core-2
I'm using the awesome ServiceStack to implement my REST backend which serves two iPhone apps written in Xamarin. Everything works great but i'm struggling in getting sessions to work correctly when the two apps are installed on the same device !
The issue is that if I login in one of the apps the second app gets authenticated and doesn't require me to login as a result of 'isCurrentUserAuthenticated()' method below.
I pass cookies with my requests to mimic the browser and to make sure user doesn't have to pass his credentials every time but I guess the problem is that maybe ServiceStack sees two authentication requests from the same IP so it authenticated them both using the first authentication requests succeeds.
Note : The two apps accesses the same database and UserAuth table but every app supports a user role different than the other.
The only way to fix it is to logout from the second app so the user can login again with his credentials to make everything work.
Can you please help with this ?
Here is the code so far :
public static class BLL
{
public static JsonServiceClient ServiceClient { get; set; }
public static string HostUri = "http://test.elasticbeanstalk.com";
public static string HostDomain = "test.elasticbeanstalk.com";
static BLL ()
{
string ss_id = ConfigRepository.GetConfigString ("ss-id");
string ss_pid = ConfigRepository.GetConfigString ("ss-pid");
ServiceClient = new JsonServiceClient (HostUri);
ServiceClient.CookieContainer.Add (new Cookie ("ss-id", ss_id, "/", HostDomain));
ServiceClient.CookieContainer.Add (new Cookie ("ss-pid", ss_pid, "/", HostDomain));
}
public static async Task<bool> isCurrentUserAuthenticated ()
{
bool result = false;
try {
Authenticate authRequest = new Authenticate ();
// Restore the cookie
var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
NSUserDefaults.StandardUserDefaults.SetString (response.UserId, "UserId");
NSUserDefaults.StandardUserDefaults.Synchronize ();
result = true;
} catch (Exception Ex) {
result = false;
}
return result;
}
public static async Task<AuthenticateResponse> Login (string userName, string password)
{
Authenticate authRequest = new Authenticate () {
provider = "credentials",
UserName = userName,
Password = password,
RememberMe = true,
};
var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
var cookies = ServiceClient.CookieContainer.GetCookies (new Uri (HostUri));
if (cookies != null) {
var ss_id = cookies ["ss-id"].Value;
var ss_pid = cookies ["ss-pid"].Value;
if (!ss_id.IsNullOrEmpty ()) {
int r = ConfigRepository.AddConfigKey ("ss-id", ss_id);
System.Diagnostics.Debug.WriteLine ("ss-id " + ss_id.ToString ());
}
if (!ss_pid.IsNullOrEmpty ()) {
int r = ConfigRepository.AddConfigKey ("ss-pid", ss_pid);
System.Diagnostics.Debug.WriteLine ("ss-pid " + ss_pid.ToString ());
}
}
NSUserDefaults.StandardUserDefaults.SetString (response.UserId, "UserId");
NSUserDefaults.StandardUserDefaults.Synchronize ();
return response;
}
public static async Task<AuthenticateResponse> Logout ()
{
Authenticate authRequest = new Authenticate () {
provider = "logout"
};
var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
return response;
}
}
The issue is because you're using the same Session Cookies with a shared ServiceClient instance which ends up referencing the same Authenticated Users Session.
ServiceStack Sessions are only based on the session identifiers (ss-id/ss-pid) specified by the clients cookies, if you use the same cookies you will be referencing the same Authenticated Users Session, they're not affected by IP Address or anything else.
If you want to authenticate as another user, use a new instance of the ServiceClient (so it's not using an existing Sessions Cookies).
My application is supposed to received a request parameter called sessionId which is supposed to be used to lookup for a crosscontext attribute.
I was looking at Spring Security to implement this and I think already have a good implementation of my AuthenticationProvider :
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ServletContext servletContext = request.getSession().getServletContext();
String sessionId = request.getParameter("sessionId");
if (sessionId != null) {
ServletContext sc = request.getSession().getServletContext();
Object obj = sc.getContext("/crosscontext").getAttribute(sessionId);
if (obj != null) {
// return new Authentication
}
} else {
logger.error("No session id provided in the request");
return null;
}
if (!GWT.isProdMode()) {
// return new Authentication
} else {
logger.error("No session id provided in the request");
return null;
}
}
Now, what I would like to do is to configure Spring Security to not prompt for a user name and password, to let it reach this authentication provider call the authenticate method.
How can I achieve this ?
I fixed my issue by reviewing the design of my security and going for something closer to the preauthenticated mechanisms that are already provided by Spring Security.
I extended 2 components of Spring Security.
First one is an AbstractPreAuthenticatedProcessingFilter, usually his role is to provide the principal provided in the headers. In my case, I retrieve the header value and search in the context shared between 2 application for an attribute that corresponds to that header and returns it as principal :
public class MyApplicationPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private static final Logger logger = Logger.getLogger(MyApplicationPreAuthenticatedProcessingFilter.class);
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
if (MyApplicationServerUtil.isProdMode()) {
String principal = request.getHeader("MY_HEADER");
String attribute = (String) request.getSession().getServletContext().getContext("/crosscontext").getAttribute(principal);
logger.info("In PROD mode - Found value in crosscontext: " + attribute);
return attribute;
} else {
logger.debug("In DEV mode - passing through ...");
return "";
}
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return null;
}
}
The other component is the AuthenticationProvider which will just check if the authentication contains a principal when it runs in prod mode (GWT prod) :
public class MyApplicationAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = Logger.getLogger(MyApplicationAuthenticationProvider.class);
public static final String SESSION_ID = "sessionId";
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (MyApplicationServerUtil.isProdMode()) {
if (StringUtils.isNotEmpty((String) authentication.getPrincipal())) {
logger.warn("Found credentials: " + (String) authentication.getPrincipal());
Authentication customAuth = new CustomAuthentication("ROLE_USER");
customAuth.setAuthenticated(true);
return customAuth;
} else {
throw new PreAuthenticatedCredentialsNotFoundException("Nothing returned from crosscontext for sessionId attribute ["
+ (String) authentication.getPrincipal() + "]");
}
} else {
Authentication customAuth = new CustomAuthentication("ROLE_USER");
customAuth.setAuthenticated(true);
return customAuth;
}
}
#Override
public boolean supports(Class<?> authentication) {
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication);
}
}
I understand that it might not be the most secure application. However, it will already be running in a secure environment. But if you have suggestions for improvement, they're welcome !
I'm using DotNetOpenAuth v3.5.0.10357 and each time a user authenticates against Facebook I get a different claimed identifier back. The token looks to be encrypted so I assume DNOA is somehow encrypting the token along with the expiry. Can anyone confirm this? Or am I using it wrong:
public ActionResult FacebookLogOn(string returnUrl)
{
IAuthorizationState authorization = m_FacebookClient.ProcessUserAuthorization();
if (authorization == null)
{
// Kick off authorization request
return new FacebookAuthenticationResult(m_FacebookClient, returnUrl);
}
else
{
// TODO: can we check response status codes to see if request was successful?
var baseTokenUrl = "https://graph.facebook.com/me?access_token=";
var requestUrl = String.Format("{0}{1}", baseTokenUrl, Uri.EscapeDataString(authorization.AccessToken));
var claimedIdentifier = String.Format("{0}{1}", baseTokenUrl, authorization.AccessToken.Split('|')[0]);
var request = WebRequest.Create(requestUrl);
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
var graph = FacebookGraph.Deserialize(responseStream);
var token = RelyingPartyLogic.User.ProcessUserLogin(graph, claimedIdentifier);
this.FormsAuth.SignIn(token.ClaimedIdentifier, false);
}
}
return RedirectAfterLogin(returnUrl);
}
}
Here's the code for FacebookAuthenticationResult:
public class FacebookAuthenticationResult : ActionResult
{
private FacebookClient m_Client;
private OutgoingWebResponse m_Response;
public FacebookAuthenticationResult(FacebookClient client, string returnUrl)
{
m_Client = client;
var authorizationState = new AuthorizationState(new String[] { "email" });
if (!String.IsNullOrEmpty(returnUrl))
{
var currentUri = HttpContext.Current.Request.Url;
var path = HttpUtility.UrlDecode(returnUrl);
authorizationState.Callback = new Uri(String.Format("{0}?returnUrl={1}", currentUri.AbsoluteUri, path));
}
m_Response = m_Client.PrepareRequestUserAuthorization(authorizationState);
}
public FacebookAuthenticationResult(FacebookClient client) : this(client, null) { }
public override void ExecuteResult(ControllerContext context)
{
m_Response.Send();
}
}
Also, I am using the RelyingPartyLogic project included in the DNOA samples, but I added an overload for ProcessUserLogin that's specific to facebook:
public static AuthenticationToken ProcessUserLogin(FacebookGraph claim, string claimedIdentifier)
{
string name = claim.Name;
string email = claim.Email;
if (String.IsNullOrEmpty(name))
name = String.Format("{0} {1}", claim.FirstName, claim.LastName).TrimEnd();
return ProcessUserLogin(claimedIdentifier, "http://facebook.com", email, name, claim.Verified);
}
It looks as though FacebookClient inherits from WebServerClient but I looked for the source on GitHub and I don't see a branch or a tag related (or at least not labeled) with the corresponding v3.5 version.
Facebook does not support OpenID. Claimed Identifier is an OpenID term. Facebook uses OAuth 2.0, so you're mixing up OpenID and OAuth.
Facebook sends a different access token every time, which is normal for the OAuth protocol. You have to use the access token to query Facebook for the user id that is consistent on every visit.
I think you need to add the offline_access permission in the token request as well, see https://developers.facebook.com/docs/reference/api/permissions/