ServiceStack and FacebookAuthProvider - servicestack

I've been working with ServiceStack and it's Auth providers. Specifically "FacebookAuthProvider".
My issue here is that the service is called from an iOS app. This app already have a valid access token and i just want to pass this value to servicestack facebook authentication.
I've seen the tests on servicestack github page, but it still doesn't make sense to me.
Is it possible to pass this access token to servicestack, so the authentication skips the part where i ask for permission, since we already did the on the app?
Or am i approching this the wrong way?

Instead of using the builtin facebook auth provider i created my own CustomFacebookAuthProvider.
The reason is that the builtin version needs a browser to redirect the user to facebook for authentication and i didn't need that. I already had an access token.
So based on the official version FacebookAuthProvider.cs i created my own.
using System;
using System.Collections.Generic;
using System.Net;
using Elmah;
using Mondohunter.Backend.BusinessLogic.Interfaces;
using ServiceStack.Common.Extensions;
using ServiceStack.Common.Web;
using ServiceStack.Configuration;
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;
using ServiceStack.Text;
using ServiceStack.WebHost.Endpoints;
namespace Mondohunter.Interfaces
{
public class CustomFacebookAuthProvider : OAuthProvider
{
public const string Name = "facebook";
public static string Realm = "https://graph.facebook.com/";
public static string PreAuthUrl = "https://www.facebook.com/dialog/oauth";
public string AppId { get; set; }
public string AppSecret { get; set; }
public string[] Permissions { get; set; }
public CustomFacebookAuthProvider(IResourceManager appSettings)
: base(appSettings, Realm, Name, "AppId", "AppSecret")
{
this.AppId = appSettings.GetString("oauth.facebook.AppId");
this.AppSecret = appSettings.GetString("oauth.facebook.AppSecret");
}
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var tokens = Init(authService, ref session, request);
try
{
if (request.oauth_token.IsNullOrEmpty())
throw new Exception();
tokens.AccessToken = request.oauth_token;
session.IsAuthenticated = true;
var json = AuthHttpGateway.DownloadFacebookUserInfo(request.oauth_token);
var authInfo = JsonSerializer.DeserializeFromString<Dictionary<string, string>>(json);
//Here i need to update/set userauth id to the email
//UpdateUserAuthId(session, authInfo["email"]);
authService.SaveSession(session, SessionExpiry);
OnAuthenticated(authService, session, tokens, authInfo);
//return json/xml/... response;
}
catch (WebException ex)
{
//return json/xml/... response;
}
catch (Exception ex)
{
//return json/xml/... response;
}
}
protected override void LoadUserAuthInfo(AuthUserSession userSession, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
if (authInfo.ContainsKey("id"))
tokens.UserId = authInfo.GetValueOrDefault("id");
if (authInfo.ContainsKey("name"))
tokens.DisplayName = authInfo.GetValueOrDefault("name");
if (authInfo.ContainsKey("first_name"))
tokens.FirstName = authInfo.GetValueOrDefault("first_name");
if (authInfo.ContainsKey("last_name"))
tokens.LastName = authInfo.GetValueOrDefault("last_name");
if (authInfo.ContainsKey("email"))
tokens.Email = authInfo.GetValueOrDefault("email");
if (authInfo.ContainsKey("gender"))
tokens.Gender = authInfo.GetValueOrDefault("gender");
if (authInfo.ContainsKey("timezone"))
tokens.TimeZone = authInfo.GetValueOrDefault("timezone");
LoadUserOAuthProvider(userSession, tokens);
}
public override void LoadUserOAuthProvider(IAuthSession authSession, IOAuthTokens tokens)
{
var userSession = authSession as CustomUserSession;
if (userSession == null) return;
userSession.Email = tokens.Email ?? userSession.PrimaryEmail ?? userSession.Email;
}
}
}
I hope it makes sense.

Related

