Azure AD/Token/Certificates/SQL - I'm lost - vb.net

I started a company 3 months ago and there is no software (CRM+other business tasks) that meets my needs. Reluctantly I decided to write my own 2 months ago and it is now 80% complete! I've been pointed here to Stack Overflow throughout my journey many times and you guys have been a great help -- first and foremost thank you! I've been able to stay quiet until now...
The problem:
I have connection strings all over the place with SQL Authentication username/password exposed. My business partner is across the country so I need to give him access and open a port etc. I've made the move to Microsoft Azure (all setup with a VPN gateway, VM's are running and linked with Azure AD) to lockdown the environment a bit. I'd like to take full advantage of what it offers and use Active Directory for authentication on login (MFA) and then use the token in my query strings removing the need for a hard coded connection string with username/passwords...
I've done a lot of research and it seems I can accomplish what I want but I can't quite figure it out.
App is written in VB.net and most Microsoft docs on the topic are in C#, requiring edits to the app.xaml.cs file which doesn't exist in vb.net, and even if I got around that piece i'm not sure how to configure the sql connection strings to use the token.
Once I get this out of the way I can get back to finishing the software so I can get on the phone and help us take off before the money runs out! Your help is greatly appreciated!
EDIT :
I'm going this route because from what I've found, I can't use Authentication = Active Directory Integrated in my sql string because my AD is not federated (no on-premise ADFS), Active Directory Password still requires the sql admin setup in Azure to be in the sql string. I looked at key vaults which while doing research led me to registering my app which seems to be exactly what I want and where my questions is derived. Microsoft provides a great example downloadable project but again its in C#...If there is a recommendation on a different option i'm all ears...
EDIT 9/3/19 -- A lot of circling and research...I need to convert this code from c# to vb.net. More information on what the below is and how it is broken down from its creator : https://colinsalmcorner.com/post/configuring-aad-authentication-to-azure-sql-databases
public async Task<string> GetAccessTokenAsync(string clientId, string clientSecret, string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority, TokenCache.DefaultShared);
var clientCred = new ClientCredential(clientId, clientSecret); << CAN I MAKE THESE (ClientId, ClientSecret) TIED TO TEXTBOXES FOR USERNAME/PASSWORD ON MY APPLICATION?
var result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
{
throw new InvalidOperationException("Could not get token");
}
return result.AccessToken; <<WHAT DOES RETURN DO HERE?
}
AND
public async Task<SqlConnection> GetSqlConnectionAsync(string tenantId, string clientId, string clientSecret, string dbServer, string dbName)
{
var authority = string.Format("https://login.windows.net/{0}", tenantId);
var resource = "https://database.windows.net/";
var scope = "";
var token = await GetTokenAsync(clientId, clientSecret, authority, resource, scope);
var builder = new SqlConnectionStringBuilder();
builder["Data Source"] = $"{dbServer}.database.windows.net";
builder["Initial Catalog"] = dbName;
builder["Connect Timeout"] = 30;
builder["Persist Security Info"] = false;
builder["TrustServerCertificate"] = false;
builder["Encrypt"] = true;
builder["MultipleActiveResultSets"] = false;
var con = new SqlConnection(builder.ToString());
con.AccessToken = token;
return con;
}

