AWS Cognito, Server-side Custom Auth with C# - c#

I am trying to get a CUSTOM_AUTH flow with AWS Cognito in the following setup:
Angular TS client app
.NET 3.1 WebAPI
Login is handled serverside, UserName + Password check works correctly. Business requires to add email based MFA.
I struggle to understand how to implement CUSTOM_AUTH flow from Server-side. All samples and documentation are restricted to Client flow, which includes SRP values.
The desired flow would be:
User enters username + password
API send them to Cognito, which triggers the CUSTOM AUTH Flow
Custom flow triggers, code being generated and an email sent out to the user email address with a generated code
Client app renders an input field for the code generated
user submits the code received via email
API calls Cognito with the generated code
Cognito confirms if the provided code match the generated one and generates the AuthToken to be used
The current login flow looks as:
user inputs username and password in the Angular App
inputs are sent to the backend API
Backend Initiates the authentication flow with Cognito through SDK
public async Task<AdminInitiateAuthResponse> AuthenticateUser(string username, string password) {
var authRequest = new AdminInitiateAuthRequest
{
UserPoolId = this._poolId,
ClientId = this._clientId,
AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH
};
authRequest.AuthParameters.Add("USERNAME", username);
authRequest.AuthParameters.Add("PASSWORD", password);
return await this._client.AdminInitiateAuthAsync(authRequest);
}
Cognito validates the username and password. From the return value we generate a token.
public CognitoUserDto CreateUserToken(AuthenticationResultType authRes) {
var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
var awsIdToken = tokenHandler.ReadJwtToken(authRes.IdToken);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = creds
};
return new CognitoUserDto
{
Email = awsIdToken.Claims.Single(e => e.Type == "email").Value,
Token = CreateToken(awsIdToken, authRes.AccessToken)
};
}
public string CreateToken(JwtSecurityToken awsToken, string accessToken) {
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Email, awsToken.Claims.Single(e => e.Type == "email").Value),
new Claim("accessToken", accessToken)
};
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = creds
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
As for Custom Auth mode with email the sample here was followed (even though its an app flow). Since I failed to find samples/documentation with server side custom auth flow attempts are pretty much restricted to trial and error.
If possible I would like to avoid the need to refactor the whole process to client side flow.
What has been done so far:
Replaced AuthFlow in AuthenticateUser method to AuthFlowType.CUSTOM_AUTH
User Pool signin experience is set to Optional MFA
Created the lambda triggers in AWS Cognito
Issues faced:
keeping the ADMIN_NO_SRP_AUTH mode does not trigger the CUSTOM AUTH flow. (somewhat expected)
changing the AuthFlow to CUSTOM_AUTH throws an exception. First sentence in AWS Docs state here states "The request for this Lambda trigger contains session", yet the Lambda expression stops with an exception that session is null. Yet, this may be right, since according to step 1 one shall include value for SRP_A, which is kinda obscure how, given no SRP is available.
reworked the defaultAuthChallenge Lambda expression to handle if session is null. In this case Lambda was executed without exception, however challengeName and session are not returned, so kinda blocked how to call AdminRespondToAuthChallengeAsync() method
public async Task<AdminRespondToAuthChallengeResponse> RespondToCognitoChallenge(string token, string session) {
return await this._client.AdminRespondToAuthChallengeAsync(new AdminRespondToAuthChallengeRequest() {
ChallengeName = ChallengeNameType.CUSTOM_CHALLENGE,
CientId = this._clientId,
UserPoolId = this._poolId,
ChallengeResponses = new Dicitonary<string, string> {{ "token", token }},
Session = session
});
}
The reworked defaultAuthChallenge Lambda look like:
export const handler = async (event) => {
if (event.request?.session === null || event.request.session.length === 0) {
//SRP_A is the first challenge, this will be implemented by cognito. Set next challenge as PASSWORD_VERIFIER.
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'PASSWORD_VERIFIER';
} else if (event.request.session && event.request.session.length === 1
&& event.request.session[1].challengeName === 'PASSWORD_VERIFIER'
&& event.request.session[1].challengeResult === true) {
//If password verification is successful then set next challenge as CUSTOM_CHALLENGE.
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
} else if (event.request.session && event.request.session.length >= 5
&& event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE'
&& event.request.session.slice(-1)[0].challengeResult === false) {
//The user has exhausted 3 attempts to enter correct otp.
event.response.issueTokens = false;
event.response.failAuthentication = true;
} else if (event.request.session && event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE'
&& event.request.session.slice(-1)[0].challengeResult === true) {
//User entered the correct OTP. Issue tokens.
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
//user did not provide a correct answer yet.
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
}
return event;
};
Your kind help is apprecaited!

