Configuring multiple SAML2 instances in IdentityServer3 using SustainSys - identityserver3

In IdentityServer3 I have configured multiple instances of SAML2 based external providers using SustainSys library as per the documentation.
I got it working, but i have question about SPOptions.EntityID aka Audience Uri. (This is NOT the EntityID that external provider give us, but instead it's the EntityID that i need to provide to external provider)
Should this Audience Uri be unique for each instance?
Lets say, i configured 2 instances of SAML2 providers(Okta and Azure AD) in production, then based on the sample code provided, for particular environment the Audience Uriwill NOT be unique.
Below is my code based on sample code. (For brevity i have removed few lines)
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var identityServerOptions = new IdentityServerOptions
{
AuthenticationOptions = new AuthenticationOptions()
{
EnableAutoCallbackForFederatedSignout = true,
EnableSignOutPrompt = false
}
.Configure(ConfigureExternalIdentityProviders)
};
idsrvApp.UseIdentityServer(identityServerOptions);
});
}
private void ConfigureExternalIdentityProviders(IAppBuilder app, string signInAsType)
{
// Add okta
AddSAML2Idp(
app,
signInAsType,
"https://id.mydomain.com/identity/Saml2", //audienceURI
"okta", //idpname
"okta", //caption
"https://www.okta.com/exk4yxtgy7ZzSDp8e0h7", // externalEntityID
"https://dev-490944.oktapreview.com/app/exk4yxtgy7ZzSDp8e0h7/sso/saml/metadata"); // metadataLocation
// Add Azure AD
AddSAML2Idp(app,
signInAsType,
"https://id.mydomain.com/identity/Saml2", //audienceURI
"azuread", //idpname
"Azure ad", //caption
"https://sts.windows.net/xxxxx-fb1d-40c4-xxxxx-xxxxxxxx/", //externalEntityID
"https://login.microsoftonline.com/xxxx-fb1d-40c4-40c4-xxxxxxx/federationmetadata/2007-06/federationmetadata.xml?appid=xxxx-xxxx-xxxx-xxxx-xxxxxx"); //metadataLocation
}
private void AddSAML2Idp(IAppBuilder app, string signInAsType,string audienceURI, string idpname, string caption, string externalEntityID, string metadataLocation)
{
var authenticationOptions = new Saml2AuthenticationOptions(false)
{
SPOptions = new SPOptions
{
EntityId = new EntityId(audienceURI),
ModulePath = string.Format("/{0}", idpname)
},
SignInAsAuthenticationType = signInAsType,
AuthenticationType = idpname,
Caption = caption
};
UseIdSrv3LogoutOnFederatedLogout(app, authenticationOptions);
authenticationOptions.SPOptions.ServiceCertificates.Add(LoadCertificateFromWindwosStore());
var identityProvider = new IdentityProvider(new EntityId(externalEntityID), authenticationOptions.SPOptions)
{
MetadataLocation = metadataLocation,
LoadMetadata = true
};
authenticationOptions.IdentityProviders.Add(identityProvider);
app.UseSaml2Authentication(authenticationOptions);
}
So for okata
Audience Uri: https://id.mydomain.com/identity/Saml2
ACS Uri: https://id.mydomain.com/identity/okta/acs
and for Azure AD
Audience Uri: https://id.mydomain.com/identity/Saml2
ACS Uri: https://id.mydomain.com/identity/azuread/acs
Note the audience uri is same for both instances.
Should it be unique for each instances like:
https://id.mydomain.com/identity/okta
https://id.mydomain.com/identity/azuread

Logically the two instances are two different SAML2 Service Providers and should have different Entity IDs. But, as you're not exposing both of them to the same upstream Idp it doesn't matter.

Related

ITfoxtec.Identity.Saml2 - Multiple authentication schemes with Asp.net Core Identity

