Custom Authentication on Asp.Net 4.5 with WIF - c#

I have an application set up with Azure ACS and .net 4.5 using claims. My application uses dropbox also. I was wondering if i could let users identify them self with dropbox alone.
I get a token from dropbox when the user logs in with dropbox and a unique id. Where in the .net pipe do i tell it that i have authenticated a user, such the principals are set on the next request also.
To make the example simple, lets say i have a form with two inputs. name,pass. If the name is 1234 and pass is 1234. then i would like to tell the asp.net pipeline that the user is authenticated. Is this possible? or do i need to create custom token handlers an such to integrate it into WIF?
Update
I found this: I would like comments on the solution, if there are security concerns i should be aware off.
var sam = FederatedAuthentication.SessionAuthenticationModule;
if (sam != null)
{
var cp = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> {new Claim("Provider","Dropbox")}, "OAuth"));
var transformer = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
if (transformer != null)
{
cp = transformer.Authenticate(String.Empty, cp);
}
var token = new SessionSecurityToken(cp);
sam.WriteSessionTokenToCookie(token);
}
All code:
public HttpResponseMessage get_reply_from_dropbox(string reply_from)
{
var response = this.Request.CreateResponse(HttpStatusCode.Redirect);
var q = this.Request.GetQueryNameValuePairs();
var uid = q.FirstOrDefault(k => k.Key == "uid");
if (!string.IsNullOrEmpty(uid.Value))
{
var sam = FederatedAuthentication.SessionAuthenticationModule;
if (sam != null)
{
var cp = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> {new Claim("Provider","Dropbox")}, "OAuth"));
var transformer = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
if (transformer != null)
{
cp = transformer.Authenticate(String.Empty, cp);
}
var token = new SessionSecurityToken(cp);
sam.WriteSessionTokenToCookie(token);
}
}
response.Headers.Location = new Uri(reply_from);
return response;
}
public async Task<string> get_request_token_url(string reply_to)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("OAuth",
string.Format("oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\", oauth_consumer_key=\"{0}\", oauth_signature=\"{1}&\"",
"<dropboxkey>","<dropboxsecret>"));
var data = await client.GetStringAsync("https://api.dropbox.com/1/oauth/request_token");
var pars = data.Split('&').ToDictionary(k=>k.Substring(0,k.IndexOf('=')),v=>v.Substring(v.IndexOf('=')+1));
return "https://www.dropbox.com/1/oauth/authorize?oauth_token=" + pars["oauth_token"]
+ "&oauth_callback=<MYSITE>/api/dropbox/get_reply_from_dropbox?reply_from=" + reply_to;
}
It works by the user request the authentication url, when the user authenticates my app it returns to get_reply_from_dropbox and logs in the user.
I offcause needs to handle some other stuff also, like what if the request do not come from dropbox.

I did this for my site using WIF 3.5 (not exactly the same) but it did use ACS+forms auth+OAuth all together, basically it uses form auth (which you can control completely) or use ACS/OAuth and link the accounts together or just use ACS/OAuth by itself.
You will have to handle logging off differently though.
http://garvincasimir.wordpress.com/2012/04/05/tutorial-mvc-application-using-azure-acs-and-forms-authentication-part-1/
DropBox uses OAuth, so I would go that route and then if you want to "link the accounts" create a user/password for forms auth linked to the DropBox Oauth account. The user doesn't necessarily have to know what auth conventions are being used. ASP.NET MVC 4 has the OAuth/forms auth built in the default project.

Related

How to use a custom SSO in my other apps?