Related

How to read email from outlook account with oauth2 and no user interaction

I working on a console app that must logon to a outlook365 email account and then get the attachments in emails.
I currently have the code I found on ExchangeOAuth2.
When I get to the part the token has to be retrieved, it opens a website and I have to select an account to be able to continue.
How can I prevent this. There is no user interaction.
My code:
string? host = Configuration.GetSection("email").GetValue<string>("host");
int? port = Configuration.GetSection("email").GetValue<int>("port");
string? clientId = Configuration.GetSection("email").GetValue<string>("clientId");
string? tenantId = Configuration.GetSection("email").GetValue<string>("tenantId");
if (string.IsNullOrEmpty(host) || port == null || string.IsNullOrEmpty(clientId) ||
string.IsNullOrEmpty(tenantId))
{
return;
}
var options = new PublicClientApplicationOptions
{
ClientId = clientId,
TenantId = tenantId,
RedirectUri = "http://localhost"
};
var publicClientApplication = PublicClientApplicationBuilder
.CreateWithApplicationOptions(options)
.Build();
var scopes = new string[] {
"email",
"offline_access",
"https://outlook.office.com/IMAP.AccessAsUser.All" // Only needed for IMAP
};
var authToken = await
publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync();
var oauth2 = new SaslMechanismOAuth2(authToken.Account.Username,
authToken.AccessToken);
[update]
I have changed my code to use microsoft graph.
I used this tutorial
But this is still asking me to go to a website en fill in the code.
That is exactly what I'm not looking for.
There is no user interaction, the console must run on it's own.
Why is this so hard?
Does anyone have an example on how to do this?
I have figured it out.
In the azure envronment for the email account, some API permissions had to be set.

is there a way to associate a spotify access token to a ASP.NET identity user?

I'm working on a multilanguage project for accademic purpose. I've written a simple Python Client that make requests to an API server written in ASP.NET. The server retrives spotify info about users. The server interacts with a DB filled by a Golang server that only makes scraping on API's exposed from Spotify. I'm aware that it's a misuse and there are better solutions
Clearly, Golang server, in order to make requests to Spotify API's, needs to know the access token returned from spotify Authorization Code Flow. Overlooking about spotify token expire time, the idea is: after user authentication through Identity module of ASP.NET server (using JWT token), associate the access token obtained calling https://accounts.spotify.com/api/token to user's informations. So, i expose an API in ASP.NET server like this
[AllowAnonymous]
[HttpPost("token")]
public async Task<ContentResult> getTokenAsync(string? code = null)
{
//to retrive information about who is the user that making call -> need later for associate spotifytoken
string accessToken = Request.Headers[HeaderNames.Authorization].ToString().Replace("Bearer ", "");
JwtSecurityTokenHandler t = new JwtSecurityTokenHandler();
var token = t.ReadJwtToken(accessToken);
var user = _userManager.FindByIdAsync(token.Subject).Result;
string s = "https://accounts.spotify.com/api/token";
if (code == null)
{
var qb = new QueryBuilder();
qb.Add("response_type", "code");
qb.Add("client_id", _config["SpotiSetting:clientId"]);
qb.Add("scope", "user-read-private user-read-email user-library-read");
qb.Add("redirect_uri", _config["SpotiSetting:redirectUser"]);
qb.Add("show_dialog", "true");
return new ContentResult
{
ContentType = "text/html",
Content = "https://accounts.spotify.com/authorize/" + qb.ToQueryString().ToString()
//Content = JsonConvert.SerializeObject(user.Result)
};
} else
{
//if i'm here, api is the callback designed for spotify
var qb = new QueryBuilder();
qb.Add("grant_type", "authorization_code");
qb.Add("code", code);
qb.Add("redirect_uri", "https://localhost:44345/spotify/token");
var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Post, s);
req.Content = new FormUrlEncodedContent(qb);
req.Headers.Authorization = new AuthenticationHeaderValue("Basic", "here_my_secret_encoded_CLIENTID:CLIENT_SECRET");
var response = await client.SendAsync(req);
var result = response.Content.ReadAsStringAsync().Result;
AccessToken json = JsonConvert.DeserializeObject<AccessToken>(result);
user.spotifyInformation.authToken = code;
user.spotifyInformation.accessToken = json;
var res = _userManager.UpdateAsync(user);
if (res.IsCompletedSuccessfully)
{
return Content("ok");
}
else
{
Content("Problem");
}
} return Content("");
}
The problem is that the second time that API is invoked, it's spotify that is sending the first authorization token (needed to request access_token), so I lost user information retrived in the first request. Should be better write two distinct API and separate callback from user request?
It's my first question here, so please to have mercy