I am aware of the answer given here which is about using Forms Authentication & SAML. In My case I am using Asp.net core Identity on .Net 5. Also I am using two authentication schemes (Cookies & JWT).
My auth pipeline goes as;
//include identity
services.AddIdentity<ApplicationUser, ApplicationRole>(SetupIdentityOptions)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//configure cookie and Jwt scheme
services.ConfigureApplicationCookie(...)
services.AddAuthentication(...) //configures default Identity
.AddJwtBearer(options => {...})
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
What I want to know is where should I add SAML2 in this pipeline.
In general the application should;
Be able to login with Cookie authentication (Identity takes care of this)
Jwt tokens should work as well for Apis (this also works with Jwt scheme)
SSO clients should get authenticated from their IdP and when redirected to AssertionConsumer() I will create additional claims create new ClaimsIdentity, create a JWT token(pass it to client) and get the user to dashboard.
I am stuck at 3rd point which is to properly add the SAML scheme without overriding cookie/jwt schemes.
The error No sign-in authentication handler is registered for the scheme 'saml2' probably occurs bedause you need to add services.AddSaml2() and app.UseSaml2()
You can use the setup from the provided example project. Newly added middleware should not interfere with what you already have.
When a SAML2 IdP redirects back to your application, you are given a result that identifies the authenticated user, e.g. Email Address or SSN (in case it is a government Id Provider).
You can combine that information with a Role (e.g. SpecialCustomer, Citizen, or an existing Role that you already have) into a cookie or JWT Token as you probably already do for other users. This can always be distinguished from other cookies and tokens by the Role. Regular [Authorize(....)] attributes will also work just fine.
I was stuck at the same point.. The solution I found:
If you check the source code of IFOXTEC.IDENTITY.SAML2, the method AddSaml2 overrides your AddAuthentication method and adds the AddCookie section.
public static IServiceCollection AddSaml2(this IServiceCollection services, string loginPath = "/Auth/Login", bool slidingExpiration = false, string accessDeniedPath = null, ITicketStore sessionStore = null, SameSiteMode cookieSameSite = SameSiteMode.Lax, string cookieDomain = null, CookieSecurePolicy cookieSecurePolicy = CookieSecurePolicy.SameAsRequest)
{
services.AddAuthentication(Saml2Constants.AuthenticationScheme)
.AddCookie(Saml2Constants.AuthenticationScheme, o =>
{
o.LoginPath = new PathString(loginPath);
o.SlidingExpiration = slidingExpiration;
if(!string.IsNullOrEmpty(accessDeniedPath))
{
o.AccessDeniedPath = new PathString(accessDeniedPath);
}
if (sessionStore != null)
{
o.SessionStore = sessionStore;
}
o.Cookie.SameSite = cookieSameSite;
o.Cookie.SecurePolicy = cookieSecurePolicy;
if (!string.IsNullOrEmpty(cookieDomain))
{
o.Cookie.Domain = cookieDomain;
}
});
return services;
}
So, to add SAML to your pipeline, you can remove the services.AddSaml2(), add the AddCookie section and, inside your policy, you can add the verification of any cookie with name saml2 to forward to your SAML schema.
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = "custom-schema";
sharedOptions.DefaultChallengeScheme = "custom-schema";
})
.AddPolicyScheme("custom-schema", null, options =>
{
options.ForwardDefaultSelector = context =>
{
if (context.Request.Headers["Authorization"].Any(x => x.StartsWith("Bearer ")))
return JwtBearerDefaults.AuthenticationScheme;
else if (context.Request.Headers["Cookie"].Any(x => x.Contains(".saml2=")))
return Saml2Constants.AuthenticationScheme;
return "Identity.Application";
};
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, null, options =>
{
//...
})
.AddCookie(Saml2Constants.AuthenticationScheme, o =>
{
o.LoginPath = new PathString("/Auth/Login");
o.SlidingExpiration = false;
o.Cookie.SameSite = SameSiteMode.Lax;
o.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});

How to limit an aspnet core Action for specific Roles with Auth0 and JWT

I'm creating a PWA with React and using Auth0 as my identity provider and JWT Bearer token as authentication. I am injecting roles into my JWT token so that the client-side can limit what options are available to the user and I have this working pretty well.
I want to now limit the server side so that an endpoint can't be called unless the user has the necessary role(s) required to access that endpoint.
Annoyingly, Auth0 doesn't appear to support adding in the roles or role claim that aspnet core seems to handle OOTB; it requires that a domain preface the roles in the claims definition. ie, https://bob.com/roles as the claim.
I'm trying to work out how to get the Authorize(Roles = "Administrator") attribute to honour the domain-prefaced claim for roles.
I have tried updating the Auth0 Rule to set the role or roles property but these never get returned; only the domain-prefaced roles claim seems to return.
I have found other info for more specific Authentication providers and they include a MapJsonKey extension on ClaimActions that look like they would fit the bill, but the standard AuthenticationOptions object in the AddAuthentication extension doesn't appear to have this.
My ConfigureServices in App.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// 1. Add Authentication Services
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = Configuration["Auth0:Authority"];
options.Audience = Configuration["Auth0:ClientId"];
});
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
My Rule for injecting the roles into the JWT in Auth0:
function (user, context, callback) {
const namespace = 'http://bob.com';
const assignedRoles = (context.authorization || {}).roles;
let idTokenClaims = context.idToken || {};
let accessTokenClaims = context.accessToken || {};
idTokenClaims[`roles`] = assignedRoles; // This was an attempt to set the roles in 'roles' but doesn't get returned.
accessTokenClaims[`roles`] = assignedRoles;
idTokenClaims[`${namespace}/roles`] = assignedRoles; // This does get returned
accessTokenClaims[`${namespace}/roles`] = assignedRoles;
context.idToken = idTokenClaims;
context.accessToken = accessTokenClaims;
callback(null, user, context);
}
Example JWT Payload
{
"http://bob.com/roles": [
"Administrator"
],
"given_name": "Name",
"iss": "{issuer}",
"sub": "{subject}",
"aud": "{audience}"
}
asp.net core Action (taken from the example project, but with auth added)
[HttpGet("[action]"), Authorize(Roles = "Administrator")]
public IEnumerable<WeatherForecast> WeatherForecasts()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
DateFormatted = DateTime.Now.AddDays(index).ToString("d"),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
});
}
What I would like is to be able to either map the http://bob.com/roles to roles, get the aspnet core Authorize attribute to look at the http://bob.com/roles, or get Auth0 to be able to return the roles in a 'roles' object.
Where I got the MapJsonKey info from:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/additional-claims?view=aspnetcore-2.2
Using Roles with the ASP.NET Core JWT middleware
https://www.jerriepelser.com/blog/using-roles-with-the-jwt-middleware/
For anyone who finds this, I found a solution to this. If you update the JWT claim to be http://schemas.microsoft.com/ws/2008/06/identity/claims/role then it all works straight away.
Updated Auth0 Rule
function (user, context, callback) {
const assignedRoles = (context.authorization || {}).roles;
let idTokenClaims = context.idToken || {};
let accessTokenClaims = context.accessToken || {};
idTokenClaims[`http://schemas.microsoft.com/ws/2008/06/identity/claims/role`] = assignedRoles;
accessTokenClaims[`http://schemas.microsoft.com/ws/2008/06/identity/claims/role`] = assignedRoles;
context.idToken = idTokenClaims;
context.accessToken = accessTokenClaims;
callback(null, user, context);
}