Azure function to create Service Principal [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
What's the recommended way to create Azure function to create a AAD Service principal.
Should we be doing Azure function using Powershell maybe?
As per your comment To Create User From Azure function using client_credentials grant flow Here I am giving you exact sample for azure function. Just plug and play :))
Example Contains:
How Would you get token using client_credentials flow
Create User on Azure Active Directory tenant Azure Function
Access Token Class:
public class AccessTokenClass
{
public string token_type { get; set; }
public string expires_in { get; set; }
public string resource { get; set; }
public string scope { get; set; }
public string access_token { get; set; }
}
Azure Active Directory Create User Class:
public class AzureFunctionCreateUserClass
{
public bool accountEnabled { get; set; }
public string displayName { get; set; }
public string mailNickname { get; set; }
public string userPrincipalName { get; set; }
public PasswordProfile passwordProfile { get; set; }
}
Azure Active Directory User Password Profile Class:
public class PasswordProfile
{
public bool forceChangePasswordNextSignIn { get; set; }
public string password { get; set; }
}
Reference To Add:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;
Azure Function Body:
[FunctionName("FunctionCreateUserUsingRestAPI")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
try
{
log.LogInformation("C# HTTP trigger function processed a request.");
//Read Request Body
var content = await new StreamReader(req.Body).ReadToEndAsync();
//Extract Request Body and Parse To Class
AzureFunctionCreateUserClass objFuncRequestClass = JsonConvert.DeserializeObject<AzureFunctionCreateUserClass>(content);
// Variable For Validation message return
dynamic validationMessage;
// Validate param I am checking here. For Testing I am not taking from here But you can
if (string.IsNullOrEmpty(objFuncRequestClass.displayName))
{
validationMessage = new OkObjectResult("displayName is required!");
return (IActionResult)validationMessage;
}
if (string.IsNullOrEmpty(objFuncRequestClass.mailNickname))
{
validationMessage = new OkObjectResult("mailNicknameis required!");
return (IActionResult)validationMessage;
}
if (string.IsNullOrEmpty(objFuncRequestClass.userPrincipalName))
{
validationMessage = new OkObjectResult("userPrincipalName is required Format: UserName#YourTenant.onmicrosoft.com!");
return (IActionResult)validationMessage;
}
//Token Request Endpoint
string tokenUrl = $"https://login.microsoftonline.com/YourTenant.onmicrosoft.com/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "b603c7be-a866-Your_client_id-e6921e61f925",
["client_secret"] = "Vxf1SluKbgu4PF0N-client_Secret-SeZ8wL/Yp8ns4sc=",
["resource"] = "https://graph.microsoft.com"
});
dynamic json;
AccessTokenClass results = new AccessTokenClass();
HttpClient client = new HttpClient();
//Request For Token
var tokenResponse = await client.SendAsync(tokenRequest);
json = await tokenResponse.Content.ReadAsStringAsync();
//Extract Token Into class
results = JsonConvert.DeserializeObject<AccessTokenClass>(json);
var accessToken = results.access_token;
//Azure Ad Password profile object
PasswordProfile objPass = new PasswordProfile();
objPass.forceChangePasswordNextSignIn = true;
objPass.password = "yourNewUserPass";
//Azure AD user Object
AzureFunctionCreateUserClass objCreateUser = new AzureFunctionCreateUserClass();
objCreateUser.accountEnabled = true;
objCreateUser.displayName = "KironFromFucntion";
objCreateUser.mailNickname = "KironMailFromFunction";
objCreateUser.userPrincipalName = "UserName#YourTenant.onmicrosoft.com";
objCreateUser.passwordProfile = objPass;
//Convert class object to JSON
var jsonObj = JsonConvert.SerializeObject(objCreateUser);
var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
using (HttpClient clientNew = new HttpClient())
{
var postJsonContent = new StringContent(jsonObj, Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Post Rquest To Create User Rest Endpoint URL: https://graph.microsoft.com/v1.0/users
var rsponseFromApi= await client.PostAsync("https://graph.microsoft.com/v1.0/users", postJsonContent);
//Check Reqeust Is Successfull
if (rsponseFromApi.IsSuccessStatusCode)
{
var result_string = await responseFromApi.Content.ReadAsStringAsync();
dynamic responseResults = JsonConvert.DeserializeObject<dynamic>(result_string);
return new OkObjectResult(responseResults);
}
else
{
var result_string = await rsponseFromApi.Content.ReadAsStringAsync();
return new OkObjectResult(result_string);
}
}
}
catch (Exception ex)
{
return new OkObjectResult(ex.Message);
}
}
Request Format:
{
"accountEnabled": true,
"displayName": "displayName-value",
"mailNickname": "mailNickname-value",
"userPrincipalName": "upn-value#tenant-value.onmicrosoft.com",
"passwordProfile" : {
"forceChangePasswordNextSignIn": true,
"password": "password-value"
}
}
Check Newly Created User On Azure Portal:
Just to sure check your newly created user on Azure Portal All Users. See the screen shot below:
Point To Remember:
For Azure Active Directory Create users access make sure you have following permission:
User.ReadWrite.All
Permission Type: Application
You can check here. See the screen shot for better understanding: make sure you have clicked Grant admin consent for yourTenant after adding permission.
Note: This is how you can Create User on Azure Active Directory using Azure Function with Client_Credentials token flow token to a specific API endpoint efficiently.

How to pass Authorization token from one webapi to other webapi?