I hit a wall trying to use a custom SSO in my apps so I figured I'd take a step back and ask questions here.
I implemented a RESTful Authentication API in .Net 5.0 (without Identity) by following this article.
I can call http://localhost:4001/api/auth/login with an email and a password to get a JWT in response
{
"token": "eyJhb<--removed jwt-->",
"errors": null
}
Is this a good approach? I didn't want to use something as bulky as Identity or as complicated as or OAuth.
How do I tie this up with a normal authentication "flow"? I know that you have to Challenge the identity provider while logging in, but I couldn't find any exemples on how to do it that would fit my situation. (Itried to implement a custom scheme with builder.AddJwtBearer("my_scheme", ... but this is where I hit a wall)
I managed to do what I wanted, so if anyone stumbles upon this:
Turns out that I was making this out to be super complicated when it's actually quite simple...
The point was to make things easy, so trying to create en authentication scheme was off topic.
Here is the final code:
using var client = new HttpClient() { BaseAddress = new Uri("http://localhost:4001/api/") };
try
{
var stringContent = new StringContent(JsonSerializer.Serialize(new { email = model.Email, password = model.Password }), Encoding.UTF8, "application/json");
var result = await client.PostAsync("auth/login", stringContent);
var jsonResponse = await result.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var authResult = JsonSerializer.Deserialize<AuthResult>(jsonResponse, options);
var jwt = authResult.Token.Jwt;
var isValidJwt = JwtHelper.ValidateCurrentToken(jwt, _myExternalAuthSettings.RsaPub, _myExternalAuthSettings.Issuer);
if (isValidJwt)
{
HttpContext.Response.Cookies.Append("my_auth", jwt, new CookieOptions()
{
Domain = "localhost",
Expires = DateTimeOffset.FromUnixTimeSeconds(authResult.Token.UnixTimeExpiresAt)
});
// finish authenticating the user
}
// something failed
}
Note: I followed this article to be able to locally validate the token with the public key (the private key is used to create it).
Cheers

How to use SSO with RingCentral C# SDK?