Getting Claims from Google in ASP.Net Core 2.1 using OpenIdDict

I'm having some trouble getting claims from Google in a ASP.Net Core 2.1 Web API using OpenIdDict.
I've selected "No Authentication" in ASP.Net MVC template since I have no interest in storing usernames / passwords myself. I will be relying on external providers (such as Google) for authentication. The client is a SPA, so I'm using Implicit Flow.
My code is based on following this tutorial (except using Google rather than GitHub):
https://www.jerriepelser.com/blog/implementing-openiddict-authorization-server-part-2/
A Token is being returned from Google - but when I examine the JWT it does not contain any claims information.
What am I missing?
My Startup.cs looks like:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Register OpenIdDict database (EF Core)
services.AddDbContext<PD.Server.DataAccess.AuthorizationDbContext>(o =>
{
o.UseSqlServer(Configuration.GetConnectionString("AuthorizationDbContext"));
o.UseOpenIddict();
});
// Authentication
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
auth.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie()
.AddGoogle(o =>
{
o.ClientId = Configuration["Authentication:Google:ClientId";
o.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
o.CallbackPath = "/signin-google";
});
services.AddOpenIddict()
.AddCore(o => o.UseEntityFrameworkCore().UseDbContext<AuthorizationDbContext>() )
.AddServer(o =>
{
o.UseMvc(); // Register MVC Binder
o.EnableAuthorizationEndpoint("/connect/authorize")
.EnableLogoutEndpoint("/connect/logout"); // Enable the Authorization end-point
o.RegisterScopes(OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
o.AllowImplicitFlow(); // Enable Implicit Flow (i.e. OAuth2 authentication for SPA's)
o.EnableRequestCaching();
o.DisableHttpsRequirement(); // DEV ONLY!
o.AddEphemeralSigningKey(); // DEV ONLY!
})
.AddValidation();
// Cors
services.AddCors();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors(builder =>
{
builder.WithOrigins("https://localhost:5001");
builder.WithMethods("GET");
builder.WithHeaders("Authorization");
});
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
app.MigrateDatabase();
// Configure the OpenIdDict Database (not shown)
InitializeAsync(app.ApplicationServices, CancellationToken.None).GetAwaiter().GetResult();
}
And my Authenticate method:
[HttpGet("~/connect/authorize")]
public IActionResult Authorize(OpenIdConnectRequest request)
{
if (!User.Identity.IsAuthenticated) return Challenge("Google");
var claims = new List<Claim>();
claims.Add(new Claim(OpenIdConnectConstants.Claims.Subject, User.FindFirstValue(ClaimTypes.NameIdentifier), OpenIdConnectConstants.Destinations.IdentityToken));
claims.Add(new Claim(OpenIdConnectConstants.Claims.Name, User.FindFirstValue(ClaimTypes.Name), OpenIdConnectConstants.Destinations.IdentityToken));
claims.Add(new Claim(OpenIdConnectConstants.Claims.Email, User.FindFirstValue(ClaimTypes.Email), OpenIdConnectConstants.Destinations.IdentityToken));
claims.Add(new Claim(OpenIdConnectConstants.Claims.EmailVerified, "true", OpenIdConnectConstants.Destinations.IdentityToken));
var identity = new ClaimsIdentity(claims, "OpenIddict");
var principle = new ClaimsPrincipal(identity);
// Create a new Authentication Ticket
var ticket = new AuthenticationTicket(principle, new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
Thanks in advance.
Your claim destinations are not set correctly.
When using the Claim constructor that takes 3 parameter, it's the claim value type that is actually set, not the destination (which is a concept specific to OpenIddict).
Consider using the following syntax:
claims.Add(new Claim(OpenIdConnectConstants.Claims.Email, User.FindFirstValue(ClaimTypes.Email)).SetDestinations(OpenIdConnectConstants.Destinations.IdentityToken));

IdentityServer3 No scopeclaims when using reference token

In IdentityServer I've added a new scope like this:
new Scope
{
Name = "myscope",
Description = "myscope",
Type=ScopeType.Resource,
ShowInDiscoveryDocument= false,
Emphasize = false,
//AccessTokenType=1, //Reference
AccessTokenType=0, //JWT
Claims = new List<ScopeClaim>
{
new ScopeClaim("location"),
}
I've added a client:
new Client
{
ClientName = "myclient",
Enabled = true,
ClientId = "myclient",
Flow = Flows.Implicit,
AllowedScopes = new List<string> {"myscope"},
Claims = new List<Claim> {new Claim("location", "datacenter")}
}
I've added an implementation of GetProfileData :
public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
await base.GetProfileDataAsync(context);
if (context.AllClaimsRequested)
context.IssuedClaims = context.Subject.Claims;
else if (context.RequestedClaimTypes != null)
context.IssuedClaims = context.Subject.Claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
}
In my webapi, I'm using AccessTokenValidation:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5300",
AllowedScopes = { "myscope" },
RequireHttpsMetadata = false,
});
services.AddAuthorization(options =>
{
options.AddPolicy("location", policy => policy.RequireClaim("location"));
});
My controller is prefixed with:
[Authorize(ActiveAuthenticationSchemes = "Bearer", Policy = "location")]
public async Task<IActionResult> Get()
{
...
}
Now, when the accesstoken is set to JWT, this works fine, I'm able to call the endpoint. Now, if I change AccessTokenType to reference token, it fails...
If I inspect the RequestedClaimTypes during the call to the profiledata endpoint, it holds the claims for 'myscope' when using JWT, but not when using Reference Token...
Am I missing some configuration or is this the way it's supposed work?? I would have expected to get the same claims in both setup
Reference Tokens are not self-contained tokens like JWTs are. They provide an ID that can be used to fetch the information that the reference token represents from a backing store.
If you're using IdentityServer3 out of the box, you should be able to request your reference token from the POST /connect/token endpoint and follow that up with a request to the token introspection endpoint:
POST /connect/accesstokenvalidation
token={tokenReceivedFromPreviousRequest}
This will return the information for that reference token that is kept in its backing store, including scopes.
As a note, that introspection endpoint accepts both Reference Tokens and JWTs.

Implement Custom Authentication In Windows Azure Mobile Services

Windows Azure Mobile Services currently doesn't have an option for custom authentication and looking at the feature request
http://feedback.azure.com/forums/216254-mobile-services/suggestions/3313778-custom-user-auth
It isn't coming anytime soon.
With a .NET backend and a .NET application how do you implement custom authentication, so that you don't have to use Facebook, Google or any of their other current providers?
There are plenty of partially completed tutorials on how this this is done with a JS backend and iOS and Android but where are the .NET examples?
I finally worked through the solution, with some help of the articles listed below, some intellisense and some trial and error.
How WAMS Works
First I wanted to describe what WAMS is in a very simple form as this part confused me for a while until it finally clicked. WAMS is just a collection of pre-existing technologies packaged up for rapid deployment. What you need to know for this scenario is:
As you can see WAMS is really just a container for a WebAPI and other things, which I won't go into detail here. When you create a new Mobile Service in Azure you get to download a project that contains the WebAPI. The example they use is the TodoItem, so you will see code for this scenario through the project.
Below is where you download this example from (I was just doing a Windows Phone 8 app)
I could go on further about this but this tutorial will get you started:
http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-windows-store-dotnet-get-started/
Setup WAMS Project
You will need your MasterKey and ApplicationKey. You can get them from the Azure Portal, clicking on your Mobile Services App and pressing Manage Keys at the bottom
The project you just downloaded, in the Controllers folder I just created a new controller called AccountController.cs and inside I put
public HttpResponseMessage GetLogin(String username, String password)
{
String masterKey = "[enter your master key here]";
bool isValidated = true;
if (isValidated)
return new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent("{ 'UserId' : 'F907F58C-09FE-4F25-A26B-3248CD30F835', 'token' : '" + GetSecurityToken(new TimeSpan(1,0, 0), String.Empty, "F907F58C-09FE-4F25-A26B-3248CD30F835", masterKey) + "' }") };
else
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Username and password are incorrect");
}
private static string GetSecurityToken(TimeSpan periodBeforeExpires, string aud, string userId, string masterKey)
{
var now = DateTime.UtcNow;
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var payload = new
{
exp = (int)now.Add(periodBeforeExpires).Subtract(utc0).TotalSeconds,
iss = "urn:microsoft:windows-azure:zumo",
ver = 2,
aud = "urn:microsoft:windows-azure:zumo",
uid = userId
};
var keyBytes = Encoding.UTF8.GetBytes(masterKey + "JWTSig");
var segments = new List<string>();
//kid changed to a string
var header = new { alg = "HS256", typ = "JWT", kid = "0" };
byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
segments.Add(Base64UrlEncode(headerBytes));
segments.Add(Base64UrlEncode(payloadBytes));
var stringToSign = string.Join(".", segments.ToArray());
var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
SHA256Managed hash = new SHA256Managed();
byte[] signingBytes = hash.ComputeHash(keyBytes);
var sha = new HMACSHA256(signingBytes);
byte[] signature = sha.ComputeHash(bytesToSign);
segments.Add(Base64UrlEncode(signature));
return string.Join(".", segments.ToArray());
}
// from JWT spec
private static string Base64UrlEncode(byte[] input)
{
var output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}
You can replace what is in GetLogin, with your own validation code. Once validated, it will return a security token (JWT) that is needed.
If you are testing on you localhost, remember to go into your web.config file and fill in the following keys
<add key="MS_MasterKey" value="Overridden by portal settings" />
<add key="MS_ApplicationKey" value="Overridden by portal settings" />
You need to enter in your Master and Application Keys here. They will be overridden when you upload them but they need to be entered if you are running everything locally.
At the top of the TodoItemController add the AuthorizeLevel attribute as shown below
[AuthorizeLevel(AuthorizationLevel.User)]
public class TodoItemController : TableController<TodoItem>
You will need to modify most of the functions in your TodoItemController but here is an example of the Get All function.
public IQueryable<TodoItem> GetAllTodoItems()
{
var currentUser = User as ServiceUser;
Guid id = new Guid(currentUser.Id);
return Query().Where(todo => todo.UserId == id);
}
Just a side note I am using UserId as Guid (uniqueidentifier) and you need to add this to the todo model definition. You can make the UserId as any type you want, e.g. Int32
Windows Phone/Store App
Please note that this is just an example and you should clean the code up in your main application once you have it working.
On your Client App
Install NuGet Package: Windows Azure Mobile Services
Go into App.xaml.cs and add this to the top
public static MobileServiceClient MobileService = new MobileServiceClient(
"http://localhost:50527/",
"[enter application key here]"
);
In the MainPage.xaml.cs I created
public class Token
{
public Guid UserId { get; set; }
public String token { get; set; }
}
In the main class add an Authenticate function
private bool Authenticate(String username, String password)
{
HttpClient client = new HttpClient();
// Enter your own localhost settings here
client.BaseAddress = new Uri("http://localhost:50527/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync(String.Format("api/Account/Login?username={0}&password={1}", username, password)).Result;
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var token = Newtonsoft.Json.JsonConvert.DeserializeObject<Token>(response.Content.ReadAsStringAsync().Result);
App.MobileService.CurrentUser = new MobileServiceUser(token.UserId.ToString());
App.MobileService.CurrentUser.MobileServiceAuthenticationToken = token.token;
return true;
}
else
{
//Something has gone wrong, handle it here
return false;
}
}
Then in the Main_Loaded function
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
Authenticate("test", "test");
RefreshTodoItems();
}
If you have break points in the WebAPI, you will see it come in, get the token, then come back to the ToDoItemController and the currentUser will be filled with the UserId and token.
You will need to create your own login page as with this method you can't use the automatically created one with the other identity providers. However I much prefer creating my own login screen anyway.
Any other questions let me know in the comments and I will help if I can.
Security Note
Remember to use SSL.
References
[] http://www.thejoyofcode.com/Exploring_custom_identity_in_Mobile_Services_Day_12_.aspx
[] http://www.contentmaster.com/azure/creating-a-jwt-token-to-access-windows-azure-mobile-services/
[] http://chrisrisner.com/Custom-Authentication-with-Azure-Mobile-Services-and-LensRocket
This is exactly how you do it. This man needs 10 stars and a 5 crates of beer!
One thing, I used the mobile Service LoginResult for login like:
var token = Newtonsoft.Json.JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);
Hope to get this into Android now!