I am using UseOpenIdConnectAuthentication to authenticate users. My application code works fine locally. But, when I run it on Azure, the SecurityTokenValidated event is never fired. Consequently, the code runs fine but the user is never authenticated. I am not sure if the issue is with my code or with Azure. This is being used in a Web Form, Asp.net application (not Core). I use the Azure trace feature to log. I can see that only "RedirectToIdentityProvider" is fired. No other event gets called. Here is my code:
Startup.Auth.Vb:
Public Sub ConfigureAuth(app As IAppBuilder)
Dim clientId As String = ""
Dim authority As String = ""
Dim redirectURI As String
Trace.TraceInformation("Hit Config Auth function")
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = New Dictionary(Of String, String)
app.SetDefaultSignInAsAuthenticationType("Cookies")
app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
.AuthenticationMode = AuthenticationMode.Active,
.CookieManager = New SystemWebCookieManager
})
redirectURI = appSettings("ID_Redirect_URI")
clientId = appSettings("ID_ClientID")
authority = appSettings("ID_Authority")
Trace.TraceInformation(redirectURI)
Trace.TraceInformation(clientId)
Trace.TraceInformation(authority)
Trace.TraceInformation("creating OpenIDAuthOptions")
Dim OpenIdAuthOption = New OpenIdConnectAuthenticationOptions() With {
.SignInAsAuthenticationType = "Cookies",
.Authority = authority,
.RequireHttpsMetadata = False,
.ClientId = clientId,
.ResponseType = "id_token",
.Scope = "openid profile roles",
.RedirectUri = redirectURI,
.PostLogoutRedirectUri = redirectURI,
.Notifications = New OpenIdConnectAuthenticationNotifications() With {
.AuthenticationFailed = Function(ctx)
Trace.TraceInformation("Auth Failed event")
Return Task.FromResult(0)
End Function,
.SecurityTokenReceived = Function(ctx)
Trace.TraceInformation("Sec Token Recieved event")
Return Task.FromResult(0)
End Function,
.MessageReceived = Function(ctx)
Trace.TraceInformation("Message Recieved event")
Return Task.FromResult(0)
End Function,
.SecurityTokenValidated = Function(ctx)
Trace.TraceInformation("Security token validated")
Return Task.FromResult(0)
End Function,
.AuthorizationCodeReceived = Function(ctx)
Trace.TraceInformation("Auth Code Recieved event")
Return Task.FromResult(0)
End Function,
.RedirectToIdentityProvider = Function(context)
Trace.TraceInformation("start of RedirectToIDProvider")
Return Task.FromResult(0)
End Function
}
}
Trace.TraceInformation("adding OpenIdAuthOptyions")
app.UseOpenIdConnectAuthentication(OpenIdAuthOption)
Trace.TraceInformation("finihsed adding OpenIdAuthOptyions")
End Sub
As I mentioned above, this code works fine locally. It only does not work when hosted on Azure. When running locally, the events are fired in this order:
RedirectToIdentityProvider
Message Received
Security Token Received
Security Token Validated
But, in Azure, only RedirectToIdentityProvider is fired.
Changed your Action to take when request is not authenticated in App Service Authentication/Authorization section in the azure portal from LogIn with Azure Active Directory to Allow Anonymous requests. As shown on the picture below:
Then the SecurityTokenValidated would be fired. App services auth takes place outside of you app, so customized auth code in your app never gets a chance to run. When you turn that off it allows your app to handle the auth itself the same way it does locally.
Here is the similar issue you could refer to.
Try changing the application manifest of the application definition on Azure to set the "oauth2AllowIdTokenImplicitFlow" property to true from false.
Go to the Azure Portal,
Select to Azure Active Directory
Select App Registrations
Select your app.
Click on Manifest
Find the value oauth2AllowIdTokenImplicitFlow and change it's value to true
Click Save
2) In your startup.cs file, change the following:
ResponseType = OpenIdConnectResponseType.Code
to
ResponseType = OpenIdConnectResponseType.CodeIdToken
and see if it helps.
Related
Imports Microsoft.Graph
Imports Azure.Identity
Module Module1
Sub Main()
Dim onlineMeeting = New OnlineMeeting With {
.StartDateTime = DateTimeOffset.Parse("2022-04-23T21:33:30.8546353+00:00"),
.EndDateTime = DateTimeOffset.Parse("2022-04-23T22:03:30.8566356+00:00"),
.Subject = "Application Token Meeting",
.Participants = New MeetingParticipants With {
.Organizer = New MeetingParticipantInfo With {
.Identity = New IdentitySet With {
.User = New Identity With {
.Id = "ba525532-764a-4aa1-8103-066beca0f5a8"
}
}
}
}
}
Dim testing As Task(Of OnlineMeeting) = createMeeting(onlineMeeting)
Console.WriteLine(testing.Status.ToString)
Console.ReadKey()
End Sub
Public Async Function createMeeting(onlineMeeting As OnlineMeeting) As Task(Of OnlineMeeting)
Dim createMeetings As OnlineMeeting = Nothing
Try
Dim clientId As String = "XXXXXXXXX"
Dim clientSecret As String = "XXXXXXXXXXXXX"
Dim tenantId As String = "XXXXXXXXXXX"
Dim options = New TokenCredentialOptions With
{
.AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
}
Dim ClientSecretCredential = New ClientSecretCredential(tenantId, clientId, clientSecret, options)
Dim graphClient As GraphServiceClient = New GraphServiceClient(ClientSecretCredential)
createMeetings = Await graphClient.Communications.OnlineMeetings.Request().AddAsync(onlineMeeting)
Catch ex As ServiceException
Console.WriteLine(ex.RawResponseBody)
End Try
Return createMeetings
End Function
End Module
When i execute this i get :
WaitingForActivation
{"error":{"code":"Forbidden","message":"","innerError":{"request-id":"XXXXX","date":"2021-08-27T11:38:30","client-request-id":"XXXXXXXX"}}}
Can someone help me?
This normally means there is no permission to perform the action.
Check you are including the relavent scope when authenticating against graph. In this case it is 'OnlineMeetings.ReadWrite' for delegated permissions and 'OnlineMeetings.ReadWrite.All' for application permissions
If using delegated permissions check the delegated user has permission to perform this action, if they can't do it manually you can't do it through graph.
If using application permissions make sure you have created an appropriate application access policy as described in the blue note here: https://learn.microsoft.com/en-us/graph/api/application-post-onlinemeetings?view=graph-rest-1.0&tabs=http
Administrators must create an application access policy and grant it to a user, authorizing the app configured in the policy to create an online meeting on behalf of that user (user ID specified in the request path).
I was tasked with writing an ASP.NET website that uses Azure Active Directory. I went with the route of OAuth and OpenID Connect. I am not able to use implicit flow and therefore must set the ResponseType to be code.
Using MSAL code samples I got most of it working but the problem is that all the samples are using a response type that returns tokens. I think I need to do it in 2 separate steps, first get the authorization code and then get the id token. I'm not exactly sure how to do this and would much appreciate some guidance here.
I have a startup class that look like this:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = authority,
ClientId = clientId,
RedirectUri = redirectUri,
Scope = "openid profile email offline_access user.readbasic.all", // a basic set of permissions for user sign in & profile access
ResponseType = OpenIdConnectResponseType.Code,
ClientSecret = clientSecret,
TokenValidationParameters = new TokenValidationParameters
{
// In a real application you would use ValidateIssuer = true for additional checks and security.
ValidateIssuer = false,
NameClaimType = "name",
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
}
});
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
// Handle any unexpected errors during sign in
context.OwinContext.Response.Redirect("/Error?message=" + context.Exception.Message);
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = GroupManager.Utils.MsalAppBuilder.BuildConfidentialClientApplication(null);
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(new[] { "openid profile email offline_access user.readbasic.all" }, context.Code).ExecuteAsync();
}
How do I take the information from the result's tokens and create a claims identity for the AuthenticationTicket.Identity and access the user info?
Please note that this is an ASP.NET application. Not MVC and not Core.
If you use MSAL, you don't need to handle the code yourself. MSAL will return the token to you after you log in interactively, please see:Overview of Microsoft Authentication Library (MSAL).
Before that, you need to take a look at Add sign-in to Microsoft to an ASP.NET web app,the workflow is:
Code example please check: https://github.com/AzureAdQuickstarts/AppModelv2-WebApp-OpenIDConnect-DotNet
Update:
Try to enable ID token
I created a vanilla ASP.NET MVC AAD Authenticated application in Visual Studio 2017. It includes the following:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
var graphUri = new Uri(AAD_GRAPH_URI);
var serviceRoot = new Uri(graphUri, tenantId);
this.aadClient = new ActiveDirectoryClient(serviceRoot, async () => await AcquireGraphAPIAccessToken(AAD_GRAPH_URI, authContext, credential));
return Task.FromResult(0);
}
}
});
For a while HttpContext.Current.Request.Url returns https://localhost:44345/ as is listed in the browser, and configured in Visual Studio for IIS Express.
However after a while it starts returning http://127.0.0.1/ instead! This results in the AzureAD auth returning the production URL instead of the localhost development URL.
I could hard code the development URL, but it is supposed to be dynamic so that it just works wherever I deploy it.
Why is IIS Express returning http://127.0.0.1/ instead of https://localhost:44345/ on my development box? And how do I get it to return the correct value.
`
We are using Microsoft's Identity Framework v2.0 in a web forms application. All is working well. We decided we want to add email verification as part of the new account set up process. If we validate the token after it is created in the same page, we are successful. But if we try to validate the token in a different page, it fails. The process is very simple:
Admin creates a new account by providing user's email and name. (we do not support self registration).
User clicks link he gets in email to validate the email was received.
Here is the code to create the email verification token:
var manager = new UserManager();
var user = new ApplicationUser() { UserName = EmailAddress.Text, Email = EmailAddress.Text, FirstName = FirstName.Text, LastName = LastName.Text };
IdentityResult result = manager.Create(user);
var provider = new DpapiDataProtectionProvider();
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("EmailConfirmation"))
{
TokenLifespan = TimeSpan.FromHours(24)
};
var strToken = manager.GenerateEmailConfirmationToken(user.Id);
//IdentityResult validToken = manager.ConfirmEmail(user.Id, strToken);
strToken = HttpUtility.UrlEncode(strToken.ToString());
NOTE: If we uncomment the line beginning //IdentityResult validToken..., then it succeeds.
Here is the code on the VerifyEmail page:
string userid = Request.QueryString["id"].ToString();
string tokenReceived = Request.QueryString["token"].ToString();
//tokenReceived = HttpUtility.UrlDecode(tokenReceived);
ApplicationUser User = new ApplicationUser();
var manager = new UserManager();
User = manager.FindById(userid);
var provider = new DpapiDataProtectionProvider();
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("EmailConfirmation"))
{
TokenLifespan = TimeSpan.FromHours(24)
};
IdentityResult validToken = manager.ConfirmEmail(User.Id, tokenReceived);
The validToken line does not succeed in this file. I have validated that the strings User.Id and tokenReceived match EXACTLY in both file, so there is no URL corruption going on. (That is why I commented out the UrlDecode since it seems to be decoded by the browser automatically - when I try to decode, it is not 100% the same as the string before encoding).
So I am certain we are calling the same method (ConfirmEmail) and that the two parameters that are passed are exactly the same strings. I am also aware that a token can only be validated once, so I am not trying to re-use them after once validating them.
Any ideas would be welcome.
I think the problem in DpapiDataProtectionProvider - If you use the same instance of this class in creating and validating the token, it'll work fine.
Any reason you are not getting UserManager from Owin Context as per VC2013 template?
I'm looking for a solution to doing incremental authorization against Google's api's with Asp.Net's Owin OAuth Libraries.
I know how to set scope for specific api's, but I would like to do it incrementally and can only see how to set it on globally.
Doc on Google Oauth Incremental Auth...
https://developers.google.com/accounts/docs/OAuth2WebServer#incrementalAuth
Current VB Code...
Public Sub ConfigureAuth(app As IAppBuilder)
Dim googleCreds = New GoogleOAuth2AuthenticationOptions() With {
.ClientId = "xxxx",
.ClientSecret = "xxx"
}
googleCreds.Scope.Add("https://www.googleapis.com/auth/analytics.readonly")
app.UseGoogleAuthentication(googleCreds)
' Would like to add another way to specify GoogleDrive, YouTube, Google+ scopes
' Example code that doesn't work that would add a 2nd Google Oauth Listener
googleCreds.Scope.Clear()
googleCreds.Scope.Add("https://www.googleapis.com/auth/drive.file")
googleCreds.AuthenticationType = "GoogleDrive"
app.UseGoogleAuthentication(googleCreds)
End Class
Here is the solution I came up with. It involves passing a "scope" parameter in the url and then parsing that in the "OnApplyRedirect" function of the Authentication options and then manually injecting the correct scope url into the redirect url.
Dim googleCreds = New GoogleOAuth2AuthenticationOptions() With {
.ClientId = "xxx",
.ClientSecret = "xxx",
.Provider = New Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticationProvider() With { _
.OnApplyRedirect = Function(context)
Dim queryString = HttpContext.Current.Request.QueryString.ToString()
Dim queryParms = HttpUtility.ParseQueryString(queryString)
' Change the value of "redirect" here
' e.g. append access_type=offline
Dim redirect As String = context.RedirectUri
redirect += "&access_type=offline"
redirect += "&approval_prompt=force"
redirect += "&include_granted_scopes=true"
Dim uri = New Uri(redirect)
If (Not String.IsNullOrEmpty(queryParms.Get("scope"))) Then
Dim scope = queryParms.Get("scope")
Dim redirectQueryString = HttpUtility.ParseQueryString(uri.Query)
Select Case scope
Case "Analytics"
redirectQueryString.Set("scope", "https://www.googleapis.com/auth/analytics.readonly")
Case "YoutTube"
redirectQueryString.Set("scope", "https://gdata.youtube.com")
Case "Drive"
redirectQueryString.Set("scope", "https://www.googleapis.com/auth/drive.file")
Case Else
LoggingUtility.LogErrorMessage("Invalid scope passed in: scope: " + scope)
End Select
redirect = uri.GetLeftPart(UriPartial.Path) + "?" + redirectQueryString.ToString()
End If
context.Response.Redirect(redirect)
End Function, _
}
}
'Google Analytics
app.UseGoogleAuthentication(googleCreds)
In OWIN version 3.1 incremental authentication works as expected. (It may well work in earlier versions, I've not tested that).
There were two tricks that were necessary to get this working:
OWIN expects a 401
If you try and call:
var ctx = HttpContext.Current.Request.GetOwinContext();
var properties = new AuthenticationProperties
{
RedirectUri = "/your/redirect",
};
ctx.Authentication.Challenge(properties, "Google");
While logged in to your application, the call to Google from the Middleware will not happen.
This can be confusing, since it just works, when using the exact same code during login.
The reason is that the Middleware only works when the current HTTP response is 401, so after the call above you need something like:
Page.Response.StatusCode = 401;
Page.Response.End();
Specification of scopes is picky
In my original code to try and call with a new scope, I had something like this:
var ctx = HttpContext.Current.Request.GetOwinContext();
var properties = new AuthenticationProperties
{
RedirectUri = redirect,
};
properties.Dictionary["scope"] ="https://www.googleapis.com/auth/calendar.readonly";
This was calling Google as expected and Goggle was correctly challenging based on the requested scopes, but then the response back to my server was breaking (never did find out where).
What I found I needed to do was to resend all my base scopes as well:
var scopes = new List<string>
{
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
};
if (additionalScopes != null)
{
scopes = scopes.Union(additionalScopes).ToList();
}
properties.Dictionary["scope"] = string.Join(" ", scopes);