When using the RingCentral C# Client SDK, how can I use Single Sign-On (SSO) which we require for our Production environment? The SDK is working fine in the Sandbox environment without SSO.
I'm using authorization as follows per the documentation, but this only works for RingCentral password auth, not SSO.
await rc.Authorize("username", "extension", "password");
This is for both the current and older SDKs:
New: https://github.com/ringcentral/ringcentral-csharp-client
Old: https://github.com/ringcentral/ringcentral-csharp
Single Sign-On is only supported via the Authorization Code OAuth 2.0 grant flow which will present the user with an login window with a SSO button that will redirect the user to the SAML Identity Provider (IdP) website for SSO-based auth.
Demo code for how to accomplish this with the two C# SDKs are available here:
https://github.com/ringcentral/ringcentral-demos-oauth/tree/master/csharp-nancy
Here's an excerpt showing the two endpoints for the homepage and OAuth redirect URI:
public DefaultModule()
{
var authorizeUri = rc.AuthorizeUri(Config.Instance.RedirectUrl, MyState);
var template = File.ReadAllText("index.html");
Get["/"] = _ =>
{
var tokenJson = "";
var authData = rc.token;
if (rc.token != null && rc.token.access_token != null)
{
tokenJson = JsonConvert.SerializeObject(rc.token, Formatting.Indented);
}
var page = Engine.Razor.RunCompile(template, "templateKey", null,
new { authorize_uri = authorizeUri, redirect_uri = Config.Instance.RedirectUrl, token_json = tokenJson });
return page;
};
Get["/callback"] = _ =>
{
var authCode = Request.Query.code.Value;
rc.Authorize(authCode, Config.Instance.RedirectUrl);
return ""; // js will close this window and reload parent window
};

Web Application and API AzureAD authentication flow ASP.NET Core

I'm currently confused about how to realize the authentication / authorization flow.
I'm developing two applications, the one is the frontend/Webapplication and the other the backend/API, both with ASP.NET Core. The goal is to use the AzureAD and use the users/groups from the domain. The authentication I already implemented on both applications and I'm able to login and restrict content based on the login state.
As reference I took this example from a microsoft developer. There should be exactly this what I want to do. There is a WebApp and API. The used authentication flow is the authorization code flow. First the user needs to login and after that when some data needs to be requested from the API, an access token will be requested.
Question 1: Is this the right authentication flow? For me this seems like a doubled authentication, because first I authenticate myself at the frontend and when the Webapp needs some data I need to authenticate myself again at the backend. The same Azure AD tenant is used, so what do you think here?
The next point what seems very "ugly" is the procedure getting some data. In the example when some data is requested first the token will be requested and after this the data. But in my opinion with a lot of boilerplate. The example code below is needed for just one request of all ToDo items.
// GET: /<controller>/
public async Task<IActionResult> Index()
{
AuthenticationResult result = null;
List<TodoItem> itemList = new List<TodoItem>();
try
{
// Because we signed-in already in the WebApp, the userObjectId is know
string userObjectID = (User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
// Using ADAL.Net, get a bearer token to access the TodoListService
AuthenticationContext authContext = new AuthenticationContext(AzureAdOptions.Settings.Authority, new NaiveSessionCache(userObjectID, HttpContext.Session));
ClientCredential credential = new ClientCredential(AzureAdOptions.Settings.ClientId, AzureAdOptions.Settings.ClientSecret);
result = await authContext.AcquireTokenSilentAsync(AzureAdOptions.Settings.TodoListResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// Retrieve the user's To Do List.
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AzureAdOptions.Settings.TodoListBaseAddress + "/api/todolist");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
// Return the To Do List in the view.
if (response.IsSuccessStatusCode)
{
List<Dictionary<String, String>> responseElements = new List<Dictionary<String, String>>();
JsonSerializerSettings settings = new JsonSerializerSettings();
String responseString = await response.Content.ReadAsStringAsync();
responseElements = JsonConvert.DeserializeObject<List<Dictionary<String, String>>>(responseString, settings);
foreach (Dictionary<String, String> responseElement in responseElements)
{
TodoItem newItem = new TodoItem();
newItem.Title = responseElement["title"];
newItem.Owner = responseElement["owner"];
itemList.Add(newItem);
}
return View(itemList);
}
//
// If the call failed with access denied, then drop the current access token from the cache,
// and show the user an error indicating they might need to sign-in again.
//
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
return ProcessUnauthorized(itemList, authContext);
}
}
catch (Exception)
{
if (HttpContext.Request.Query["reauth"] == "True")
{
//
// Send an OpenID Connect sign-in request to get a new set of tokens.
// If the user still has a valid session with Azure AD, they will not be prompted for their credentials.
// The OpenID Connect middleware will return to this controller after the sign-in response has been handled.
//
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme);
}
//
// The user needs to re-authorize. Show them a message to that effect.
//
TodoItem newItem = new TodoItem();
newItem.Title = "(Sign-in required to view to do list.)";
itemList.Add(newItem);
ViewBag.ErrorMessage = "AuthorizationRequired";
return View(itemList);
}
//
// If the call failed for any other reason, show the user an error.
//
return View("Error");
}
Question 2: Is there a "less ugly" approach to access the data if the flow in Q1 is right?
I found a proper solution to solve this.
I just used the approach from this example here multitenant-saas-guidance and it works like charm.

How to implement refresh token workflow into OAUTH workflow in MVC C# app?

