Get user email from facebook in asp net - c#

I'm trying to get user name and user email from facebook. I read a lot of information on this topic and this is my final code that works for some reason only on my facebook app admin account:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
/*Other external login options*/
var FacebookOptions = new FacebookAuthenticationOptions()
{
AppId = "My App Id",
AppSecret = "My App Secret",
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
BackchannelHttpHandler = new FacebookBackChannelHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.7/me?fields=id,name,email"
};
}
}
public class FacebookBackChannelHandler : HttpClientHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.Replace("?access_token", "&access_token"));
}
return await base.SendAsync(request, cancellationToken);
}
}
public class AccountController : Controller
{
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
/*my sign in code that works on my facebook app admin account*/
}
}
On every over account I get loginInfo.Email is equal to null.
In developers.facebook.com I have:
When someone clicks on "Login with Facebook" he gets this message:
If I click "Review the info you provide" i get:
What am I missing? Why doesn't it work?

Finally found an answer! All that needs to be done is to add Scope = { "email" } to FacebookOptions and that's solves the problem!
My code now:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
/*Other external login options*/
var FacebookOptions = new FacebookAuthenticationOptions()
{
AppId = "My App Id",
AppSecret = "My App Secret",
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
BackchannelHttpHandler = new FacebookBackChannelHandler(),
Scope = { "email" },
UserInformationEndpoint = "https://graph.facebook.com/v2.7/me?fields=id,name,email"
};
}
}
The rest of the code stays the same. I only added error page for the case if this problem returns:
public class AccountController : Controller
{
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if(loginInfo.Email == null)
{
return RedirectToAction("FacebookError", "Account");
}
/*my sign in code*/
}
}

Facebook offers people full control over the permissions they grant to an app. That control extends beyond the point at which they see the login dialog. They can choose not to grant certain permissions during the login process. They can also revoke permissions in their Facebook privacy settings at any time. Apps should check for the validity of permissions before attempting to perform an API call where they are required. For example, checking that email action is still granted before attempting to publish an Open Graph story.
facebook provides the a graph API endpoint to retrieve a list of granted permissions:
GET /{user-id}/permissions
The call must be made with either a user access token or your app access token. The call will return a JSON string containing the permission names which have been granted to the app and their status:
{
"data": [
{
"permission": "public_profile",
"status": "granted"
},
{
"permission": "publish_actions",
"status": "granted"
},
{
"permission": "user_friends",
"status": "declined"
}
]
}
by using this, you can detect whether it is possible or not to fetch the information you want. hope this helps.
reference: https://developers.facebook.com/docs/facebook-login/permissions/requesting-and-revoking

Related

How to set up a Sign In only Azure B2C user flow policy? Getting HTTP401 error

