My goal is to allow users to connect to our Azure Sql Server using their Azure Active Directory credentials. I'm trying to follow the steps in this article, but I'm getting an error I can't sort out:
Connect to Azure SQL Database with Azure Multi-Factor Authentication
Below are the appropriate pieces of my code, which I largely copied from the example in the article (except my app is written in VB.NET so I had to translate). It requires the Microsoft.IdentityModel.Clients.ActiveDirectory assembly, which I got from NuGet.
Public Module DB
Private ConnectionProvider As ActiveDirectoryAuthProvider
'Gets run at application start
Public Sub SetProvider()
ConnectionProvider = New ActiveDirectoryAuthProvider
SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, ConnectionProvider)
End Sub
End Module
'I can't believe Microsoft doesn't just have this as a class that's already been written
Public Class ActiveDirectoryAuthProvider
Inherits SqlAuthenticationProvider
Private ReadOnly _clientId As String = "Our Client ID From The Azure Portal"
Private ReadOnly _redirectUri As New Uri("A Valid URL")
Public Overrides Async Function AcquireTokenAsync(parameters As SqlAuthenticationParameters) As Task(Of SqlAuthenticationToken)
Dim authContext As New AuthenticationContext(parameters.Authority)
authContext.CorrelationId = parameters.ConnectionId
Dim result As AuthenticationResult
Select Case parameters.AuthenticationMethod
Case SqlAuthenticationMethod.ActiveDirectoryInteractive
result = Await authContext.AcquireTokenAsync(parameters.Resource, _clientId, _redirectUri, New PlatformParameters(PromptBehavior.Auto), New UserIdentifier(parameters.UserId, UserIdentifierType.RequiredDisplayableId))
Case SqlAuthenticationMethod.ActiveDirectoryIntegrated
result = Await authContext.AcquireTokenAsync(parameters.Resource, _clientId, New UserCredential())
Case SqlAuthenticationMethod.ActiveDirectoryPassword
result = Await authContext.AcquireTokenAsync(parameters.Resource, _clientId, New UserPasswordCredential(parameters.UserId, parameters.Password))
Case Else
Throw New InvalidOperationException()
End Select
Return New SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)
End Function
Public Overrides Function IsSupported(ByVal authenticationMethod As SqlAuthenticationMethod) As Boolean
Return authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryIntegrated OrElse authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryInteractive OrElse authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryPassword
End Function
End Class
'And finally, I create new connections like this:
New SqlConnection($"Server=tcp:ourserver.database.windows.net,1433;Initial Catalog=OurDatabase;TrustServerCertificate=True;Pooling=False;Encrypt=True;Authentication=""Active Directory Interactive"";User ID={Environment.UserName}#OurDomain.com;")
Using this code, I do get the popup from Azure asking me to sign in when I run SqlConnection.Open. As soon as I've signed in however, I get the following exception:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException
AADSTS7000218: The request body must contain the following parameter:
'client_assertion' or 'client_secret'.
Any idea how I can fix that?
So, in digging through every resource I could find, I came across this question:
“error_description”:"AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'
The answer linked above points to the "Redirect URI" registered with the application in Azure as the cause of the issue.
The Microsoft article in my question states: "For this article, any valid value is fine for RedirectUri, because it isn't used here." The example they use is: "https://mywebserver.com/".
Contrary to the quote from Microsoft, the answer I linked above points out that Azure uses the Redirect URI to determine the type of application that is being registered. Changing the URI from my company's website to "https://login.microsoftonline.com/common/oauth2/nativeclient" fixes my problem. That URL is one of the default values Azure lets you pick from. It evidently indicates to Azure that you are registering a native app, not a web app. Once Azure knows this, it seems to stop demanding a "'client_assertion' or 'client_secret'", which I can only assume are things required for web app authentication.
I faced with this problem a bit earlier, spent a lot of time to figure out the problem and the only solution which helped was adding of "https://login.microsoftonline.com/common/oauth2/nativeclient" on "Authentication" tab to "Redirect URIs" like it was mentioned by Keith Stein
Related
I am working on a project which I did not write, have inherited, and have an issue that I'm not sure quite how to solve. My background is not in .NET, so please excuse anything that doesn't sound right, as I may not know what the correct terminology should be.
We are using Visual Studio 2008 to compile a project that is running on Windows CE 6.0. We are using the Compact Framework v2.0. The software is running on an Embedded processor in a network (WIFI) connected industrial environment. The main UI is written in VB, and all of the supporting DLLs are written using C#.
Up until now we've only been required to connect to http (non-secure) web addresses for GET requests. We now have a requirement to switch these addresses over to https (secure) for security's sake.
The HttpWebRequest is built/submitted from VB. When I provide the code with the https address, I get the "Could not establish secure channel for SSL/TLS" error that is in the subject.
Here is the code for that request:
Dim myuri As System.Uri = New System.Uri(sUrl)
Dim myHttpwebresponse As HttpWebResponse = Nothing
Dim myhttpwebrequest As HttpWebRequest = CType(WebRequest.Create(myuri), HttpWebRequest)
myhttpwebrequest.KeepAlive = False
myhttpwebrequest.Proxy.Credentials = CredentialCache.DefaultCredentials
myhttpwebrequest.ContentType = "text/xml"
myhttpwebrequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
myhttpwebrequest.AllowAutoRedirect = False
myhttpwebrequest.Timeout = 150000
Dim mycred As NetworkCredential = New NetworkCredential(username, password)
Dim myCredentialCache As CredentialCache = New CredentialCache()
myCredentialCache.Add(myuri, "Basic", mycred)
myhttpwebrequest.Credentials = myCredentialCache
myhttpwebrequest.Method = "GET"
myhttpwebrequest.ProtocolVersion = HttpVersion.Version10
ServicePointManager.CertificatePolicy = New AcceptServerNameMismatch
myHttpwebresponse = CType(myhttpwebrequest.GetResponse(), HttpWebResponse)
I have done quite a bit of reading over the last day or so that indicate that the CertificatePolicy is where I can override the ICertificatePolicy classes to essentially validate all SSL requests. Definitely not safe, and not ideal, but I'm not sure of another way to handle these requests.
My class to do this is:
Public Class MyCertificatePolicy
Implements ICertificatePolicy
Public Shared DefaultValidate As Boolean = True
Public Sub trustedCertificatePolicy()
End Sub
Public Function CheckValidationResult(ByVal srvPoint As ServicePoint, _
ByVal cert As X509Certificate, ByVal request As WebRequest, ByVal problem As Integer) _
As Boolean Implements ICertificatePolicy.CheckValidationResult
Return True
End Function
End Class
Unfortunately when the response comes back, it never calls CheckValidationResult(). Thus, no validation and the error.
So my questions...
The "Right" way to do this according to everything that I've read is to use the ServerCertificateValidationCallback. Unfortunately with the version of Compact Framework that we are using (maybe all?) it is not included. Is there something that I'm missing that would cause that function not to get called?
Again, from what I've read, I believe that the Framework that we're running on doesn't support TLS v1.1 or v1.2. Which most current servers are running. Is there a way in VB to get around this?
Is there another Request method that can be used?
Any help or guidance as to where to go from here is greatly appreciated!
You need to install the trusted root certificate on the device(s), that matches the SSL certificate on your server.
Or change the certificate on the server to match one of the Trusted Roots on the device(s). By default, the devices ship with a very small number of trusted CAs, unlike desktop browsers that contain nearly every CA in the world.
I am using ReportExecutionServiceSoapClient in .Net Core i got the latest version of .net Core and tried to get a report from reporting services to work. after I've used the WCF connection service I was able to add the code with looks like bellow
// Instantiate the Soap client
ReportExecutionServiceSoap rsExec = new ReportExecutionServiceSoapClient(ReportExecutionServiceSoapClient.EndpointConfiguration.ReportExecutionServiceSoap);
// Create a network credential object with the appropriate username and password used
// to access the SSRS web service
string historyID = null;
TrustedUserHeader trustedUserHeader = new TrustedUserHeader();
ExecutionHeader execHeader = new ExecutionHeader();
// Here we call the async LoadReport() method using the "await" keyword, which means any code below this method
// will not execute until the result from the LoadReportAsync task is returned
var taskLoadReport = rsExec.LoadReportAsync(reportPath, historyID);
// By the time the LoadReportAsync task is returned successfully, its "executionInfo" property
// would have already been populated. Now the remaining code in this main thread will resume executing
string deviceInfo = null;
string format = "EXCEL";
// Now, similar to the above task, we will call the RenderAsync() method and await its result
var taskRender = await rsExec.RenderAsync(renderReq);
When it hist renderAsync all falls apart because the credentials for the service are not set anywhere. I've tried to Login async with no success. Also I've tried to set the credentials with SetExecutionCredentialsAsync but I've got and error saying "The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'NTLM'." I don't know how to change that for ReportExecutionServiceSoapClient.
I have read some posts in which Microsoft guys says that the authentication with a soap is not resolved but for me it seems so close to be true. I feel like I am missing something.
Technology stack: VS 2017, .net Core web api, ssrs 2016, sql server 2016 standard
How can I authenticate the user for this call?
I know this is an old question but I had the same issue and stumbled onto the answer.
After creating the ReportExecutionServiceSoap object you can specify the username and password in the ClientCredentials. I've had success with this using the Basic client credential type. Be sure you are using HTTPS, otherwise your password is sent in plaintext to the reporting server. I also recommend storing the user/password in a secure place and not code.
BasicHttpBinding rsBinding = new BasicHttpBinding();
rsBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
rsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
EndpointAddress rsEndpointAddress = new EndpointAddress("https://servername/ReportServer/ReportExecution2005.asmx");
var rsExec = new ReportExecutionServiceSoapClient(rsBinding, rsEndpointAddress);
rsExec.ClientCredentials.UserName.UserName = "username";
rsExec.ClientCredentials.UserName.Password = "pass";
Here's what I'm trying to do: I'm trying to generate an access token on the server side that will serve to authenticate a user as in this example:
https://ga-dev-tools.appspot.com/embed-api/server-side-authorization/
I've written this code in VB.net after several tries:
Public GA_Token As String
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim filename As String = {{path to p12 file}}
Dim serviceAccountEmail As String = {{service account generated email from IAM & Admin from console.developers.google.com of the form user#project name.iam.gserviceaccount.com}}
Dim certificate = New X509Certificate2(filename, "password", X509KeyStorageFlags.Exportable Or X509KeyStorageFlags.MachineKeySet Or X509KeyStorageFlags.PersistKeySet)
Dim Scopes As IEnumerable(Of String) = {AnalyticsService.Scope.AnalyticsReadonly}
Dim credential As New ServiceAccountCredential(New ServiceAccountCredential.Initializer(serviceAccountEmail) With {.Scopes = Scopes}.FromCertificate(certificate))
GA_Token = credential.GetAccessTokenForRequestAsync(Request.Url.ToString, CancellationToken.None).Result
End Sub
GA_Token is then written to the Analytics page I created.
The problem is that, when I go to view the reports, I get a 403 error from Chrome.
Objectbody: "{"error":{"errors":[{"domain":"global","reason":"insufficientPermissions","message":"User does not have any Google Analytics account."}],"code":403,"message":"User does not have any Google Analytics account."}}"headers: Objectcache-control: "private, max-age=0"content-encoding: "gzip"content-length: "146"content-type: "application/json; charset=UTF-8"date: "Fri, 18 Nov 2016 18:25:08 GMT"expires: "Fri, 18 Nov 2016 18:25:08 GMT"server: "GSE"vary: "Origin, X-Origin"www-authenticate: "Bearer realm="https://accounts.google.com/", error=insufficient_scope, scope="https://www.googleapis.com/auth/analytics.edit""proto: Object__defineGetter__: defineGetter()defineSetter: defineSetter()lookupGetter: lookupGetter()lookupSetter: lookupSetter()constructor: Object()hasOwnProperty: hasOwnProperty()isPrototypeOf: isPrototypeOf()propertyIsEnumerable: propertyIsEnumerable()toLocaleString: toLocaleString()toString: toString()valueOf: valueOf()get proto: proto()set proto: proto()result: Objecterror: Objectcode: 403errors: Array[1]0: Objectlength: 1__proto__: Array[0]message: "User does not have any Google Analytics account."proto: Object__proto__: Objectstatus: 403statusText: null__proto__: Object_.nH # cb=gapi.loaded_0:606_.du.Vh # cb=gapi.loaded_0:742(anonymous function) # view-selector2.js:109h.o0 # cb=gapi.loaded_0:75xs # cb=gapi.loaded_0:78Wq # cb=gapi.loaded_0:78_.C.uea # cb=gapi.loaded_0:77Ap # cb=gapi.loaded_0:71
cb=gapi.loaded_0:67 Uncaught Object {result: Object, body: "{"error":{"errors":[{"domain":"global","reason":"i…er does not have any Google Analytics account."}}", headers: Object, status: 403, statusText: null}(anonymous function) # cb=gapi.loaded_0:67
Now, I can use the OAuth2 protocol to generate the token and have it display if the user logs in to his/her account, but I'm trying to bypass that. The problem I'm running into is that I don't see where I'm supposed to set the permissions for the account. I went into IAM and Admin and enabled Domain-wide Delegation on two different service accounts to test with. I set them with every permission imaginable (which worked out to Owner + 18 other permissions). Where else do I need to set permissions, or am I overlooking something?
First, set the User email like this here, not sure how to do that in VB.
You say:
I can use the OAuth2 protocol to generate the token and have it display if the user logs in to his/her account, but I'm trying to bypass that.
But I see that you're also trying to use a Google Service Account, which I think only works if you are a paying G Suite user.
I've spent a lot of time to figure this out for my Gmail client and by accident found the solution:
The permissions should be visible per user under Connected Apps.
But this does mean however that your user has to manually grant access once, after that you can just get a token using the Google Service Account.
I tried and it works for me.
I had previously setup a Project called "YYYassistant" and I created an account:
Then I created a simple WinForm app and could not reproduce the issue, this works:
Imports Google.Apis.Auth.OAuth2
Imports System.Security.Cryptography.X509Certificates
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim scopes As String() = New String() {AnalyticsService.Scope.Analytics}
' view and manage your Google Analytics data
Dim keyFilePath = "C:\Temp\YYYassistant-1234db98765.p12"
' Downloaded from https://console.developers.google.com
Dim serviceAccountEmail = "XXX#YYYassistant.iam.gserviceaccount.com"
' found https://console.developers.google.com
'loading the Key file
Dim certificate = New X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable)
Dim credential = New ServiceAccountCredential(New ServiceAccountCredential.Initializer(serviceAccountEmail) With {.Scopes = scopes}.FromCertificate(certificate))
End Sub
End Class
"code":403,"message":"User does not have any Google Analytics account."
Its a problem with your account, make a new one.
The issue is explained on the Json
"{"error":{"errors":[{"domain":"global","reason":"insufficientPermissions","message":"User does not have any Google Analytics account."}]
{ "User does not have any Google Analytics account." }
Maybe change the code will not change anything, because it's an access error.
In a few words, You gmail used to get the Token has no account linked.
The validation process for the Auth look wells. Before change code i recommend you do the following steps.
1.- Get a valid Email and Passwords
I can believe you already got this (you already have the toke), grab this credentials and go to
https://analytics.google.com/analytics/web/ , login with these credential and And see if you have any account linked.
If you account have no access you will see
Note: If you use the Oauth validate, the email used to get the token can be different that the one used on the project, maybe you need to use someone else's credentials. Be 100% sure of what credential are you using.
2.- If you have no account you need to ask access, so the user with the Google Analytics Account needs to give you access. In the desired account you need to go Admin -> User Management , and add the email used on the Validation Process:
When you have you token, remember store it and refresh it whenever you need, the tokens needs to be mananged
3.- If you want to get your own data, maybe you can use a P12key, as Jeremy says, this method is more easy to validate, but you need to add the p12key on every query (usually the library does it for you). But with this method you can only access to data linked to your own gmail account. If you change the method you can have the same error, that it the need to be 100% of the used account
I'm using Office 365 API Tools for Visual Studio. When I run the following code everything works fine and my documents are returned.
string SPResourceID = "https://tenant.sharepoint.com";
string SPServiceRoot = "https://tenant.sharepoint.com/_api";
var authenticator = new Authenticator();
var result = await authenticator.AuthenticateAsync(SPResourceID, ServiceIdentifierKind.Resource);
// Create a client proxy:
this.client = new SharePointClient(new Uri(SPServiceRoot), result.GetAccessToken);
this.client.Context.IgnoreMissingProperties = true;
However, when I try to access a sub web such as:-
string SPResourceID = "https://tenant.sharepoint.com/test";
string SPServiceRoot = "https://tenant.sharepoint.com/test/_api";
I am asked to reauthenticate and then get the error "AADSTS50001: Resource 'https://tenant.sharepoint.com/test/' is not registered for the account". Has anyone got any ideas as to how I can access libraries in a sub web and is it possible to pass in the name of the library rather than relying on it being the default library.
Thanks,
Geoff
Your SPResourceID is still https://tenant.sharepoint.com/.
The resourceId is the ID your Office 365 subscription is registered with in azure ad.
I have a winforms (VB 2008) based app that I'm developing and I want to use custom roles for user access.
Application Layout:
I have a Main form that opens a login form when certain actions occur. The Login form intern uses an authentication class that I've created, to authenticate users and set access rights. On my Applications settings page, I have Authentication Mode set to Application-defined because I cannot use Windows Authentication in the environment where this will be deployed.
The application uses a MS SQL 2005 db and the 3 tables I'm using in the authentication process are the User_Account , User_Roles and User_Access tables. The combination of an account in the User_Account and the roles within the User_Roles table are the bases for the User_Access table. Using the User_Access table is how I assign access to the various functions within the application
Authentication Method:
To authenticate a user, I'm using the "My.User.CurrentPrincipal" (Code below) method. The My.User object works great and allows the use of "My.User.Name" property throughout the app when referring to the currently authenticated user.
Access Method:
In order to set the current users access levels I'm using a function within my Authentication class and passing in My.User.Name as a variable. The function uses a Dataset Table Adaptor and a Select Case statement inside a For loop to assign all the access levels for the user (Function code below).
My Problem:
This method of assigning access rights to a user does work but it's not persistent throughout the application as the My.User object is. I would like to find a way to create custom roles through the My.User object using its .IsInRole property. I would like to have these roles dynamically created using my User_Roles table.
This would allow the custom roles to be used throughout my application using the My.User.IsInRole("MyRole") syntax ...similar to how I'm currently able to use My.User.Name. Unfortunately the only roles I can currently validate against are the built in Windows type accounts (Adminisrator ...ect.).
I have found lots of information and examples related to ASP.Net as well as setting up Winforms Windows authentication but nothing so far directly related to my issue.
I think there's a way to accomplish this...but I have not been able to find it. Any help would be greatly appreciated!!
Thank you for your help!
'User Authentication example:
If Authenticate.CheckPassword(tbxUserName.Text, strPassword) Then
My.User.CurrentPrincipal = New GenericPrincipal(New GenericIdentity(tbxUserName.Text), Nothing)
'Access assignment example:
Public Shared Function GetUser(ByVal strUsername As String) As Authenticate
Using UserAdapter As New dbUserTableAdapters.User_AccountsTableAdapter()
Dim UserTable As dbUser.User_AccountsDataTable = UserAdapter.GetByUser(strUsername)
Dim tempUser As New Authenticate() _
With {.ID = UserTable(0).id, _
.Username = UserTable(0).User_Name, _
.Password = UserTable(0).id}
Using AccessAdapter As New dbUserTableAdapters.User_AccessTableAdapter()
Dim AccessTable As dbUser.User_AccessDataTable = AccessAdapter.GetByUser(tempUser.ID)
For c As Integer = 0 To AccessTable.Rows.Count - 1
Select Case AccessTable(c).Role_Id
Case RoleType.SysAdmin
tempUser.AllowSysAdmin = True
Case RoleType.Maintenance
tempUser.AllowMaintenance = True
Case RoleType.ReportAll
tempUser.AllowRptAll = True
Case RoleType.ReportException
tempUser.AllowRptExceptions = True
Case RoleType.EventManagment
tempUser.AllowEventStart = True
Case Else
End Select
Next
Return tempUser
End Using
End Using
End Function
I think you need to implement a custom IPrincipal object which accesses your SQL table. Try this page.
Edit:
First, have a look at the definitions of IIdentity and IPrincipal. You'll note that IIdentity doesn't have a 'Role' property defined. They've chosen to implement an additional property called Role on their implementation of IIdentity (SampleIIdentity) and then they've used it from their implementation of IPrincipal. What I'm suggesting is that you implement your own Role property (which queries your existing table) and returns one (or an array) of a Role type you define yourself. Then in your implementation of IPrincipal, you can code IsInRole to query the new Role property. Hopefully that makes more sense that my rather skimpy answer.