I have configured two applications in Azure AD. One is a Web API called app-A and another is a Web API called app-B.
how to I generate a token at app-A using client credentials token
and pass that token to app-B?
If I understand your question correct you want to forward Authorization token from one Web API service to another Web API?
This is how I did it:
Create a session context that exists within the request context. This is done by using Unity and HierarchicalLifetimeManager.
Extract all headers from the request at app-a and put it into the session context
Using the HttpClient to insert the cookies before calling app-b.
If you want to, you could also just extract the token only instead of all cookies.
SessionContext
public class SessionContext
{
public string Token { get; private set; }
public CookieHeaderValue Cookies { get; private set; }
public void SetToken(string token)
{
if(Token != null)
throw new InvalidOperationException("Token is already set in this session.");
Token = token;
}
public void SetCookies(CookieHeaderValue cookies)
{
if (Cookies != null)
throw new InvalidOperationException("Cookies is already set in this session.");
Cookies = cookies;
}
}
CookieFetcher
/// <summary> ActionFilter to extract all cookie and add it to the <see cref="SessionContext"/>. </summary>
public class CookieFetcherAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var cookies = actionContext.Request.Headers.GetCookies().SingleOrDefault();
if (cookies == null)
return;
var sessionContext = actionContext.Request.GetDependencyScope().GetService<SessionContext>();
sessionContext.SetCookies(cookies);
}
}
Unity config
// Gets a new TokenProvider per request
container.RegisterType<SessionContext>(new HierarchicalLifetimeManager());
Client
public class Client
{
private CookieHeaderValue Cookies => sessionContext.Cookies;
public Client(SessionContext sessionContext)
{
this.sessionContext = sessionContext;
}
private HttpClient CreateHttpClient()
{
// If cookie/sessionId based authentication is used.
if (Cookies != null)
{
handler.CookieContainer = ConvertToCookieContainer(Cookies, baseUri.GetRootHostName());
handler.UseCookies = true;
}
var client = new HttpClient(handler, true);
client.BaseAddress = baseUri;
return client;
}
private static CookieContainer ConvertToCookieContainer(CookieHeaderValue cookies, string cookiePath)
{
var container = new CookieContainer();
foreach (var cookie in cookies.Cookies)
{
container.Add(new Cookie(cookie.Name, cookie.Value, "/", cookiePath));
}
return container;
}
}

Using CustomCredentialsAuthProvider in JsonServiceClient

I try to implement my own custom CredentialsAuthProvider. The server seems to work fine with the following implementation:
public class MyCustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
if (userName == "testuser" && password == "1234")
{
return true;
}
else
{
return false;
}
}
public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
session.FirstName = "Testuser Joe Doe";
authService.SaveSession(session, SessionExpiry);
return null;
}
}
When I call on my Browser http://localhost:8088/auth/credentials?UserName=testuser&Password=1234 I get back a page containing a session ID and the testuser Joe Doe. Looks fine.
Now I try to call this from my Windows WPF client. I have created a Login Page and a LoginViewModel class since I implement the MVVM pattern. But I do not understand, what I really have to set the provider property in the Authenticate class to.
In my WPF class I have the following:
public partial class App : Application
{
public JsonServiceClient ServiceClient { get; private set; }
public App()
{
this.InitializeComponent();
}
// ....
}
And then in my LoginViewModel I have a Login() method which is a RelayCommand implementation of the login button like so (The form contains also a field where you have to enter the name of the application server since there is more than one. This is why I compose the baseUri in the handler):
private void Login()
{
var baseUri = $"http://{AppServer}:8088";
((App)Application.Current).InitServiceClient(baseUri);
var client = ((App) Application.Current).ServiceClient;
//var response = client.Send<AuthResponse>(new Auth { UserName = "Test", Password = "TestPassword" });
var authResponse = client.Post(new Authenticate
{
provider = CredentialsAuthProvider.Name, // <-- WHAT SHOULD THIS BE???
UserName = "testuser",
Password = "1234",
RememberMe = true,
});
// ....
}
CredentialsAuthProvider is unknown by the compiler. What do I need to pass here and what assemblies do I need? So far I have:
ServiceStack.Ckient
ServiceStack.Interfaces
ServiceStack.Text
MyService.ServiceModel //DLL containing the DTOs etc., NO implementations
What am I missing and doing wrong here?
CredentialsAuthProvider.Name just provides typed access to the "credentials" string literal, which you can use in its place, e.g:
var authResponse = client.Post(new Authenticate
{
provider = "credentials",
UserName = "testuser",
Password = "1234",
RememberMe = true,
});
You can find the list of Auth provider literals in the Authentication docs.

AuthUserSession is null inside ServiceStack service after successful auth