In my AccountController I have the following methods:
/*
* Called when requesting to sign up or sign in
*/
public void SignUpSignIn(string redirectUrl)
{
redirectUrl = redirectUrl ?? "/";
// Use the default policy to process the sign up / sign in flow
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = redirectUrl });
return;
}
/*
* Called when requesting to sign up
*/
public void SignUp()
{
// Use the default policy to process the sign up flow
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, Globals.SignUpPolicyId);
return;
}
The UserFlow is set up inside of Azure, called B2C_1_signup, and that's what Globals.SignUpPolicyId evaluates to. Yet, whenever I test it out, I get an HTTP 401 error.
Here's the razor code that creates my button/link:
#Html.ActionLink("Sign Up!", "SignUp", "Account", routeValues: null, htmlAttributes: new { id = "signUpLink", #class = "btn btn-default" })
Whenever I test the link provided by Microsoft inside of the B2C Tenant, it brings up the Sign Up page correctly.
Here's the cleansed link provided by Microsoft for testing:
https://mytenantname.b2clogin.com/mytenantname.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_signup&client_id=RANDOM_GUID&nonce=defaultNonce&redirect_uri=http%3A%2F%2Flocalhost%3A1111&scope=openid&response_type=id_token&prompt=login
What am I missing??
• The redirect URI string defined in the account controller should be defined in the app config settings as a private static string and the B2C policies as different identifiers as public static strings due to which when during the user flow, authentication redirection will happen through by referencing the concerned app config string rather than finding it in the controller file itself. Since, you are encountering HTTP 401 error due to authentication issues related to the browser session.
Please find below the app controller sample methods calling the Azure AD B2C policies which works correctly as defined below for sign up, sign in and profile of
the user to be authenticated: -
public class AccountController : Controller
{
public void SignIn()
{
if (!Request.IsAuthenticated)
{
// To execute a policy, you simply need to trigger an OWIN challenge.
// You can indicate which policy to use by specifying the policy id as the AuthenticationType
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignInPolicyId);
}
}
public void SignUp()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignUpPolicyId);
}
}
public void Profile()
{
if (Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.ProfilePolicyId);
}
}
public void SignOut()
{
// To sign out the user, you should issue an OpenIDConnect sign out request
if (Request.IsAuthenticated)
{
IEnumerable<AuthenticationDescription> authTypes = HttpContext.GetOwinContext().Authentication.GetAuthenticationTypes();
HttpContext.GetOwinContext().Authentication.SignOut(authTypes.Select(t => t.AuthenticationType).ToArray());
}
}
}
Also, refer the below link for more clarified information: -
https://bitoftech.net/2016/08/31/integrate-azure-ad-b2c-asp-net-mvc-web-app/
Also, find the below gif output for reference: -

Google Authentication get user profile picture

