I'm trying to use a single HTTPHandler to authenticate a user's open id and receive a claimresponse. The initial authentication works, but the claimresponse does not. The error I receive is "This webpage has a redirect loop." What am I doing wrong?
public class OpenIdLogin : IHttpHandler
{
private HttpContext _context = null;
public void ProcessRequest(HttpContext context)
{
_context = context;
var openid = new OpenIdRelyingParty();
var response = openid.GetResponse();
if (response == null)
{
// Stage 2: user submitting Identifier
openid.CreateRequest(context.Request.Form["openid_identifier"]).RedirectToProvider();
}
else
{
// Stage 3: OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
//FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, false);
string identifier = response.ClaimedIdentifier;
//****** TODO only proceed if we don't have the user's extended info in the database **************
ClaimsResponse claim = response.GetExtension<ClaimsResponse>();
if (claim == null)
{
//IAuthenticationRequest req = openid.CreateRequest(identifier);
IAuthenticationRequest req = openid.CreateRequest(Identifier.Parse(identifier));
var fields = new ClaimsRequest();
fields.Email = DemandLevel.Request;
req.AddExtension(fields);
req.RedirectingResponse.Send(); //Is this correct?
}
else
{
context.Response.ContentType = "text/plain";
context.Response.Write(claim.Email); //claim.FullName;
}
break;
case AuthenticationStatus.Canceled:
//TODO
break;
case AuthenticationStatus.Failed:
//TODO
break;
}
}
}
The redirect loop is because if you ever get a positive assertion back without an email you send the user right back to ask for it... over and over... The trouble is that some OPs will never give you an email address. And these same OPs may just immediately redirect with another positive assertion that again lacks email address.
When claims don't come back with a positive assertion, you need to show the user a page of your own asking him for those claims personally.
Generally once you get an accepted request, you want to store the claimed identifier and use it for the rest of the session. The message likely means that the provider is denying your request because it believes you have a redirect loop. Instead of verifying the ID on every request, you should accept that the user is authenticated until the session ends.
Related
I am using GenerateEmailConfirmationTokenAsync to generate the token Email Confirmation Link and using ConfirmEmailAsync to Validate the link. It is working fine.
Problem - Link should work only once. If user using same link 2nd time link should be invalid. I debug and found each time ConfirmEmailAsync IdentityResult.IsSucceded true. I was expecting VerifyUserTokenAsync should return false second time but it is always returning true.
Please suggest solution. Thanks
Using .Net Core 3.1, Identity Server 4
// To Generate Token
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
// To Confirm Token
var result = await _userManager.ConfirmEmailAsync(user, code);
// Customised Token Provider
public class EmailConfirmationTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
{
public EmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options, ILogger<EmailConfirmationTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{ }
// Starup.cs Code
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Tokens.EmailConfirmationTokenProvider = "email_confirmation_provider";
options.SignIn.RequireConfirmedEmail = true;
})
.AddDefaultTokenProviders()
.AddTokenProvider<EmailConfirmationTokenProvider<ApplicationUser>>("email_confirmation_provider");
services.Configure<EmailConfirmationTokenProviderOptions>(options =>
{
options.TokenLifespan = TimeSpan.FromSeconds(3600);
});
I think you need to override the behavior of ConfirmEmailAsync to something like this,
If the token matches a known user that indicates that it was a validly issued token.
Will then attempt to confirm token with User manager.
If confirmation fails then token has expired and an appropriate action is taken.
Else if the token confirmed, it is removed from associated user and thus invalidating the reuse of that token.
public override async System.Threading.Tasks.Task<IdentityResult> ConfirmEmailAsync(string userId, string token) {
var user = await FindByIdAsync(userId);
if (user == null) {
return IdentityResult.Failed("User Id Not Found");
}
var result = await base.ConfirmEmailAsync(userId, token);
if (result.Succeeded) {
user.EmailConfirmationToken = null;
return await UpdateAsync(user);
} else if (user.EmailConfirmationToken == token) {
//Previously Issued Token expired
result = IdentityResult.Failed("Expired Token");
}
return result;
}
Similar can be done for password resets.
Approach 2, not tried yet but give a try,
Try to modify the Security timestamps of token to invalidate those once confirmation is done,
UserManager.UpdateSecurityStampAsync(userId);
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.
I am trying to redirect the user to sub domain based on his IP address location. I have a page load observer which runs a function on each request and it gets the user Location and when I try to redirect to another domain it gives me "Too many redirects" error and I can't figure out a way to solve this issue.
Currently my code looks like as follows
string CountryName = "";
var Country = HttpContext.Current.Response.Cookies["Country"];
Country.Expires = DateTime.Now.AddDays(365);
var ip = HttpContext.Current.Request.UserHostAddress;
if (!string.IsNullOrEmpty(ip) && ip != null && ip != "127.0.0.1")
{
using (var client = new WebServiceClient(xxxxx, "xxxxxxxx"))
{
var IpCountry = client.Country(ip);
CountryName = IpCountry.Country.Name;
}
switch (CountryName)
{
case "Denmark":
if (Country.Value != CountryName)
{
Country.Value = CountryName;
HttpContext.Current.Response.Redirect("/");
}
break;
case "United Kingdom":
if (Country.Value != CountryName)
{
Country.Value = CountryName;
HttpContext.Current.Response.Redirect("/en");
}
break;
case "Germany":
if (Country.Value != CountryName)
{
Country.Value = CountryName;
HttpContext.Current.Response.Redirect("/de");
}
break;
case "Sweden":
if (Country.Value != CountryName)
{
Country.Value = CountryName;
HttpContext.Current.Response.Redirect("/se");
}
break;
case "Norway":
if (Country.Value != CountryName)
{
Country.Value = CountryName;
HttpContext.Current.Response.Redirect("/no");
}
break;
default:
if (Country.Value != CountryName)
{
Country.Value = CountryName;
//HttpContext.Current.Response.Redirect("http://www.google.com");
}
break;
}
}
else if (loadedArgs.pageview.Area.ID != 2)
{
HttpContext.Current.Response.Redirect("/choose-country");
}
Further more I also would like to know what could be other possible ways to handle this scenario in more better way so this code don't run on every page load once the cookies are set. Many thanks in advance.
I do not have access to your code, but, if I am reading your code right the fix for the redirect issue is to check for the cookies existence before any creation/ redirection logic. I made some changes, please give this a try and let me know of any issues.
var context = HttpContext.Current;
var cookieName = "Country";
var ip = context.Request.UserHostAddress;
if (!string.IsNullOrWhiteSpace(ip) && ip != "127.0.0.1")
{
//If the cookie is present (means cookie is set already) then return
if (context.Request.Cookies[cookieName] != null)
{
return;
}
string countryName;
using (var client = new WebServiceClient(xxxxx, "xxxxxxxx"))
{
var ipCountry = client.Country(ip);
countryName = ipCountry.Country.Name;
}
context.Response.Cookies.Add(new HttpCookie(cookieName)
{
Value = countryName,
Expires = DateTime.Now.AddDays(365)
});
switch (countryName)
{
case "Denmark":
context.Response.Redirect("/");
break;
case "United Kingdom":
context.Response.Redirect("/en");
break;
case "Germany":
context.Response.Redirect("/de");
break;
case "Sweden":
context.Response.Redirect("/se");
break;
case "Norway":
context.Response.Redirect("/no");
break;
default:
//context.Response.Redirect("http://www.google.com");
break;
}
}
else if (loadedArgs.pageview.Area.ID != 2)
{
context.Response.Redirect("/choose-country");
}
Thank you,
Soma.
you can use the below code to get the client IP address. i.e., the IP address of the machine which requested a page in your website.
String UserIP = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(UserIP))
{
UserIP = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
}
Below is the format we need to use for the API URL.
freegeoip.net/{format}/{IP_or_hostname}
format –> is the response output format. It could be CSV or XML or JSON.
For example, if we would like to get the response in JSON format then we could use the API URL as 'http://freegeoip.net/json/clientIPAddress_here'
string url = "http://freegeoip.net/json/" + UserIP.ToString();
WebClient client = new WebClient();
string jsonstring = client.DownloadString(url);
To get the sample response format, run this http://freegeoip.net/json/XX.XX.XX.XX (replace XX.XX.XX.XX with the required ip address) URL directly in the browser. You will see the below response format.
{
"ip":"xxx.xxx.xxx.xxx",
"country_code":"",
"country_name":"",
"region_code":"",
"region_name":"",
"city":"",
"zip_code":"",
"time_zone":"",
"latitude":0,
"longitude":0,
"metro_code":0
}
Use the below code to convert the response to JSON object and then read the values of JSON object and save to a session variable or some other variable.
dynamic dynObj = JsonConvert.DeserializeObject(jsonstring);
System.Web.HttpContext.Current.Session["LocId"] = dynObj.country_code;
You can also detect the country using the Location header too
On HttpWebRequest you can set AllowAutoRedirect to false to handle the redirect yourself.
// don't allow redirects, they are allowed by default so we're going to override
myRequest.AllowAutoRedirect = false;
// send the request
HttpWebResponse response = myRequest.GetResponse();
// check the header for a Location value
if( response.Headers["Location"] == null )
{
// null means no redirect
}
else
{
// anything non null means we got a redirect
}
Solving the "Too many redirects" issue:
Redirect loop is like a situation wherein > "A points to B and B points back to A"
. Such a redirection will keep browser in an infinite loop and the webpage will never be displayed. In old times, such redirect or infinite loops used to result in a hung browser.Thankfully, modern browsers are able to detect such redirect loops and they break the cycle by displaying an error message: “Error 310 (net::ERR_TOO_MANY_REDIRECTS): there were too many redirects”.
This problem may be caused either at the client end or the server end. If there is no redirection loop on the server side, the problem is likely to be solved just by deleting cookies from the browser.
So I suggest please check your routeconfig.cs for such route Or similar redirect operation that points back to itself after deleting the cookies from the browser
Hope this helps :)
I think the problem lies in the logic that does not identify if the request is before or after redirecting. The sequence seems to be
The code executes once when a URL xyz.com
The redirect to xyz.com/en happens
Again the code executes and tries to redirect to xyz.com/en/en
This can be confirmed with some more debugging.
I suggest your code should execute and redirect if the URL being requested doesn't end with "en" or "de" (the list can be maintained in the application)
You might need to change the way you handle your default country too, because if the logic is no country indicates default country, then what I mentioned above will not work.
In Asp.Net MVC, I have a class that handles invalid IP addresses and re-directs them based on the IP address. I use the following code:
protected override async void HandleUnauthorizedRequest(AuthorizationContext context)
{
var ipAddress = HttpContext.Current.Request.UserHostAddress;
if (await IpAddressValid(ipAddress)) return;
var urlHelper = new UrlHelper(context.RequestContext);
var address = urlHelper.Action("InvalidIpRange", "Account");
context.Result = new RedirectResult(address ?? "www.google.com");
}
private async Task<bool> IpAddressValid(string ipAddress)
{
if (ipAddress == "::1")
return true;
var longIp = Ip2Int(ipAddress);
var addr = await GeoIPCountry.ReturnGeoIpCountries(longIp, longIp); //dbContext.GeoIPCountries.Where(x => x.Start >= longIp && x.End <= longIp);
return addr.All(g => g.CountryCode == "US");
}
/// <summary>
/// Converts IP Address to Long Stack Space.
/// </summary>
/// <param name="ipAddress"></param>
/// <returns></returns>
public int Ip2Int(string ipAddress)
{
try
{
var addr = IPAddress.Parse(ipAddress);
uint ip =
(uint) IPAddress.NetworkToHostOrder((int) BitConverter.ToUInt32(addr.GetAddressBytes(), 0));
return (int)ip;
}
catch (Exception)
{
return 0;
}
}
At the top of each controller that requires it, all you have to do is add the [IpAddressAuthorize] flag and it will handle everything. This happens before any other action so if you should be able to modify it pretty easily.
You don't provide much information on the 'observer that runs on every request', but if it really does run on every request, it will simply run again when you redirect the user after checking their country.
To debug this, simply place breakpoints wherever you call a redirect, and check which one gets repeatedly called.
For example, let's say that the user is from Denmark, in which case you
context.Response.Redirect("/");
But this will cause your 'observer' to run again and redirect again! So you need to check if the redirection already occurred by placing another cookie, or by checking whether the requested page is already a language-specific page and therefore there is no need to redirect again.
I am working on a cross platform web app using angular and webapi. The problem is when the angular app runs in a cordova container. To play nice with the rest of the applications on the device, I am required to use a plugin for SSO.. This plugin is what is causing me issues, because it does a few things. It intercepts all the http requests and adds a bearer token to the header, which is generated by a 3rd party Token provider, so I can't decode it, and overwrites any bearer token I have set in the header.It also seems to block cookies..
So it makes it a bit tricky when you can't send you own local credentials.
So I started with https://coding.abel.nu/2014/06/writing-an-owin-authentication-middleware/ and http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.OAuth/OAuthBearerAuthenticationHandler.cs
So I figured I should write my own middleware to take care of this; I thought since the standard oauth middleware can work without cookies, I should not have too hard a time getting my slightly different bearer token middleware to do it.. But that has not been the case... Writing my own middleware.. so I'm able to get the header, validate with the external token provider, but I can't actually sign in.
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
try
{
// Find token in default location
string requestToken = null;
string authorization = Request.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorization))
{
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
requestToken = authorization.Substring("Bearer ".Length).Trim();
}
}
.... Take the Request token call other Server, verify token...
Also
public override async Task<bool> InvokeAsync()
{
var ticket = await this.AuthenticateAsync();
if(ticket != null)
{
this.Context.Authentication.SignIn(new AuthenticationProperties(), grantIdentity);
return false;
}
}
So in the end the SignIn does not cause a error or anything, but does not actually signin. As soon as I get to a controller action with an [Authorize] attribute, I get a 401. I not have any external cookies enabled. There is a high probability that I am on the wrong track or I am making it way too hard.
You are doing it way too hard.
Instead of creating your own bearer authentication middleware you should change the default OAuthBearerAuthenticationProvider.
Here is a sample for sending the token in the query string.
//in Startup class
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new QueryStringOAuthBearerProvider(),
//your settings
});
//implementation
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
private const string AccessTokenQueryKey = "access_token";
public override Task RequestToken(OAuthRequestTokenContext context)
{
//check if token found in the default location - "Authorization: Bearer <token>" header
if (string.IsNullOrEmpty(context.Token))
{
var token = context.Request.Query.Get(AccessTokenQueryKey);
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
}
return Task.FromResult<object>(null);
}
}
So… I ment to answer it earlier, but I was able to figure it out, without override the authorize attribute. I ended up looking at the source for the OWIN security code. The trick is, you really need 2 OWIN middleware components. One is the what I call (and I stole this from the owin source) the server middleware. The server middleware responds to the challenge and/or if you are feeling crazy generate local credentials for you. This middleware is also a PASSIVE middleware component. I won’t get in to generating the local credentials unless someone asks , because it’s a bit off point, but if someone thinks it will be helpful, I can update.
public class LowCalorieAuthenticationServerHandler : AuthenticationHandler<LowCalorieAuthenticationServerOptions>
{
//Important this needs to be overriden, but just calls the base.
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
return Task.FromResult<AuthenticationTicket>(null);
}
/// <summary>The apply response challenge async.</summary>
/// <returns>The <see cref="Task"/>.</returns>
protected override async Task ApplyResponseChallengeAsync()
{
if (this.Response.StatusCode != 401)
{
Task.FromResult<object>(null);
return;
}
var challenge = this.Helper.LookupChallenge(
this.Options.AuthenticationType,
this.Options.AuthenticationMode);
if (challenge != null)
{
//OK in here you call the rediret to the 3rd party
//return a redirect to some endpoint
}
Task.FromResult<object>(null);
return;
}
}
Anyway notice how the override AuthenticateCoreAsync() just returns
return Task.FromResult(null);
This is because we don’t want this middleware to modify the request. ApplyResponseChallengeAsync will wait for a Challenge and redirect you to the 3rd party login. IF you want to create a local token of some sort you would override the InvokeAsync method
The second middle ware you need is the token/external credentials validator. This will then authenticate the user somehow. In the case of the local bearer token that is built into the OWIN security, it simple deserializes the token and if it can, and the token is not expired it authenticates the user. So in the case that you want to verify the token with a 3rd part sso, such as google or anything, you insert you logic here. In my case I not only wanted to call the 3rd party provider to get the user info, but to check if they token was still valid for single sign out, and to prevent multiple sessions.
public class LowCalorieAuthenticationHandler : AuthenticationHandler<LowCalorieAuthenticationOptions>
{
//Going to give you the user for the request.. You Need to do 3 things here
//1. Get the user claim from teh request somehow, either froma header, request string, or cookie what ever you want
//2. validate the user with whatever user store or 3rd party SSO you want
//3. Generate a AuthenticationTicket to send to on to the request, you can use that to see if the user is valid in any Identity collection you want.
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
//Good to throw in a point of override here.. but to keep it simple-ish
string requestToken = null;
string authorization = Request.Headers.Get("Authorization");
//TOTAL FAKEOUT.. I am going to add a bearer token just so the simple sample works, but your client would have to provide this
authorization = "Bearer 1234567869";
//STEP 1
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
requestToken = authorization.Substring("Bearer ".Length).Trim();
return await FakeExternalBearer(requestToken);
}
return null;
}
private async Task<AuthenticationTicket> FakeExternalBearer(string token)
{
var authenticationType = Options.AuthenticationType;
//pretend to call extenal Resource server to get user //STEP 2
//CallExternal(token)
//Create the AuthTicket from the return.. I will fake it out
var identity = new ClaimsIdentity(
authenticationType,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier,"user1", null, authenticationType));
identity.AddClaim(new Claim(ClaimTypes.Name, "Jon",null, authenticationType));
var properties = new AuthenticationProperties();
properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(1);
properties.IssuedUtc = DateTime.UtcNow;
var ticket = new AuthenticationTicket(identity, properties);
return ticket;
}
}
Ok here we override AuthenticateCoreAsync, but we actually do something now. This this were your do you user authentication. This is the ACTIVE part of the middleware. Note it needs to return a valid AuthenticationTicket. This will run on each request so be careful what you call and how often.
So I have a very simple example here https://github.com/jzoss/LowCalorieOwin If anyone is interested in more detail, please ask. I can add more. I did make it too hard, because now that I understand it, it’s pretty easy, but there is really no good examples on how to do this.
I want to use DotNetOpenAuth in my website for authentication + authorization (gmail).
However, I would like to ask: What should I persist?
I thought:
In the DB: for each user save a Guid and his gmail (fetched)
In formAuthentication cookie the Guid I have assigned to that user.
Any other suggestions?
public bool Login()
{
IAuthenticationResponse authResponse = GoogleConsumerHandler.RelyingParty.GetResponse();
if (authResponse != null)
{
HandleAuthResponse(authResponse);
}
else
{
HandleAuthNullResponse(authResponse);
}
return false;
}
#region private methods
private void HandleAuthResponse(IAuthenticationResponse authResponse)
{
switch (authResponse.Status)
{
case AuthenticationStatus.Authenticated:
State.FetchResponse = authResponse.GetExtension<FetchResponse>();
var consumer = new WebConsumer(GoogleConsumerHandler.ServiceDescription, mConsumerTokenManager);
AuthorizedTokenResponse accessToken = consumer.ProcessUserAuthorization(authResponse);
if (accessToken != null)
{
var email = authResponse.ClaimedIdentifier;
//existing or new
Guid userId = mCRMService.GetUserId(email, accessToken.AccessToken);
State.GoogleAccessToken = accessToken.AccessToken;
FormsAuthentication.SetAuthCookie(userId.ToString(), false);
//authenticat and authorized
//Response.Redirect("~/Browser.htm");
}
else
{
//authenticated and not authorized
//MultiView1.SetActiveView(AuthorizationDenied);
}
break;
case AuthenticationStatus.Canceled:
break;
case AuthenticationStatus.Failed:
break;
default:
//not authenticated
//this.MultiView1.SetActiveView(this.AuthenticationFailed);
break;
}
}
private void HandleAuthNullResponse(IAuthenticationResponse authResponse)
{
// Google requires that the realm and consumer key be equal,
// so we constrain the realm to match the realm in the web.config file.
// This does mean that the return_to URL must also fall under the key,
// which means this sample will only work on a public web site
// that is properly registered with Google.
// We will customize the realm to use http or https based on what the
// return_to URL will be (which will be this page).
var consumer = new WebConsumer(GoogleConsumerHandler.ServiceDescription, mConsumerTokenManager);
//Realm realm = "http://localhost:8976/";
Realm realm = System.Web.HttpContext.Current.Request.Url.Scheme + Uri.SchemeDelimiter + consumer.ConsumerKey + "/";
IAuthenticationRequest authReq = GoogleConsumerHandler.RelyingParty.CreateRequest(GoogleConsumerHandler.GoogleOPIdentifier, realm);
// Prepare the OAuth extension
string scope = GoogleConsumerHandler.GetScopeUri(GoogleConsumerHandler.Applications.Gmail);
consumer.AttachAuthorizationRequest(authReq, scope);
// We also want the user's email address
var fetch = new FetchRequest();
fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
authReq.AddExtension(fetch);
authReq.RedirectToProvider();
}
For authentication purposes you should store the OpenID ClaimedIdentifier you get back in the IAuthenticationResponse object. That serves as the "primary key" for your users so you can recognize them when they return. I suggest you use the claimed_id as the FormsAuthentication username instead of a random GUID as well. Also storing the email address you collect is fine, but it's inadvisable to use that as the means to recognize a returning user.
Remember that you can't log in "gmail users". You can log in OpenID users, that may use any Provider. You can limit that to "Google" users by filtering on the IAuthenticationResponse.Provider.Uri for the Google OP Endpoint, but even then you're not guaranteed that those accounts use Gmail (their email address might be foo#bar.com anyway).
Finally, if all you need is their authentication and email address (whatever email that is) you can do so using the OpenID AX extension (built into DNOA) and you don't need "authorization", which might greatly simplify your code.