I have a self hosted service stack app in which I'm using Facebook Oath and Redis - the Facebook and redis side of things seem to be working ie. when I visit
abc.com/auth/facebook
The custom user session gets populated in OnAuthenticated method. The Redis cache has the data persisted correctly..so far so good
The problem Im having is understanding how to retrieve this CustomUserSession in a subsequent request. To begin with the oauth redirect page "/About-Us" is where I want to retrieve the session value however it is always null
[DataContract]
public class CustomUserSession : AuthUserSession
{
[DataMember]
public string CustomId { get; set; }
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
// receiving session id here and can retrieve from redis cache
}
public override bool IsAuthorized(string provider)
{
// when using the [Authenticate] attribute - this Id is always
// a fresh value and so doesn't exist in cache and cannot be auth'd
string sessionKey = SessionFeature.GetSessionKey(this.Id);
cacheClient = ServiceStackHost.Instance.TryResolve<ICacheClient>();
CustomUserSession session = cacheClient.Get<CustomUserSession>(sessionKey);
if (session == null)
{
return false;
}
return session.IsAuthenticated;
}
}
[DefaultView("AboutUs")]
public class AboutUsService : AppServiceBase
{
public object Get(AboutUsRequest request)
{
var sess = base.UserSession;
return new AboutUsResponse
{
//custom Id is always null??
Name = sess.CustomId
};
}
}
public abstract class AppServiceBase : Service
{
protected CustomUserSession UserSession
{
get
{
return base.SessionAs<CustomUserSession>();
}
}
}
How I register the cache & session etc.
AppConfig = new AppConfig(appSettings);
container.Register(AppConfig);
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager("10.1.1.10:6379"));
container.Register(c =>
c.Resolve<IRedisClientsManager>().GetCacheClient());
ConfigureAuth(container, appSettings);
the contents of ConfigureAuth()
var authFeature = new AuthFeature(
() => new CustomUserSession(),
new IAuthProvider[]
{
new FacebookAuthProvider(appSettings), // override of BasicAuthProvider
}
) {HtmlRedirect = null, IncludeAssignRoleServices = false};
Plugins.Add(authFeature);
I feel I'm missing something obvious here.... thanks in advance
To register to use a CustomUserSession for your typed Sessions, it needs to be specified when you register the AuthFeature, e.g:
Plugins.Add(new AuthFeature(() => new CustomUserSession(), ...));

Web API v2 SelfHost middleware security AuthenticateCoreAsync does not prevent access

I was following the instructions on http://leastprivilege.com/2013/11/11/client-certificate-authentication-middleware-for-katana/ but also followed Diminic's Pluralishight video on Web API security as I was trying to apply a client certificate authentication on my self hosted Web API v2 project.
I call the service from Advanced REST Client Chrome extension app, meaning it does not contain a client certificate in the request, and I see that cert == null but after that I still get a valid response from the server.
Is there something missing from this tutorial code?
public class ClientCertificateAuthenticationOptions : AuthenticationOptions
{
public X509CertificateValidator Validator { get; set; }
public bool CreateExtendedClaimSet { get; set; }
public ClientCertificateAuthenticationOptions() : base(“X.509″)
{
Validator = X509CertificateValidator.ChainTrust;
CreateExtendedClaimSet = false;
}
}
public class ClientCertificateAuthenticationHandler :
AuthenticationHandler<ClientCertificateAuthenticationOptions>
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var cert = Context.Get<X509Certificate2>(“ssl.ClientCertificate”);
if (cert == null)
{
return Task.FromResult<AuthenticationTicket>(null);
}
try
{
Options.Validator.Validate(cert);
}
catch
{
return Task.FromResult<AuthenticationTicket>(null);
}
var claims = GetClaimsFromCertificate(
cert, cert.Issuer, Options.CreateExtendedClaimSet);
var identity = new ClaimsIdentity(Options.AuthenticationType);
identity.AddClaims(claims);
var ticket = new AuthenticationTicket(
identity, new AuthenticationProperties());
return Task.FromResult<AuthenticationTicket>(ticket);
}
}
public class ClientCertificateAuthenticationMiddleware :
AuthenticationMiddleware<ClientCertificateAuthenticationOptions>
{
public ClientCertificateAuthenticationMiddleware(
OwinMiddleware next,
ClientCertificateAuthenticationOptions options)
: base(next, options)
{ }
protected override AuthenticationHandler<ClientCertificateAuthenticationOptions> CreateHandler()
{
return new ClientCertificateAuthenticationHandler();
}
}
app.UseClientCertificateAuthentication();
app.UseWebApi(WebApiConfig.Register());
Did you decorate your ApiController with the Authorize attribute?
[Authorize]
public class MyWebApiController : ApiController
{
}
Otherwise, the status code of your request will be 200 and not 401 even if the username/password do not match.

Resources