If user is authenticated via google, I need to get his profile picture.
If user is authenticated via facebook I get his profile picture with this code:
var info = await _signInManager.GetExternalLoginInfoAsync();
var identifier = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var picture = $"https://graph.facebook.com/{identifier}/picture"; // 3
So, which code I need to use in 3 line for getting user's profile picture in case user is authenticated via google?
Accessing user profile picture, full solution.
Stack:
Asp Net Identity 4 v4 + Asp Net Identity + Google People Api
Steps
1. Preparation
1.1 I'm using default HttpClient to generate HTTP requests to Google API
1.2 Install NUGET System.Net.Http.Json to ease serialization.
1.3 Use a [json to c#] tool, to create a DTO like this:
/// <summary>
/// 🟡 official docs.
/// https://developers.google.com/people/api/rest/v1/people#Person.Photo
/// </summary>
public class PeopleApiPhotos {
public string resourceName { get; set; }
public string etag { get; set; }
public List<Photo> photos { get; set; }
public class Source {
public string type { get; set; }
public string id { get; set; }
}
public class Metadata {
public bool primary { get; set; }
public Source source { get; set; }
}
public class Photo {
public Metadata metadata { get; set; }
public string url { get; set; }
}
}
2. Identity Server: when users confirms external info data, send http get request, to retrieve avatar picture url:
Identity Server with Asp Net Identity > Login > OnPostConfirmationAsync: FULL METHOD
public async Task < IActionResult > OnPostConfirmationAsync(string returnUrl = null) {
returnUrl = returnUrl ? ?Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null) {
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new {
ReturnUrl = returnUrl
});
}
// try to get profile picture
string pictureUri = string.Empty;
if (info.LoginProvider.ToLower() == "google") {
var httpClient = _httpClientFactory.CreateClient();
string peopleApiKey = _configuration["GoogleApiKey:PeopleApiKey"];;
var googleAccountId = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var photosResponse = await httpClient.GetFromJsonAsync < PeopleApiPhotos > (
$ "https://people.googleapis.com/v1/people/{googleAccountId}?personFields=photos&key={peopleApiKey}");
pictureUri = photosResponse ? .photos.FirstOrDefault() ? .url;
}
if (ModelState.IsValid) {
// Cria usuário
var user = new AppUser {
UserName = Input.Email,
Email = Input.Email,
FirstName = Input.FirstName,
LastName = Input.LastName,
ProfilePictureUrl = pictureUri
};
var result = await _userManager.CreateAsync(user);
if (result.Succeeded) {
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded) {
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page("/Account/ConfirmEmail", pageHandler: null, values: new {
area = "Identity",
userId = userId,
code = code
},
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $ "Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
// If account confirmation is required, we need to show the link if we don't have a real email sender
if (_userManager.Options.SignIn.RequireConfirmedAccount) {
return RedirectToPage("./RegisterConfirmation", new {
Email = Input.Email
});
}
await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach(var error in result.Errors) {
ModelState.AddModelError(string.Empty, error.Description);
}
}
ProviderDisplayName = info.ProviderDisplayName;
ReturnUrl = returnUrl;
return Page();
}
2.1 Identity Server with Asp Net Identity > Login > OnPostConfirmationAsync: HIGHLIGHT:
❗️ this is only a little part from full method above.
Checks if external provider is Google, and use [NameIdentifier] and [Google Api Key] to reach People Endpoint.
// try to get profile picture
string pictureUri = string.Empty;
if (info.LoginProvider.ToLower() == "google") {
var httpClient = _httpClientFactory.CreateClient();
// ApiKey can get generated in [Google Developers Console](https://console.developers.google.com/apis/credentials).
string peopleApiKey = _configuration["GoogleApiKey:PeopleApiKey"];;
var googleAccountId = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var photosResponse = await httpClient.GetFromJsonAsync < PeopleApiPhotos > (
$ "https://people.googleapis.com/v1/people/{googleAccountId}?personFields=photos&key={peopleApiKey}");
pictureUri = photosResponse ? .photos.FirstOrDefault() ? .url;
}
3. Identity Server > Profile Service Implementation:
public async Task GetProfileDataAsync(ProfileDataRequestContext context) {
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.ToList();
claims.Add(new Claim(JwtClaimTypes.GivenName, user.FirstName));
claims.Add(new Claim(JwtClaimTypes.FamilyName, user.LastName));
// Insert a new claim, that gets ProfilePictureUrl persisted in my app user in database.
claims.Add(new Claim(JwtClaimTypes.Picture, user.ProfilePictureUrl));
context.IssuedClaims = claims;
}
4. Acessing profile user in ReactJS Frontend:
To get user profile data in frontend, i'm using OidcClient-js
// here i'm using oidc-client.js
// user object is loaded with user full data.
let profilePictureUrl = user.profile.picture;
Thanks to #DaImTo response.
People.get method returns a person object which contains
Your user needs to be authenticated with the profile scope.
Raw http request
GET https://people.googleapis.com/v1/people/me?personFields=photos HTTP/1.1
Authorization: Bearer [YOUR_ACCESS_TOKEN]
Accept: application/json
response
{
"resourceName": "people/117200475532672775346",
"etag": "%EgQBAzcuGgQBAgUHIgxHcHNCRHZycjVkZz0=",
"photos": [
{
"metadata": {
"primary": true,
"source": {
"type": "PROFILE",
"id": "1172004755672775346"
}
},
"url": "https://lh3.googleusercontent.com/a-/AOh14GhroCYJp2P9xeYeYk1npchBPK-zbtTxzNQo0WAHI20=s100"
},
{
"metadata": {
"source": {
"type": "CONTACT",
"id": "3faa96eb0baa4be"
}
},
"url": "https://lh6.googleusercontent.com/-vuhaM1mUvwE/VFOBzFDW-TI/AAAAAAAAAAA/izR9rgfDIyoVoHd7Mq_OJmdbwjhEnfhEQCOQCEAE/s100/photo.jpg"
}
]
}
Note: You can also get this information from the userinfo endpoint however Google does not guarantee that they will send the claims everytime you make the request so IMO its best to go though the people api.

How to challenge Windows Azure Active Directory authentication?

We have a SPA with angularjs 1.6 and asp.net web API. We use Microsoft Account Authentication in OWIN middleware.
In Startup.Auth.cs
MicrosoftAccountAuthenticationOptions microsoftAccountAuthenticationOptions = new MicrosoftAccountAuthenticationOptions()
{
Caption = "Connection with your Microsoft account",
ClientId = microsoftAccountAuthenticationConfigurationElement.ClientId,
ClientSecret = microsoftAccountAuthenticationConfigurationElement.ClientSecret,
};
app.UseMicrosoftAccountAuthentication(microsoftAccountAuthenticationOptions);
Our controller method to challenge the authentication
[Route("ExternalLogin")]
[HttpPost]
[AllowAnonymous]
public IHttpActionResult ExternalLogin(string authenticationType, string returnUrl)
{
return new ChallengeResult(new List<AuthenticationHeaderValue>(), this)
{
RedirectUri = this.Url.Route("ExternalLoginCallback", new { returnUrl }),
AuthenticationType = authenticationType
};
}
And the ChallengeResult class
internal class ChallengeResult : UnauthorizedResult
{
public ChallengeResult(IEnumerable<AuthenticationHeaderValue> challenges, ApiController controller):base(challenges, controller)
{ }
public string AuthenticationType { get; set; }
public string RedirectUri { get; set; }
public override Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = this.RedirectUri }, this.AuthenticationType);
return base.ExecuteAsync(cancellationToken);
}
}
This works perfectly.
Now we want add an Office365 authentication. So we use Windows Azure Active Directory authentication:
WindowsAzureActiveDirectoryBearerAuthenticationOptions windowsAzureActiveDirectoryBearerAuthenticationOptions = new WindowsAzureActiveDirectoryBearerAuthenticationOptions()
{
Tenant = windowsAzureActiveDirectoryAuthenticationConfigurationElement.Tenant,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = windowsAzureActiveDirectoryAuthenticationConfigurationElement.Audience
}
};
app.UseWindowsAzureActiveDirectoryBearerAuthentication(windowsAzureActiveDirectoryBearerAuthenticationOptions);
And we reuse ExternalLogin api to challenge the authentication.
In this case the client receive a unauthorized result 401 instead of the redirect 302.
My question : Why the authentication challenge doesn't tranform the unauthorized result in redirect result on https://login.microsoftonline.com/ ?
Note : If use ADAl.js on client side to challenge the authentication then the redirection works. But I don't wish use the adal.js library
In the case of a SPA + API, the API should do Bearer token authentication as you have configured now.
But the authentication redirect should start from the SPA using ADAL.JS etc.
An API should not do 302 redirects for authentication.
If the caller is a program running on a server, what are they supposed to do?
That's why it returns a 401, because authentication failed.