I am new to OAUTH. I have been working on implementing OAUTH into my MVC c# application to access ping federate. After much research, and failed attempt at using the ping federate nuget, I came across this link that finally gave some clarity to the full process with a coding example. I have came across much generic examples of the endpoints i need to access but never a full workflow coding example. After implementing that code with some changes and was successful at signing in the ping user into my MVC app, I started doing more research about the refresh token. Questions...
Q. I know how to access a a refresh token, meaning I know which endpoint used to refresh the access token after I have authenticated the user in ping federate. But what is the refresh token used for? Is it used to extend my application's session once it ends? Or it used for if the user signs out of my application then they click the 'Sign in with Ping Federate' link on the login and not have them authenticate again as long as the refresh token is still valid?
Q. And if the refresh token is used for when after a user authenticates the first time, and I save the refresh token in the db and then user signs back using that 'Sign in with Ping Federate' link on my login back how can I know what user that is to lookup the refresh token in the db to give them access to my site without re-authenticating them with ping federate? Since when they come to that link 'Sign in with Ping Federate' I do not know who they are?
This is the below code that I am using, from user MatthiasRamp in the link i provided...I want to add my refresh token logic with the below code.
public async Task<ActionResult> Login(string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl) && Request.UrlReferrer != null)
returnUrl = Server.UrlEncode(Request.UrlReferrer.PathAndQuery);
if (Url.IsLocalUrl(returnUrl) && !string.IsNullOrEmpty(returnUrl))
_returnUrl = returnUrl;
//callback function
_redirectUrl = Url.Action("AuthorizationCodeCallback", "ExternalLogin", null, Request.Url.Scheme);
Dictionary<string, string> authorizeArgs = null;
authorizeArgs = new Dictionary<string, string>
{
{"client_id", "0123456789"}
,{"response_type", "code"}
,{"scope", "read"}
,{"redirect_uri", _redirectUrl}
// optional: state
};
var content = new FormUrlEncodedContent(authorizeArgs);
var contentAsString = await content.ReadAsStringAsync();
return Redirect("http://localhost:64426/oauth/authorize?" + contentAsString);}
public async Task<ActionResult> AuthorizationCodeCallback()
{
// received authorization code from authorization server
string[] codes = Request.Params.GetValues("code");
var authorizationCode = "";
if (codes.Length > 0)
authorizationCode = codes[0];
// exchange authorization code at authorization server for an access and refresh token
Dictionary<string, string> post = null;
post = new Dictionary<string, string>
{
{"client_id", "0123456789"}
,{"client_secret", "ClientSecret"}
,{"grant_type", "authorization_code"}
,{"code", authorizationCode}
,{"redirect_uri", _redirectUrl}
};
var client = new HttpClient();
var postContent = new FormUrlEncodedContent(post);
var response = await client.PostAsync("http://localhost:64426/token", postContent);
var content = await response.Content.ReadAsStringAsync();
// received tokens from authorization server
var json = JObject.Parse(content);
_accessToken = json["access_token"].ToString();
_authorizationScheme = json["token_type"].ToString();
_expiresIn = json["expires_in"].ToString();
if (json["refresh_token"] != null)
_refreshToken = json["refresh_token"].ToString();
//SignIn with Token, SignOut and create new identity for SignIn
Request.Headers.Add("Authorization", _authorizationScheme + " " + _accessToken);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
ctx.Authentication.SignOut(DefaultAuthenticationTypes.ExternalBearer);
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);
var ctxUser = ctx.Authentication.User;
var user = Request.RequestContext.HttpContext.User;
//redirect back to the view which required authentication
string decodedUrl = "";
if (!string.IsNullOrEmpty(_returnUrl))
decodedUrl = Server.UrlDecode(_returnUrl);
if (Url.IsLocalUrl(decodedUrl))
return Redirect(decodedUrl);
else
return RedirectToAction("Index", "Home");
}

Unable to authenticate Google Calendar API v3 programmatically?