I can understand that you want to use Azure AD access token to connect to Azure SQL DB. Based on the docs you provided and my own research , for now, we can use Azure AD service principle to get access token to connect to Azure SQL only.
I believe you have did some research on it already and find the codes to implement it using C# SDK. However there is no VB.net code sample to do it which confused you .
Honestly , I am not so familiar with VB.net , but I still implemented a demo to do that for you , pls refer to the code below :
Imports System.IO
Imports System.Net
Imports System.Text
Module Module1
Sub Main()
Dim tenant = ""
Dim client_id = ""
Dim username = ""
Dim password = ""
Dim client_secret = ""
Dim sqlserverName = ""
Dim dbname = ""
Dim dbConn = New SqlClient.SqlConnection
dbConn.ConnectionString = "Data Source=" + sqlserverName + ".database.windows.net;Initial Catalog=" + dbname + ";Connect Timeout=30"
dbConn.AccessToken = getAccessToken(tenant, client_id, client_secret, username, password)
Console.WriteLine("User: " + username)
Console.WriteLine("access token value:" + dbConn.AccessToken)
dbConn.Open()
Dim sqlQuery = "select ##version"
Dim Result = New SqlClient.SqlCommand(sqlQuery, dbConn).ExecuteScalar()
Console.WriteLine("query result from Azure SQL : " + Result.ToString)
Console.ReadKey()
End Sub
Function getAccessToken(tenant, client_id, client_secret, username, password) As String
Dim request As HttpWebRequest
request = HttpWebRequest.Create("https://login.microsoftonline.com/" + tenant + "/oauth2/token")
request.Method = "POST"
request.ContentType = "application/x-www-form-urlencoded"
Dim requestBody =
"grant_type=client_credentials" +
"&client_id=" + client_id +
"&client_secret=" + WebUtility.UrlEncode(client_secret) +
"&username=" + WebUtility.UrlEncode(username) +
"&password=" + WebUtility.UrlDecode(password) +
"&resource=https%3A%2F%2Fdatabase.windows.net%2F"
Dim encoding As New UTF8Encoding
Dim byteData As Byte() = encoding.GetBytes(requestBody)
request.ContentLength = byteData.Length
Dim postreqstream As Stream = request.GetRequestStream()
postreqstream.Write(byteData, 0, byteData.Length)
postreqstream.Close()
Dim result = request.GetResponseAsync().GetAwaiter().GetResult()
Dim result_str = New StreamReader(result.GetResponseStream()).ReadToEnd
Dim tokenResp As TokenResponse = Newtonsoft.Json.JsonConvert.DeserializeObject(Of TokenResponse)(result_str)
Return tokenResp.access_token
End Function
Public Class TokenResponse
Public Property token_type As String
Public Property expires_in As String
Public Property ext_expires_in As String
Public Property expires_on As String
Public Property not_before As String
Public Property resource As String
Public Property access_token As String
End Class
End Module
Pls refer to the steps below to create associated Azure AD apps and fill all params to run it :
Creating a Azure AD app in your tenant ,type its name and click register directly, in my case , the app name is DBadmin:
Note its application ID :
Creating a client secret for this app and note the secret :
Adding Azure SQL user login permission to this app :
Grant permission as an admin:
Go to "Groups" blade ,create an Azure AD group , adding the app we just created at same time :
Go to the Azure SQL server that you want to access , and adding the group we just created in to Azure AD admin of SQL server :
Remember to click save after adding .
Ok, we have done all configs here , just modify the params in code to try to connect to your Azure SQL .
Code result :
Hope it helps . Anyway If you just want to hide connection string in your config file.Key vault should be the best choice for you .

Related

Hangout OAuth - Invalid Scope : Some requested scopes cannot be shown