Azure Mobile App and Authentication

I have a Mobile App I am writing, at present it is simply the To Do Item list quick start application with custom Authentication added. I have the associated Xamarin Forms app.
From the App I am able to login using the LoginAsync method, my website returns a token and shows the username I am logging in as, but subsequent calls suggest I am not authorised.
After a bit of debugging, I can see that the request arrives at the web server with the X-ZUMO-AUTH header and the token in the value, but I can see that the User does not seem to be populated and the call to the GetAllTodoItems method is returned as 401:Unauthorized.
In the startup code for the website, the ConfigureMobileApp contains the following:
app.UseWebApi(config);
if (string.IsNullOrEmpty(settings.HostName))
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
I have an account controller class:
[Route(".auth/login/custom")]
public class AccountController : ApiController
{
private static string URL = "https://myapidev.azurewebsites.net/";
private static string KEY = "FC31EB8CAAAAAA9D74EEE3613A7A08CA65CB1ACAA8CEFF82A5B5E915625B31D";
public AccountController()
{
}
[HttpPost]
public IHttpActionResult Post([FromBody] LoginUser assertion)
{
if (isValidAssertion(assertion))
{
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(new Claim[] { new Claim(JwtRegisteredClaimNames.Sub, assertion.username) },
ConfigurationManager.AppSettings["SigningKey"],
ConfigurationManager.AppSettings["ValidAudience"],
ConfigurationManager.AppSettings["ValidIssuer"],
TimeSpan.FromHours(24));
return Ok(new LoginResult()
{
authenticationToken = token.RawData,
user = new LoginResultUser() { userId = assertion.username }
});
}
else // user assertion was not valid
{
return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid Request"));
}
}
private bool isValidAssertion(LoginUser assertion)
{
return assertion != null;
}
}
The TodoItemController contains the following:
[Authorize]
[MobileAppController]
public class TodoItemController : TableController<TodoItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
EducaterAPIDevContext context = new EducaterAPIDevContext();
DomainManager = new EntityDomainManager<TodoItem>(context, Request);
//// Get the SID of the current user.
//var claimsPrincipal = this.User as ClaimsPrincipal;
//string sid = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier).Value;
}
// GET tables/TodoItem
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
...
...
}
On calling the query method from the Xamarin App, it returns with 401 even though the X-ZUMO-AUTH is in the headers and contains the correct token issued by the login method.
Have I missed something or has anyone come across this issue before - any help would be appreciated?
Have you turned on Authentication/Authorization in your App Service? Without it, the token will never be decoded.
This is the most common issue.
After digging I found the issue, initially there was a configuration issue - the above comments helped thanks. The Audiences and Issuers must match your azure site including including the trailing slash.
The issue once the configuration had been corrected was that the token which is passed correctly from my App did not get processed at the server side so all Authorized areas where out-of-bounds. This was because of the order of calls in the ConfigureMobileApp method. I was calling the app.UseWebApi method before the app.UseAppServiceAuthentication method, changing the order suddenly had the token being tested again.
The dummy site I have working now has the following:
public static void ConfigureMobileApp(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
//For more information on Web API tracing, see http://go.microsoft.com/fwlink/?LinkId=620686
SystemDiagnosticsTraceWriter traceWriter = config.EnableSystemDiagnosticsTracing();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.MapApiControllers()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
// Use Entity Framework Code First to create database tables based on your DbContext
//Database.SetInitializer(new EducaterAPIDevInitializer());
// To prevent Entity Framework from modifying your database schema, use a null database initializer
// Database.SetInitializer<EducaterAPIDevContext>(null);
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
var options = new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
};
app.UseAppServiceAuthentication(options);
}
app.UseWebApi(config);
}