ASP.Net Core - Turn SAML assertion into ClaimsPrincipal

There's a question about using SAML in ASP.Net Core, but I need additional help.
The only answer there mentions Kentor.AuthServices, but I don't understand how to use it. Everything I find on this or other SAML libraries, the documentation, blog posts, and sample applications are all about contacting some external authentication service and handling login and logout.
But I don't need any of that. The setup I'm working with does that in an edge-facing firewall application, and login/logout requests never reach my application. All I get is a SAML token in a cookie, which I need to validate and turn into a ClaimsPrincipal. I can't (the deployment network setup is insanely paranoid) and don't want to contact any identity provider.
Currently I've written a piece of middleware that takes the cookie, parses it, and parses out the parts I need for the claims principal. But I don't do any validation, either of the XML signature or of the SAML validity (valid time attributes etc). With .Net Core 2.0 Preview 2 I can do the XML signature validation, but I'm still stuck on doing the SAML validation. Is there a library that simply validates SAML constraints and does nothing else (or, at least, where I can ignore everything else)? I believe Kentor or ITfoxtec or elerch's SAML2.Core must contain such functionality, but I can't figure out where it is.
I have done this with SecurityTokenHandlerCollection class in System.IdentityModel.Tokens
I hope this code will help you.
public Saml2SecurityToken DeserializeSAMLResponse(string samlResponse)
{
//Deserializing saml response
Saml2SecurityToken token;
using (var reader = XmlReader.Create(new StringReader(samlResponse)))
{
reader.ReadToFollowing("Assertion", Infrastructure.Enumerations.StringEnum.GetStringValue(SAMLProtocoles.SAML_20_ASSERTION));
// Deserialize the token so that data can be taken from it and plugged into the RSTR
SecurityTokenHandlerCollection tokenHandlerCollection = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
token = (Saml2SecurityToken)tokenHandlerCollection.ReadToken(reader.ReadSubtree());
}
//Deserializing successful
return token;
}
It will internally validate the SAML and parse it in Saml2SecurityToken
After you get the token you can the the Users Credentials like this
public User ReadSamlResponse(string samlResponse, string profileName, bool isSAMLProfile = true)
{
User User = new User();
var DecodedSamlResponse = Convert.FromBase64String(samlResponse);
string ResponseDecoded = coding.UTF8.GetString(DecodedSamlResponse);
Saml2SecurityToken Token = _samlAuthenticationService.DeserializeSAMLResponse(ResponseDecoded);
if ()// apply condition here if you need to validate signature
{
if (!_samlAuthenticationService.ValidateSamlToken(ResponseDecoded, AuthenticationConnector, isSAMLProfile))
throw new Exception("Signature is invalid");
}
User = GetUserFromToken(Token);
return User;
}
And to get User for Security Token you can do this
public User GetUserFromToken(Saml2SecurityToken Token)
{
//Get user information from the token started
User User = new User();
if (Token != null)
{
if (Token.Assertion.Subject.NameId != null && (Token.Assertion.Subject.NameId.Format == null || Token.Assertion.Subject.NameId.Format.OriginalString == "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"))
User.EmailAddress = Token.Assertion.Subject.NameId.Value;
foreach (var Statement in Token.Assertion.Statements)
{
var AttributeStatement = Statement as Saml2AttributeStatement;
var AuthenticationStatement = Statement as Saml2AuthenticationStatement;
if (AttributeStatement != null)
foreach (var Saml2Attribute in AttributeStatement.Attributes)
{
if (Saml2Attribute.Name.Equals("mail") || Saml2Attribute.Name.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"))
User.EmailAddress = Saml2Attribute.Values[0];
if (Saml2Attribute.Name.Equals("uid") || Saml2Attribute.Name.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"))
User.Name = Saml2Attribute.Values[0];
if (Saml2Attribute.Name.Equals("phone"))
User.MobileNumber = Saml2Attribute.Values[0];
if (Saml2Attribute.Name.Equals("title"))
User.JobTitle = Saml2Attribute.Values[0];
if (Saml2Attribute.Name.Equals("company"))
User.CompanyName = Saml2Attribute.Values[0];
}
if (AuthenticationStatement != null)
{
User.SAMLSessionIndex = AuthenticationStatement.SessionIndex;
}
}
}
//Successfully parsed user credentials
return User;
}
http://blog.scottlogic.com/2015/11/19/oauth2-with-saml2.html
This blog of scott has explained it in simple way.

Firebase Authenticate Simple Login

TL;DR : Is there any way to use the auth=CREDENTIALS with the Simple Login (Email/Password) in Firebase?
I am trying to connect my C# Application's users to my Firebase. I could set up pretty much all calls using my Secret Token, but now I need to be able to, at least, get the current user UID so I know where the data should be sent to.
The way I went with my PUSH, PUT, GET request was something like this, using my secret token as login:
var authToken = "SECRET";
url = "https://MyLocation.firebaseio.com/" + url + ".json?auth=" + authToken;
return WebRequest.Create(url);
But now I'd like to get something supporting the Email/Password simple login, something like this:
var authToken = "{email:an#email.com, password:thePassword}";
url = "https://MyLocation.firebaseio.com/" + url + ".json?auth=" + authToken;
return WebRequest.Create(url);
My tries using CURL weren't successful... Maybe there's no way to do that? or any suggestions?
Thanks for the help!
I spoke with the support at Firebase and found a temporary solution, and a real solution.
Real solution: Manage the user and their password manually in all environments, using Firebase as "Database". That was basically what I was trying to do with my question. That resolve in using Firebase custom auth.
Temporary solution: (And what I did as I do not need as much security as the real solution offers)
Get something that identify the current user. Here I can get the current user email without even asking him.
Base64 the identifier:
byte[] result = System.Text.Encoding.UTF8.GetBytes(email);
email = Convert.ToBase64String(result);
Put, push, patch the required information via REST to firebaseio.com/Base64
In the user interface, that uses JavaScript, do the same process to read/write data at the user, using something like base64.min.js
var ref = new Firebase("https://aFirebase.firebaseio.com");
//Things happen
...
//We register a user
function createUser(email, password){
//Allows us to create a user within firebase
ref.createUser({
email : email,
password : password
}, function(error, userData){
if (error) {
//The creation of the user failed
alert(error);
} else {
//The creation of the user succeeded
console.log("Successfully created user account with uid:", userData.uid);
//We make sure we are at the correct position in our firebase
ref = ref.root().child(base64.encode(email));
//We check if the child exist
if(ref == ref.root()){
//The child doesn't exist
//We have to create it
user = {};
//Set the child with a value for the UID, that will fit with the rules
user[base64.encode(email)] = {uid:userData.uid};
//We set the new child with his value in firebase
ref.set(user);
}else{
//The child exist, we can update his information to go accordingly with our rules
ref.update({uid:userData.uid});
}
//Who wants to register and then not be logged in?
//We can add something upon login if his email is not validated...
login(email, password);
}
}
);
}
Now we have to update our rules in Firebase:
{
"rules": {
"$uid":{
".read":"!(data.child('uid').exists() == true) || data.child('uid').val() == auth.uid",
".write":"!(data.child('uid').exists() == true) || data.child('uid').val() == auth.uid"
}
}
}
With this, the application is somehow secure (as long as the user use the C# application and the JS application, where the rules will be set).
In case of a WebApi application a JWT token could be used along with OWIN pipeline.
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { FirebaseValidAudience },
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = OnValidateIdentity
},
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeys = issuerSigningKeys,
ValidAudience = FirebaseValidAudience,
ValidIssuer = FirebaseValidIssuer,
IssuerSigningKeyResolver = (arbitrarily, declaring, these, parameters) => issuerSigningKeys
}
});
Here is the sample of Firebase ASP.NET WebApi Authentication application: https://github.com/PavelDumin/firebase-webapi-auth