I am facing the below error while generating token for service account for the Hangout Scope - https://www.googleapis.com/auth/chat.bot.
Where i receive 400 response code after making a post request to this url -
https://www.googleapis.com/oauth2/v4/token
the params are
Content-Type:application/x-www-form-urlencoded
httpMode:POST
body:grant_type=jwt-bearer&assertion=assertion-token
Note:This was completely working fine. Suddenly am facing this issue.
cross verified: jwt generation,service_account_id and etc...
Error Response : { "error": "invalid_scope", "error_description": "Some requested scopes cannot be shown": [https://www.googleapis.com/auth/chat.bot]}
code for generating assertion:
//FORMING THE JWT HEADER
JSONObject header = new JSONObject();
header.put("alg", "RS256");
header.put("typ", "JWT");
//ENCODING THE HEADER
String encodedHeader = new String(encodeUrlSafe(header.toString().getBytes("UTF-8")));
//FORMING THE JWT CLAIM SET
JSONObject claimSet = new JSONObject();
claimSet.put("iss","123#hangout.iam.gserviceaccount.com");
claimSet.put("sub","one#domain.com");
claimSet.put("scope","https://www.googleapis.com/auth/chat.bot");
claimSet.put("aud","https://oauth2.googleapis.com/token");
long time = System.currentTimeMillis() / 1000;
claimSet.put("exp",time+3600);
claimSet.put("iat",time);
//ENCODING THE CLAIM SET
String encodedClaim = new String(encodeUrlSafe(claimSet.toString().getBytes("UTF-8")));
//GENERATING THE SIGNATURE
String password = "secretofkey", alias = "privatekey";
String signInput = encodedHeader + "." + encodedClaim;
Signature signature = Signature.getInstance("SHA256withRSA");
String filepath = "/check/PrivateKeys/hangoutPKEY.p12";
KeyStore kstore = KeyStore.getInstance("PKCS12");
fis = new FileInputStream(filepath);
kstore.load(fis, password.toCharArray());
KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry) kstore.getEntry(alias, new KeyStore.PasswordProtection(password.toCharArray()));
PrivateKey pKey = pke.getPrivateKey();
signature.initSign(pKey);
signature.update(signInput.getBytes("UTF-8"));
String encodedSign = new String(encodeUrlSafe(signature.sign()), "UTF-8");
//JWT GENERATION
String JWT = signInput + "." + encodedSign;
String grant_type = URLEncoder.encode("urn:ietf:params:oauth:grant-type:jwt-bearer");
reqBody = "grant_type=" + grant_type + "&assertion=" + JWT;
public static byte[] encodeUrlSafe(byte[] data) {
Base64 encoder = new Base64();
byte[] encode = encoder.encodeBase64(data);
for (int i = 0; i < encode.length; i++) {
if (encode[i] == '+') {
encode[i] = '-';
} else if (encode[i] == '/') {
encode[i] = '_';
}
}
return encode;
}
Does anyone have any idea, where am going wrong?
Short answer:
You are trying to use domain-wide authority to impersonate a regular account. This is not supported in Chat API.
Issue detail:
You are using the sub parameter when building your JWT claim:
claimSet.put("sub","one#domain.com");
Where sub refers to:
sub: The email address of the user for which the application is requesting delegated access.
I noticed that, if I add the sub parameter to my test code, I get the same error as you.
Solution:
Remove this line from your code in order to authorize with the service account (without impersonation) and handle bot data:
claimSet.put("sub","one#domain.com");
Background explanation:
Chat API can be used for bots to manage their own data, not to manage end-user data. Therefore, you can only use a service account to act as the bot, without impersonating an end-user.
From this Issue Tracker comment:
At the present moment, Chat API can only be used to manage bot-related data (listing the spaces in which the bot is included, etc.). Using domain-wide delegation to manage regular users' data is not currently possible.
Feature request:
If you'd like to be able to access regular users' data with your service account and domain-wide delegation via Chat API, you are not alone. This feature has been requested before in Issue Tracker:
Accessing the Google Chats of regular users using domain-wide delegated permission and service account credentials
I'd suggest you to star the referenced issue in order to keep track of it and to help prioritizing it.
Reference:
Using service accounts
Delegating domain-wide authority to the service account
Preparing to make an authorized API call

RestSharp Returning Unauthorized

I am having an issue and think I may be missing something with RestSharp.
I am authorizing and getting back a cookie just fine... see below. But then when I call to get the data it returns unauthorized. It works just fine in Postman but not in the code below. I am using a console app and I have tried to send the cookie via AddHeader, AddCookie, and just as a parameter. The responseLogin does contain the correct cookie. Any help would be great.
Dim clientLogin = New RestClient("http://[URI to Authorize]............")
Dim requestLogin = New RestRequest(Method.POST)
requestLogin.AddParameter("application/x-www-form-urlencoded", "[Username and password here.....]", ParameterType.RequestBody)
Dim responseLogin As IRestResponse = clientLogin.Execute(requestLogin)
Dim client = New RestClient("http://[URI to get data]............")
Dim request = New RestRequest(Method.GET)
request.AddHeader("Cookie", responseLogin.Cookies(0).Value.ToString)
request.AddHeader("Accept", "application/json")
Dim response As IRestResponse = client.Execute(request)
The Cookie header needs to contain the name and value for the cookie, e.g.
Dim authCookie = responseLogin.Cookies(0) ' Probably should find by name
request.AddHeader("Cookie", String.Format("{0}={1}", authCookie.Name, authCookie.Value))
However, the documentation (I've never used RestSharp personally) says that RestSharp has automatic support for cookies, so if you reuse the RestClient instance and set the CookieContainer you shouldn't need to do anything to handle the cookies manually (unless you want to, which in some cases may be preferable).
Dim client = New RestClient(New Uri("[Base URI...]"))
client.CookieContainer = New System.Net.CookieContainer()
Dim requestLogin = New RestRequest("[login page path]", Method.POST)
requestLogin.AddParameter("application/x-www-form-urlencoded", "[Username and password here.....]", ParameterType.RequestBody)
Dim responseLogin As IRestResponse = client.Execute(requestLogin)
Dim request = New RestRequest("[data api path", Method.GET)
request.AddHeader("Accept", "application/json")
Dim response As IRestResponse = client.Execute(request)
You could probably just reuse the cookie container with different RestClient instances instead of reusing the client.
I had the same issue on RestClient .NET framework 4.5.2 version.
It turns out you have to implement the IAuthenticator interface.
public class MyAuth : IAuthenticator
{
readonly string _password;
readonly string _passwordKey;
readonly string _username;
readonly string _usernameKey;
public MyAuth(string usernameKey, string username, string passwordKey, string password)
{
_usernameKey = usernameKey;
_username = username;
_passwordKey = passwordKey;
_password = password;
}
public void Authenticate(IRestClient client, IRestRequest request)
=> request
.AddCookie(_usernameKey, _username)
.AddCookie(_passwordKey, _password);
//.AddParameter(_usernameKey, _username)
//.AddParameter(_passwordKey, _password);
}
I did this and my request worked.

how to do push notification to i-pad on windows azure

I have a MVC Web API Project & I need to send push notification to iPad. I have used .cer and .p12 files for this & it is working fine. Now I need to publish it to an azure server but I'm a newbie to azure and don't know where to store and how to access the certificate.
I have tried to upload certificate to the cloud service, but am not able to access it.
I am using the following code:
string strDeviceToken = "abcd";
string strPushMessage = "hello";
var payload1 = new NotificationPayload(strDeviceToken, strPushMessage, 1, "default");
payload1.AddCustom("RegionID", "IDQ10150");
var p = new List<NotificationPayload> { payload1 };
var push = new PushNotification(true, certificatePath, "password");
string strfilename = push.P12File;
var message1 = push.SendToApple(p);
hi i am using this code and its work
for http://www.c-sharpcorner.com/UploadFile/pandeypradip/apple-push-notification-using-Asp-Net/
Attech Dll above link
string strDeviceToken ="xyz"
string strPushMessage="test"
payload1.AddCustom("RegionID", "IDQ10150");
var p = new List { payload1 };
string certificatePath = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/certificates/xyz.p12");
var push = new PushNotification(true, certificatePath, "password");
string strfilename = push.P12File;
var message1 = push.SendToApple(p);

Getting .NET Client to recognize authentication session cookie

I am using "RememberMe=true", and would like my service client to re-use the open session if it's available. I got the bulk of the code from the link below - this code works but authentication fails every time at first and re-authenticates. Do I have to send the ss-pid cookie somehow?
One more note: this is a WinForms client accessing my servicestack service.
ServiceStack JsonServiceClient OnAuthenticationRequired
My code
Private Shared _UserName As String = "xxxxx"
Private Shared _Password As String = "yyyyy"
Private Shared _clientAuthenticationRequested As New Action(Of WebRequest)(AddressOf InteractiveAuthentication)
Public Shared ReadOnly Property ServiceClient() As JsonServiceClient
Get
If _serviceClient Is Nothing Then
_serviceClient = New JsonServiceClient(ServiceContext.ServiceUrl)
_serviceClient.OnAuthenticationRequired = _clientAuthenticationRequested
_serviceClient.UserName = _UserName
_serviceClient.Password = _Password
//service requiring authentication
Dim v = _serviceClient.Get(Of Tonto.Svc.Model.AppConstants)(
New Tonto.Svc.Model.AppConstants())
End If
Return _serviceClient
End Get
End Property
Private Shared Sub InteractiveAuthentication(sourcerequest As System.Net.WebRequest)
Dim v = _serviceClient.Send(Of ServiceStack.AuthenticateResponse)(
New ServiceStack.Authenticate() With {
.UserName = _UserName,
.Password = _Password,
.RememberMe = True})
End Sub
You can't have the client remember your session between the creation of clients out of the box. The RememberMe option will not work here, as the client does not have a persistent cookie store like a web browser.
You can however access the cookie store of the client, after you have authenticated then read the session value cookie, and restore it in future client instances. Essentially you provide the persistence layer.
Sorry it's c# not VB. But I think the concept should be clear enough.
var host = "http://localhost:9001";
JsonServiceClient client = new JsonServiceClient(host);
// Authenticate with the service
client.Post(new Authenticate { UserName = "test", Password = "password" });
// Read the session cookie after successfully authenticating
var cookies = client.CookieContainer.GetCookies(new Uri(host));
var sessionCookieValue = cookies["ss-id"].Value;
// Store the value of sessionCookieValue, so you can restore this session later
client = null;
So if you were to save the ss-id value to a file, you can restore the value when the application is started, then add it back into the client's cookie store before making requests.
// Another client instance ... we will reuse the session
JsonServiceClient anotherClient = new JsonServiceClient(host);
// Restore the cookie
anotherClient.CookieContainer.Add(new Cookie("ss-id", sessionCookieValue, "/", "localhost"));
// Try access a secure service
anotherClient.Get(new TestRequest());

Get AD Guid from HttpContext.Current.User

I have tried many, many different ways, to get this data. But I can't get it to work.
I have a MVC4 application, hooked up with Active Directory. But I need the users AD GUID.
I tried:
(Guid)Membership.GetUser(User.Identity.Name).ProviderUserKey;
WebSecurity.CurrentUserId;
But none of them work.
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, User.Identity.Name);
if(user != null)
{
Guid userGuid = user.Guid ?? Guid.Empty;
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
I managed to solve it (Not pretty...):
string login = HttpContext.Current.User.Identity.Name;
string domain = login.Substring(0, login.IndexOf('\\'));
string userName = login.Substring(login.IndexOf('\\') + 1);
DirectoryEntry domainEntry = new DirectoryEntry("LDAP://" + domain);
DirectorySearcher searcher = new DirectorySearcher(domainEntry);
searcher.Filter = string.Format("(&(objectCategory=person)(objectClass=user)(sAMAccountName={0}))",userName);
SearchResult searchResult = searcher.FindOne();
DirectoryEntry entry = searchResult.GetDirectoryEntry();
Guid objectGuid = entry.Guid;
The original code used : entry.NativeGuid, but I changed because of Little / Big endian "problems"
entry.Guid has the same "format" as in AD.