ServiceStack API and ASP MVC Authentication in two ways

I'm having trouble solving architecture of an ASP MVC application that servers html pages and web services through ServiceStack.
The application lives in the base url eg "http://myapplication.com" and SS lives in "http://myapplication.com/api" because it is the easiest way to configure both.
In general everything works fine, but when I reached the part of the authorization and authentication, is where I'm stuck.
For one, I need the application handle cookies as ASP normally do FormsAuthentication through, and users would go through a login screen and could consume actions and controllers when the attribute "Authorize" is used. This is typical of ASP, so I have no problem with it, such as "http://myapplication.com/PurchaseOrders".
On the other hand, clients of my application will consume my web service api from javascript. Those web services will also be tagged in some cases with the attribute "Authenticate" of ServiceStack. For example "http://myapplication.com/api/purchaseorders/25" would have to validate if the user can view that particular purchase order, otherwise send a 401 Unauthorized so javascript can handle those cases and display the error message.
Last but not least, another group of users will make use of my API by a token, using any external application (probably Java or .NET). So I need to solve two types of authentication, one using username and password, the other by the token and make them persistant so once they are authenticated the first time, the next calls are faster to solve from the API.
This is the code that I have so far, I've put it very simply to make clear the example.
[HttpPost]
public ActionResult Logon(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
JsonServiceClient client = new JsonServiceClient("http://myapplication.com/api/");
var authRequest = new Auth { provider = CredentialsAuthProvider.Name, UserName = model.UserName, Password = model.Password, RememberMe = model.RememberMe };
try
{
var loginResponse = client.Send(authRequest);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(loginResponse.UserName, false, 60);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
Response.Cookies.Add(cookie);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Test");
}
}
catch (Exception)
{
ModelState.AddModelError("", "Invalid username or password");
}
}
return View();
}
As for the authentication provider I am using this class
public class MyCredentialsAuthProvider : CredentialsAuthProvider
{
public MyCredentialsAuthProvider(AppSettings appSettings)
: base(appSettings)
{
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
if (userName == "testuser" && password == "nevermind")
{
return true;
}
else
{
return false;
}
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
//Fill the IAuthSession with data which you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
//...
session.CreatedAt = DateTime.Now;
session.DisplayName = "Mauricio Leyzaola";
session.Email = "mauricio.leyzaola#gmail.com";
session.FirstName = "Mauricio";
session.IsAuthenticated = true;
session.LastName = "Leyzaola";
session.UserName = "mauricio.leyzaola";
session.UserAuthName = session.UserName;
var roles = new List<string>();
roles.AddRange(new[] { "admin", "reader" });
session.Roles = roles;
session.UserAuthId = "uniqueid-from-database";
//base.OnAuthenticated(authService, session, tokens, authInfo);
authService.SaveSession(session, SessionExpiry);
}
}
On the Configure function of AppHost I am setting my custom authentication class to use it as the default. I guess I should create another class and add it here as well, to handle the token scenario.
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new MyCredentialsAuthProvider(appSettings)
}, htmlRedirect: "~/Account/Logon"));
So far, ServiceStack is working as expected. I can submit a post to /auth/credentials passing username and password and it stores this information, so next call to a service the request is already authorized, great so far!
The question I need to know is how to call (and probably set somewhere in SS) the user that is logging in from my Account controller. If you see the first block of code I am trying to call the web service (looks like I am doing it wrong) and it works, but the next call to any web service looks unauthenticated.
Please don't point me to ServiceStack tutorials, I've been there for the last two days and still cannot figure it out.
Thanks a lot in advance.
Here is what I usually use:
You can replace the "Logon" action method with the code below:
public ActionResult Login(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
var authService = AppHostBase.Resolve<AuthService>();
authService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var response = authService.Authenticate(new Auth
{
UserName = model.UserName,
Password = model.Password,
RememberMe = model.RememberMe
});
// add ASP.NET auth cookie
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToLocal(returnUrl);
}
catch (HttpError)
{
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
...and the plugins:
//Default route: /auth/{provider}
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
new CustomBasicAuthProvider()
}));
....the Auth provider classes:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return UserLogUtil.LogUser(authService, userName, password);
}
}
public class CustomBasicAuthProvider : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return UserLogUtil.LogUser(authService, userName, password);
}
}
...finally, the logging utility class
internal static class UserLogUtil
{
public static bool LogUser(IServiceBase authService, string userName, string password)
{
var userService = new UserService(); //This can be a webservice; or, you can just call your repository from here
var loggingResponse = (UserLogResponse)userService.Post(new LoggingUser { UserName = userName, Password = password });
if (loggingResponse.User != null && loggingResponse.ResponseStatus == null)
{
var session = (CustomUserSession)authService.GetSession(false);
session.DisplayName = loggingResponse.User.FName.ValOrEmpty() + " " + loggingResponse.User.LName.ValOrEmpty();
session.UserAuthId = userName;
session.IsAuthenticated = true;
session.Id = loggingResponse.User.UserID.ToString();
// add roles and permissions
//session.Roles = new List<string>();
//session.Permissions = new List<string>();
//session.Roles.Add("Admin);
//session.Permissions.Add("Admin");
return true;
}
else
return false;
}
}

Categories