Perhaps I am the only one that thinks Google's API documentation is awful but I've spent more time on this simple task than I wanted.
Currently my project is using a GDATA implementation to connect with the Google Calendar API v2. I followed this guide: http://www.codeproject.com/Articles/565032/Google-Calendar-Integration-in-ASP-NET-Create-ed
But I noticed that Google is deprecating version 2 of their API this fall. I am trying to figure out how I can connect to their version 3 API which appears to be using OAuth2.
After reading their documentation and searching the internet >:( - The problem I keep running into is EVERY sample, tutorial or youtube video I've come across that shows how to implement this involve the Google consent screen where the user clicks "Accept".
I've tried doing the following but honestly not sure if it's even the right direction?
// Register the authenticator. The Client ID and secret have to be copied from the API Access
// tab on the Google APIs Console.
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = "MY_CLIENT_ID";
provider.ClientSecret = "MY_CLIENT_SECRET";
// Create the service. This will automatically call the previously registered authenticator.
var service = new CalendarService();
My application doesn't need the user's account/consent (OAuth), I need to connect like I am currently in my code-behind.
So the question is how do I "upgrade" my current implementation to v3? Do I use OAuth, Service Account? I've found plenty of examples showing the v3 usages for how to retrieve events and insert them... but they all authenticate with a user consent screen on the front end.
Here is my current GData implementation...
public class GoogleGateway : IGoogleGateway
{
private readonly IRepository<UserSetting> _settingsRepository;
private Service _googleService;
private CalendarService _googleCalendar;
private Uri _calendarUri;
public GoogleGateway(IRepository<UserSetting> settingsRepository)
{
_settingsRepository = settingsRepository;
}
public IEnumerable<EventEntry> GetAllEvents(DateTime? startDate)
{
if (!Connect()) return new List<EventEntry>();
// Create the query object:
EventQuery query = new EventQuery();
query.Uri = _calendarUri;
if (startDate != null)
query.StartTime = startDate.Value;
// Tell the service to query:
EventFeed calFeed = _googleCalendar.Query(query);
return calFeed.Entries.Cast<EventEntry>();
}
public bool Connect()
{
var calSettings = _settingsRepository.Get().Where(x => x.Setting == "Calendar");
if (calSettings.Any())
{
var username = calSettings.First(x => x.Meta == "GoogleUsername").Value;
var password = calSettings.First(x => x.Meta == "GooglePassword").Value;
var calendarUri = new Uri(calSettings.First(x => x.Meta == "CalendarFeed").Value);
var applicationName = calSettings.First(x => x.Meta == "ApplicationName").Value;
_calendarUri = calendarUri;
//FeedQuery feedQuery = new FeedQuery();
_googleService = new Service("cl", applicationName);
_googleCalendar = new CalendarService(applicationName);
// Set your credentials:
_googleService.setUserCredentials(username, password);
_googleCalendar.setUserCredentials(username, password);
return true;
}
return false;
}
public void AddEvent(string title, string contents, string location, DateTime startTime, DateTime endTime)
{
if (!Connect()) return;
EventEntry.EVENT_CATEGORY = new AtomCategory("Appointments");
EventEntry entry = new EventEntry
{
Title = { Text = title },
Content = { Content = contents },
};
// Set the title and content of the entry.
// Set a location for the event.
Where eventLocation = new Where();
eventLocation.ValueString = location;
entry.Locations.Add(eventLocation);
When eventTime = new When(startTime, endTime);
entry.Times.Add(eventTime);
Uri postUri = new Uri("http://www.google.com/calendar/feeds/default/private/full");
// Send the request and receive the response:
AtomEntry insertedEntry = _googleCalendar.Insert(postUri, entry);
}
public void DeleteEvent(string eventId)
{
if (!Connect()) return;
var events = GetAllEvents(null);
var appointment = events.First(x => x.EventId == eventId);
_googleService.Delete(appointment);
}
}
I'm growing desperate at this point, any help would be very appreciated. Include your twitter handle in your answer and I'll buy you a coffee!
UPDATED
I currently have the following, but I is still not authenticating... :(
static CalendarService BuildService()
{
String serviceAccountEmail = "xxxxxxxxxxxxx-31xxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com";
var certPath = HttpContext.Current.Server.MapPath("/xxxxxxxxxxxx.p12");
var certificate = new X509Certificate2(certPath, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { CalendarService.Scope.Calendar }
}.FromCertificate(certificate));
// Create the service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential, <<<<<< DOES NOT RESOLVE!
ApplicationName = "MyApplication",
});
var test = service.Calendars.Get("xxxxxxxxxxxxxxxxxx#group.calendar.google.com");
return service;
}
The problem is that you are storing credentials in plaintext. In Oauth2 the users won't give you their credentials (thus access to everything) but instead they enable your app to access the data of a specific type / scope.
It's not clear from your description whether you only ever access one calendar fully in your control or you have multiple users. In the first case the answer would be use service account (https://developers.google.com/accounts/docs/OAuth2ServiceAccount). In the second case if you are a calendar app with many users, you will need to go down the user consent road and you should read on :)
For offline access you can specify that the access_type should be offline when retrieving the credentials for the first time. Together with the access token you'll also get a refresh token, which you can use to re-authenticate at any later time without any more user clicks (https://developers.google.com/accounts/docs/OAuth2WebServer#refresh). However, at least one consent screen it is.

Categories