Posting to FB Page error(s)

Scenario: I want to get user access token of the fb page admin by JS login and retrieving token ONE TIME, and will store that in database. Then daily, I want to do wall post to those page.
I am using JS to get the initial token and storing it. Then using c# FacebookSDK for the web requests.
FB.login(function (response) {
var r = response;
// get access token of the user and update in database
$("#FacebookAccessToken").val(response.authResponse.accessToken);
},
{
scope: 'manage_pages,publish_stream'
});
Now I am saving this token in database as I will be using this for future graph calls - is this right?
On server side when I need to do a post after a day I retrieve that token and do the processing as below:
// step 1 get application access token
var fb1 = new FacebookClient();
dynamic appTokenCLient = fb1.Get("oauth/access_token", new
{
client_id = appId,
client_secret = appSecret,
grant_type = "client_credentials",
scope = "manage_pages,publish_stream",
redirect_uri = siteUrl
});
var fbTokenSettingVal = GetTokenFromDB(); // getting access token from database which was stored during JS fb login
// step 2 extend token
var fb2 = new FacebookClient(appTokenCLient.access_token);
dynamic extendedToken = fb2.Get("oauth/access_token", new
{
client_id = appId,
client_secret = appSecret,
grant_type = "fb_exchange_token",
fb_exchange_token = fbTokenSettingVal.Val
});
var userExtendedToken = extendedToken.access_token; // get extended token and update database
// step 3 get page access token from the pages, that the user manages
var fb3 = new FacebookClient { AppId = appId, AppSecret = appSecret, AccessToken = userExtendedToken };
var fbParams = new Dictionary<string, object>();
var publishedResponse = fb3.Get("/me/accounts", fbParams) as JsonObject;
var data = JArray.Parse(publishedResponse["data"].ToString());
var pageToken = "";
foreach (var account in data)
{
if (account["name"].ToString().ToLower().Equals("PAGE_NAME"))
{
pageToken = account["access_token"].ToString();
break;
}
}
// step 4 post a link to the page - throws error !
var fb4 = new FacebookClient(pageToken);
fb4.Post("/PAGE_NAME/feed",
new
{
link = "http://www.stackoverflow.com"
});
The last 4th step throws error, when posting to selected page:
The user hasn't authorized the application to perform this action
Have tried several different ways, but in vain. Assume that there is just a simple step which I am doing wrong here.
Also, is it ok to extend the fb access token for user every time before making request?
Any way to check if token is expired or not?
If you want to use that access token for future. You need to take offline_access token and that token you need to store in database.
This offline accesstoken will be expire once user will change the password or delete your application from facebook account.
private void GetPermenentAccessTokenOfUser(string accessToken)
{
var client2 = new FacebookClient(accessToken);
//get permenent access token
dynamic result = client2.Post("oauth/access_token", new
{
client_id = _apiKey,
client_secret = _apiSecret,
grant_type = "fb_exchange_token",
fb_exchange_token = accessToken
});
_accessToken = result.access_token;
}
Looks like for new apps we need to apply for manage_pages permission from our application:
which I am doing now. As it shows error when doing login:
Also, the app needs to be live, so they can reproduce this permission and verify that we do need this permission to post to pages. Its good for fb users safety but a time taking process for developers.
Any way this can be skipped for testing purpose?

Categories

Resources